/* * zipfs.c -- * * Implementation of the ZIP filesystem used in AndroWish. * * Copyright (c) 2013-2019 Christian Werner * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclFileSystem.h" #include "zipfs.h" #if !defined(_WIN32) && !defined(_WIN64) #include #endif #include #include #include #include #include #include #ifndef MAP_FILE #define MAP_FILE 0 #endif #ifdef __FreeBSD__ /* * On FreeBSD the fdlopen(3) can be used in conjunction with shm_open(3) * for loading native code. */ #define ZIPFS_MEMLOAD 1 #include #endif #if defined(linux) && !defined(ANDROID) /* * On Linux >= 3.17 the memfd_create(2) system call can be * potentially used in combination with dlopen(3) for loading * native code. Otherwise, shm_open(3) can be used, too. */ #define ZIPFS_MEMLOAD 1 #include #include #include #ifndef __NR_memfd_create #define __NR_memfd_create 319 #endif /* Wrapper to call memfd_create(2). */ static inline int memfd_create(const char *name, unsigned int flags) { return syscall(__NR_memfd_create, name, flags); } #endif #ifdef HAVE_ZLIB #include "zlib.h" #include "zcrypt.h" /* * Various constants and offsets found in ZIP archive files. */ #define ZIP_SIG_LEN 4 /* Local header of ZIP archive member (at very beginning of each member). */ #define ZIP_LOCAL_HEADER_SIG 0x04034b50 #define ZIP_LOCAL_HEADER_LEN 30 #define ZIP_LOCAL_SIG_OFFS 0 #define ZIP_LOCAL_VERSION_OFFS 4 #define ZIP_LOCAL_FLAGS_OFFS 6 #define ZIP_LOCAL_COMPMETH_OFFS 8 #define ZIP_LOCAL_MTIME_OFFS 10 #define ZIP_LOCAL_MDATE_OFFS 12 #define ZIP_LOCAL_CRC32_OFFS 14 #define ZIP_LOCAL_COMPLEN_OFFS 18 #define ZIP_LOCAL_UNCOMPLEN_OFFS 22 #define ZIP_LOCAL_PATHLEN_OFFS 26 #define ZIP_LOCAL_EXTRALEN_OFFS 28 /* Central header of ZIP archive member at end of ZIP file. */ #define ZIP_CENTRAL_HEADER_SIG 0x02014b50 #define ZIP_CENTRAL_HEADER_LEN 46 #define ZIP_CENTRAL_SIG_OFFS 0 #define ZIP_CENTRAL_VERSIONMADE_OFFS 4 #define ZIP_CENTRAL_VERSION_OFFS 6 #define ZIP_CENTRAL_FLAGS_OFFS 8 #define ZIP_CENTRAL_COMPMETH_OFFS 10 #define ZIP_CENTRAL_MTIME_OFFS 12 #define ZIP_CENTRAL_MDATE_OFFS 14 #define ZIP_CENTRAL_CRC32_OFFS 16 #define ZIP_CENTRAL_COMPLEN_OFFS 20 #define ZIP_CENTRAL_UNCOMPLEN_OFFS 24 #define ZIP_CENTRAL_PATHLEN_OFFS 28 #define ZIP_CENTRAL_EXTRALEN_OFFS 30 #define ZIP_CENTRAL_FCOMMENTLEN_OFFS 32 #define ZIP_CENTRAL_DISKFILE_OFFS 34 #define ZIP_CENTRAL_IATTR_OFFS 36 #define ZIP_CENTRAL_EATTR_OFFS 38 #define ZIP_CENTRAL_LOCALHDR_OFFS 42 /* Central end signature at very end of ZIP file. */ #define ZIP_CENTRAL_END_SIG 0x06054b50 #define ZIP_CENTRAL_END_LEN 22 #define ZIP_CENTRAL_END_SIG_OFFS 0 #define ZIP_CENTRAL_DISKNO_OFFS 4 #define ZIP_CENTRAL_DISKDIR_OFFS 6 #define ZIP_CENTRAL_ENTS_OFFS 8 #define ZIP_CENTRAL_TOTALENTS_OFFS 10 #define ZIP_CENTRAL_DIRSIZE_OFFS 12 #define ZIP_CENTRAL_DIRSTART_OFFS 16 #define ZIP_CENTRAL_COMMENTLEN_OFFS 20 #define ZIP_MIN_VERSION 20 #define ZIP_COMPMETH_STORED 0 #define ZIP_COMPMETH_DEFLATED 8 #define ZIP_PASSWORD_END_SIG 0x5a5a4b50 /* * Macros to read and write 16 and 32 bit integers from/to ZIP archives. */ #define ZipReadInt(p) \ ((p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24)) #define ZipReadShort(p) \ ((p)[0] | ((p)[1] << 8)) #define ZipWriteInt(p, v) \ do { \ (p)[0] = (v) & 0xff; \ (p)[1] = ((v) >> 8) & 0xff; \ (p)[2] = ((v) >> 16) & 0xff; \ (p)[3] = ((v) >> 24) & 0xff; \ } while (0) #define ZipWriteShort(p, v) \ do { \ (p)[0] = (v) & 0xff; \ (p)[1] = ((v) >> 8) & 0xff; \ } while (0) /* * Windows drive letters. */ #if defined(_WIN32) || defined(_WIN64) #define HAS_DRIVES 1 static const char drvletters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; #else #define HAS_DRIVES 0 #endif /* * Mutex to protect localtime(3) when no reentrant version are available. */ #if !defined(_WIN32) && !defined(_WIN64) #ifndef HAVE_LOCALTIME_R #ifdef TCL_THREADS TCL_DECLARE_MUTEX(localtimeMutex) #endif #endif #endif /* * In-core description of mounted ZIP archive file. */ typedef struct ZipFile { char *name; /* Archive name */ Tcl_Channel chan; /* Channel handle or NULL */ unsigned char *data; /* Memory mapped or malloc'ed file */ long length; /* Length of memory mapped file */ int ismapped; /* True when data must be unmapped */ unsigned char *tofree; /* Non-NULL if malloc'ed file */ int nfiles; /* Number of files in archive */ int baseoffs; /* Archive start */ int baseoffsp; /* Password start */ int centoffs; /* Archive directory start */ char pwbuf[264]; /* Password buffer */ #if defined(_WIN32) || defined(_WIN64) HANDLE mh; #endif int nopen; /* Number of open files on archive */ struct ZipEntry *entries; /* List of files in archive */ struct ZipEntry *topents; /* List of top-level dirs in archive */ #if HAS_DRIVES int mntdrv; /* Drive letter of mount point */ #endif int mntptlen; /* Length of mount point */ char mntpt[1]; /* Mount point */ } ZipFile; /* * In-core description of file contained in mounted ZIP archive. */ typedef struct ZipEntry { char *name; /* The full pathname of the virtual file */ ZipFile *zipfile; /* The ZIP file holding this virtual file */ long offset; /* Data offset into memory mapped ZIP file */ int nbyte; /* Uncompressed size of the virtual file */ int nbytecompr; /* Compressed size of the virtual file */ int cmeth; /* Compress method */ int isdir; /* Set to 1 if directory, -1 if root */ int depth; /* Number of slashes in path. */ int crc32; /* CRC-32 */ int timestamp; /* Modification time */ int isenc; /* True if data is encrypted */ unsigned char *data; /* File data if written */ struct ZipEntry *next; /* Next file in the same archive */ struct ZipEntry *tnext; /* Next top-level dir in archive */ struct ZipDirEntry *parent; /* Pointer to parent directory */ } ZipEntry; /* * In-core description of directory contained in mounted ZIP archive. */ typedef struct ZipDirEntry { struct ZipEntry entry; /* See above */ Tcl_HashTable children; /* List of children of directory, * with ZipEntry pointer keys */ } ZipDirEntry; /* * File channel for file contained in mounted ZIP archive. */ typedef struct ZipChannel { ZipFile *zipfile; /* The ZIP file holding this channel */ ZipEntry *zipentry; /* Pointer back to virtual file */ unsigned long nmax; /* Max. size for write */ unsigned long nbyte; /* Number of bytes of uncompressed data */ unsigned long nread; /* Pos of next byte to be read from the channel */ unsigned char *ubuf; /* Pointer to the uncompressed data */ unsigned char *tofree; /* Pointer to free on close or NULL */ int iscompr; /* True if data is compressed */ int isdir; /* Set to 1 if directory, -1 if root */ int iswr; /* True if open for writing */ Tcl_Channel chan; /* Tcl core's channel structure */ int evmask; /* TCL_READABLE and friends */ } ZipChannel; /* * List of ZipChannels in thread. */ typedef struct ThreadSpecificData { int initialized; Tcl_HashTable chanTab; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Additional bits in ZipChannel.evmask. */ #define ZIPCHANNEL_NONBLOCK (TCL_EXCEPTION << 8) #define ZIPCHANNEL_PENDING (TCL_EXCEPTION << 9) /* * Structure describing file event on ZipChannel. */ typedef struct ZipEvent { Tcl_Event header; /* Standard event information */ ZipChannel *info; /* Pointer to ZipChannel */ } ZipEvent; /* * Global variables. * * Most are kept in single ZipFS struct. When build with threading * support this struct is protected by the ZipFSMutex (see below). * * The "zipHash" component is the process wide global table of all mounted * ZIP archive files; keys are mount point path names. * * The "fileHash" component is the process wide global table of all known * ZIP archive members in all mounted ZIP archives; keys are path names. * * The "dirHash" component is the process wide global table of all known * ZIP directory archive members in all mounted ZIP archives; keys are * ZipDirEntry pointers. */ static struct { int initialized; /* True when initialized */ int lock; /* RW lock, see below */ int waiters; /* RW lock, see below */ int wrmax; /* Maximum write size of a file */ int idCount; /* Counter for channel names */ Tcl_HashTable zipHash; /* Mount to ZipFile mapping */ Tcl_HashTable fileHash; /* File name to ZipEntry mapping */ Tcl_HashTable dirHash; /* Like fileHash but directories only */ } ZipFS = { 0, 0, 0, 0, 0, }; /* * For password rotation. */ static const char pwrot[16] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0 }; /* * Table to compute CRC32. */ static const unsigned int crc32tab[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, }; /* * Forward declarations. */ static ZipEntry * ZipFSLookup(char *filename); static ThreadSpecificData * ZipChannelInit(void); static int ZipEventProc(Tcl_Event *evPtr, int flags); /* * For embedding a ZIP in the binary. */ #if defined(ZIPFS_IN_TCL) || defined(ZIPFS_IN_TK) #if defined(MAC_OSX_TCL) #define ZIPFS_USE_ZEMBED 1 #include #include static unsigned char *_binary_zembed_start = NULL; static unsigned char *_binary_zembed_end = NULL; #else #if defined(__GNUC__) && !defined(ANDROID) #define ZIPFS_USE_ZEMBED 1 typedef unsigned char zembed_data[0]; const zembed_data _binary_zembed_start __attribute__((weak)); extern const zembed_data _binary_zembed_end __attribute__((weak, alias("_binary_zembed_start"))); #endif #endif #endif /* *------------------------------------------------------------------------- * * ReadLock, WriteLock, Unlock -- * * POSIX like rwlock functions to support multiple readers * and single writer on internal structs. * * Limitations: * - a read lock cannot be promoted to a write lock * - a write lock may not be nested * *------------------------------------------------------------------------- */ TCL_DECLARE_MUTEX(ZipFSMutex) #ifdef TCL_THREADS static Tcl_Condition ZipFSCond; static void ReadLock(void) { Tcl_MutexLock(&ZipFSMutex); while (ZipFS.lock < 0) { ZipFS.waiters++; Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, NULL); ZipFS.waiters--; } ZipFS.lock++; Tcl_MutexUnlock(&ZipFSMutex); } static void WriteLock(void) { Tcl_MutexLock(&ZipFSMutex); while (ZipFS.lock != 0) { ZipFS.waiters++; Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, NULL); ZipFS.waiters--; } ZipFS.lock = -1; Tcl_MutexUnlock(&ZipFSMutex); } static void Unlock(void) { Tcl_MutexLock(&ZipFSMutex); if (ZipFS.lock > 0) { --ZipFS.lock; } else if (ZipFS.lock < 0) { ZipFS.lock = 0; } if ((ZipFS.lock == 0) && (ZipFS.waiters > 0)) { Tcl_ConditionNotify(&ZipFSCond); } Tcl_MutexUnlock(&ZipFSMutex); } #else #define ReadLock() do {} while (0) #define WriteLock() do {} while (0) #define Unlock() do {} while (0) #endif /* *------------------------------------------------------------------------- * * DosTimeDate, ToDosTime, ToDosDate -- * * Functions to perform conversions between DOS time stamps * and POSIX time_t. * *------------------------------------------------------------------------- */ static time_t DosTimeDate(int dosDate, int dosTime) { struct tm tm; time_t ret; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; /* let mktime() deal with DST */ 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 & 0x7e0) >> 5; tm.tm_sec = (dosTime & 0x1f) << 1; ret = mktime(&tm); if (ret == (time_t) -1) { /* fallback to 1980-01-01T00:00:00+00:00 (DOS epoch) */ ret = (time_t) 315532800; } return ret; } static int ToDosTime(time_t when) { struct tm *tmp, tm; #ifdef TCL_THREADS #if defined(_WIN32) || defined(_WIN64) /* Win32 uses thread local storage */ tmp = localtime(&when); tm = *tmp; #else #ifdef HAVE_LOCALTIME_R tmp = &tm; localtime_r(&when, tmp); #else Tcl_MutexLock(&localtimeMutex); tmp = localtime(&when); tm = *tmp; Tcl_MutexUnlock(&localtimeMutex); #endif #endif #else tmp = localtime(&when); tm = *tmp; #endif return (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); } static int ToDosDate(time_t when) { struct tm *tmp, tm; #ifdef TCL_THREADS #if defined(_WIN32) || defined(_WIN64) /* Win32 uses thread local storage */ tmp = localtime(&when); tm = *tmp; #else #ifdef HAVE_LOCALTIME_R tmp = &tm; localtime_r(&when, tmp); #else Tcl_MutexLock(&localtimeMutex); tmp = localtime(&when); tm = *tmp; Tcl_MutexUnlock(&localtimeMutex); #endif #endif #else tmp = localtime(&when); tm = *tmp; #endif return ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; } /* *------------------------------------------------------------------------- * * MemUnload -- * * Unloads a dynamically loaded binary code file from memory. Code * pointers in the formerly loaded file are no longer valid after calling * this function. * * Results: * None. * * Side effects: * Code removed from memory. * *------------------------------------------------------------------------- */ #ifdef ZIPFS_MEMLOAD static void MemUnload(Tcl_LoadHandle loadHandle) { void *handle = loadHandle->clientData; int shm_fd = ((int *)(loadHandle + 1))[0]; dlclose(handle); ckfree(loadHandle); if (shm_fd >= 0) { close(shm_fd); } } #endif /* *---------------------------------------------------------------------- * * MemSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ #ifdef ZIPFS_MEMLOAD static void * MemSymbol( Tcl_Interp *interp, /* Place to put error messages. */ Tcl_LoadHandle loadHandle, /* Value from TcpDlopen(). */ const char *symbol) /* Symbol to look up. */ { const char *native; /* Name of the library to be loaded, in * system encoding */ Tcl_DString newName, ds; /* Buffers for converting the name to * system encoding and prepending an * underscore*/ void *handle = (void *) loadHandle->clientData; /* Native handle to the loaded library */ void *proc; /* Address corresponding to the resolved * symbol */ /* * Some platforms still add an underscore to the beginning of symbol * names. If we can't find a name without an underscore, try again with * the underscore. */ native = Tcl_UtfToExternalDString(NULL, symbol, -1, &ds); proc = dlsym(handle, native); /* INTL: Native. */ if (proc == NULL) { Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); native = Tcl_DStringAppend(&newName, native, -1); proc = dlsym(handle, native); /* INTL: Native. */ Tcl_DStringFree(&newName); } Tcl_DStringFree(&ds); if (proc == NULL) { const char *errorStr = dlerror(); if (interp) { if (!errorStr) { errorStr = "unknown"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\": %s", symbol, errorStr)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, NULL); } } return proc; } #endif /* *------------------------------------------------------------------------- * * MemLoad -- * * Load a shared library using dlopen(3) and the memfd_create(2) * system call available since kernel version 3.17. The technique * is borrowed from * https://x-c3ll.github.io/posts/fileless-memfd_create * * Results: * A standard Tcl completion code. * * Side effects: * New code may suddenly appear in memory. * *------------------------------------------------------------------------- */ #ifdef ZIPFS_MEMLOAD static int MemLoad(Tcl_Obj *pathPtr, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags) { #ifdef linux struct utsname uts; int major, minor; #endif int shm_fd = -1, i, ch, dlopenFlags = 0, isshm = -1; ZipEntry *z = NULL; unsigned long keys[3]; void *handle; Tcl_LoadHandle newHandle; unsigned char *in, *mbuf = MAP_FAILED; char path0[256], *path = NULL, *name, *tail; #ifdef linux memset(&uts, 0, sizeof(uts)); uname(&uts); if ((sscanf(uts.release, "%d.%d", &major, &minor) != 2) || (major < 3) || ((major == 3) && (minor < 17))) { /* Kernel version is less than 3.17, use shm_open(3). */ isshm = 1; } else { isshm = 0; } #endif /* Use (RTLD_NOW|RTLD_LOCAL) as default. */ if (flags & TCL_LOAD_GLOBAL) { dlopenFlags |= RTLD_GLOBAL; } else { dlopenFlags |= RTLD_LOCAL; } if (flags & TCL_LOAD_LAZY) { dlopenFlags |= RTLD_LAZY; } else { dlopenFlags |= RTLD_NOW; } strcpy(path0, "/dev/shm"); /* Used for dlopen(3). */ path = path0 + strlen(path0); /* Used for shm_open(3). */ name = Tcl_GetString(pathPtr); WriteLock(); z = ZipFSLookup(name); if ((z == NULL) || z->isdir || ((z->cmeth != ZIP_COMPMETH_STORED) && (z->cmeth != ZIP_COMPMETH_DEFLATED))) { goto error; } if (z->data == NULL) { in = z->zipfile->data + z->offset; } else { in = z->data; } tail = strrchr(name, '/'); if (tail != NULL) { ++tail; } else { tail = name; } #ifdef linux if (!isshm) { shm_fd = memfd_create(tail, 1); /* MFD_CLOEXEC */ if (shm_fd < 0) { /* Fall back to shm_open(3). */ isshm = 1; } } #endif if (isshm) { sprintf(path, "/%d_%.230s", getpid(), tail); shm_fd = shm_open(path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600); } if (shm_fd < 0) { goto error; } if (ftruncate(shm_fd, z->nbyte) < 0) { goto error; } mbuf = (unsigned char *) mmap(0, z->nbyte, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, shm_fd, 0); if (mbuf == MAP_FAILED) { goto error; } if (z->isenc) { int len = z->zipfile->pwbuf[0] & 0xff; char pwbuf[260]; for (i = 0; i < len; i++) { ch = z->zipfile->pwbuf[len - i]; pwbuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; } pwbuf[i] = '\0'; init_keys(pwbuf, keys, crc32tab); memset(pwbuf, 0, sizeof(pwbuf)); for (i = 0; i < 12; i++) { ch = in[i]; zdecode(keys, crc32tab, ch); } in += i; } if (z->cmeth == ZIP_COMPMETH_DEFLATED) { z_stream stream; int err; unsigned char *ubuf = NULL; unsigned int j; memset(&stream, 0, sizeof(stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = z->nbytecompr; if (z->isenc) { stream.avail_in -= 12; ubuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in); if (ubuf == NULL) { goto error; } for (j = 0; j < stream.avail_in; j++) { ch = in[j]; ubuf[j] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); stream.next_in = ubuf; } else { stream.next_in = in; } stream.next_out = mbuf; stream.avail_out = z->nbyte; if (inflateInit2(&stream, -15) != Z_OK) { goto inflError; } err = inflate(&stream, Z_SYNC_FLUSH); inflateEnd(&stream); if ((err == Z_STREAM_END) || ((err == Z_OK) && (stream.avail_in == 0))) { if (ubuf != NULL) { Tcl_Free((char *) ubuf); } goto success; } inflError: if (ubuf != NULL) { Tcl_Free((char *) ubuf); } goto error; } else if (z->isenc) { unsigned int j, len; len = z->nbytecompr - 12; for (j = 0; j < len; j++) { ch = in[j]; mbuf[j] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); } else { memcpy(mbuf, in, z->nbyte); } success: munmap(mbuf, z->nbyte); Unlock(); if (!isshm) { sprintf(path0, "/proc/%d/fd/%d", getpid(), shm_fd); } #ifdef __FreeBSD__ handle = fdlopen(shm_fd, dlopenFlags); #else handle = dlopen(path0, dlopenFlags); #endif if (isshm) { close(shm_fd); shm_fd = -1; shm_unlink(path); } if (handle == NULL) { if (shm_fd >= 0) { close(shm_fd); } return TCL_ERROR; } newHandle = ckalloc(sizeof(*newHandle) + sizeof(int)); newHandle->clientData = handle; newHandle->findSymbolProcPtr = &MemSymbol; newHandle->unloadFileProcPtr = &MemUnload; /* Keep the memfd file descriptor; it's closed on unload. */ ((int *)(newHandle + 1))[0] = shm_fd; *unloadProcPtr = &MemUnload; *loadHandle = newHandle; return TCL_OK; error: if (mbuf != MAP_FAILED) { munmap(mbuf, z->nbyte); } Unlock(); if (shm_fd >= 0) { close(shm_fd); } if (isshm && (shm_fd >= 0) && (path[0] != '\0')) { shm_unlink(path); } return TCL_ERROR; } #endif /* *------------------------------------------------------------------------- * * CountSlashes -- * * This function counts the number of slashes in a pathname string. * * Results: * Number of slashes found in string. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int CountSlashes(const char *string) { int count = 0; const char *p = string; while (*p != '\0') { if (*p == '/') { count++; } p++; } return count; } /* *------------------------------------------------------------------------- * * CanonicalPath -- * * This function computes the canonical path from a directory * and file name components into the specified Tcl_DString. * * Results: * Returns the pointer to the canonical path contained in the * specified Tcl_DString. * * Side effects: * Modifies the specified Tcl_DString. * *------------------------------------------------------------------------- */ static char * CanonicalPath(const char *root, const char *tail, Tcl_DString *dsPtr) { char *path; int i, j, c, isunc = 0; #if HAS_DRIVES if ((tail[0] != '\0') && (strchr(drvletters, tail[0]) != NULL) && (tail[1] == ':')) { tail += 2; } /* UNC style path */ if (tail[0] == '\\') { root = ""; ++tail; } if (tail[0] == '\\') { root = "/"; ++tail; } #endif /* UNC style path */ if ((root[0] == '/') && (root[1] == '/')) { isunc = 1; } if (tail[0] == '/') { root = ""; ++tail; isunc = 0; } if (tail[0] == '/') { root = "/"; ++tail; isunc = 1; } i = strlen(root); j = strlen(tail); Tcl_DStringSetLength(dsPtr, i + j + 1); path = Tcl_DStringValue(dsPtr); memcpy(path, root, i); path[i++] = '/'; memcpy(path + i, tail, j); #if HAS_DRIVES for (i = 0; path[i] != '\0'; i++) { if (path[i] == '\\') { path[i] = '/'; } } #endif for (i = j = 0; (c = path[i]) != '\0'; i++) { if (c == '/') { int c2 = path[i + 1]; if (c2 == '/') { continue; } if (c2 == '.') { int c3 = path[i + 2]; if ((c3 == '/') || (c3 == '\0')) { i++; continue; } if ((c3 == '.') && ((path[i + 3] == '/') || (path [i + 3] == '\0'))) { i += 2; while ((j > 0) && (path[j - 1] != '/')) { j--; } if (j > isunc) { --j; while ((j > 1 + isunc) && (path[j - 2] == '/')) { j--; } } continue; } } } path[j++] = c; } if (j == 0) { path[j++] = '/'; } path[j] = 0; Tcl_DStringSetLength(dsPtr, j); return Tcl_DStringValue(dsPtr); } /* *------------------------------------------------------------------------- * * AbsolutePath -- * * This function computes the absolute path from a given * (relative) path name into the specified Tcl_DString. * * Results: * Returns the pointer to the absolute path contained in the * specified Tcl_DString. * * Side effects: * Modifies the specified Tcl_DString. * *------------------------------------------------------------------------- */ static char * AbsolutePath(const char *path, #if HAS_DRIVES int *drvPtr, #endif Tcl_DString *dsPtr) { char *result; #if HAS_DRIVES if (drvPtr != NULL) { *drvPtr = 0; } #endif if (*path == '~') { Tcl_DStringAppend(dsPtr, path, -1); return Tcl_DStringValue(dsPtr); } if ((*path != '/') #if HAS_DRIVES && (*path != '\\') && (((*path != '\0') && (strchr(drvletters, *path) == NULL)) || (path[1] != ':')) #endif ) { Tcl_DString pwd; /* relative path */ Tcl_DStringInit(&pwd); Tcl_GetCwd(NULL, &pwd); result = Tcl_DStringValue(&pwd); #if HAS_DRIVES if ((result[0] != '\0') && (strchr(drvletters, result[0]) != NULL) && (result[1] == ':')) { if (drvPtr != NULL) { drvPtr[0] = result[0]; if ((drvPtr[0] >= 'a') && (drvPtr[0] <= 'z')) { drvPtr[0] -= 'a' - 'A'; } } result += 2; } #endif result = CanonicalPath(result, path, dsPtr); Tcl_DStringFree(&pwd); } else { /* absolute path */ #if HAS_DRIVES if ((path[0] != '\0') && (strchr(drvletters, path[0]) != NULL) && (path[1] == ':')) { if (drvPtr != NULL) { drvPtr[0] = path[0]; if ((drvPtr[0] >= 'a') && (drvPtr[0] <= 'z')) { drvPtr[0] -= 'a' - 'A'; } } } #endif result = CanonicalPath("", path, dsPtr); } return result; } /* *------------------------------------------------------------------------- * * ZipFSLookup -- * * This function returns the ZIP entry struct corresponding to * the ZIP archive member of the given file name. * * Results: * Returns the pointer to ZIP entry struct or NULL if the * the given file name could not be found in the global list * of ZIP archive members. * * Side effects: * None. * *------------------------------------------------------------------------- */ static ZipEntry * ZipFSLookup(char *filename) { char *realname; Tcl_HashEntry *hPtr; ZipEntry *z; Tcl_DString ds; #if HAS_DRIVES int drive = 0; #endif Tcl_DStringInit(&ds); #if HAS_DRIVES realname = AbsolutePath(filename, &drive, &ds); #else realname = AbsolutePath(filename, &ds); #endif hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, realname); z = hPtr ? (ZipEntry *) Tcl_GetHashValue(hPtr) : NULL; Tcl_DStringFree(&ds); #if HAS_DRIVES if ((z != NULL) && drive && (drive != z->zipfile->mntdrv)) { z = NULL; } #endif return z; } #ifdef NEVER_USED /* *------------------------------------------------------------------------- * * ZipFSLookupMount -- * * This function returns an indication if the given file name * corresponds to a mounted ZIP archive file. * * Results: * Returns true, if the given file name is a mounted ZIP archive file. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSLookupMount(char *filename) { char *realname; Tcl_HashEntry *hPtr; Tcl_HashSearch search; ZipFile *zf; Tcl_DString ds; int match = 0; #if HAS_DRIVES int drive = 0; #endif Tcl_DStringInit(&ds); #if HAS_DRIVES realname = AbsolutePath(filename, &drive, &ds); #else realname = AbsolutePath(filename, &ds); #endif hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); for (; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search);) { if ((zf = (ZipFile *) Tcl_GetHashValue(hPtr)) != NULL) { #if HAS_DRIVES if (drive && (drive != zf->mntdrv)) { continue; } #endif if (strcmp(zf->mntpt, realname) == 0) { match = 1; break; } } } Tcl_DStringFree(&ds); return match; } #endif /* *------------------------------------------------------------------------- * * ZipFSCloseArchive -- * * This function closes a mounted ZIP archive file. * * Results: * None. * * Side effects: * A memory mapped ZIP archive is unmapped, allocated memory is * released. * *------------------------------------------------------------------------- */ static void ZipFSCloseArchive(Tcl_Interp *interp, ZipFile *zf) { #if defined(_WIN32) || defined(_WIN64) if ((zf->data != NULL) && (zf->tofree == NULL)) { if (zf->ismapped) { UnmapViewOfFile(zf->data); } zf->data = NULL; } if (zf->mh != INVALID_HANDLE_VALUE) { CloseHandle(zf->mh); } #else if ((zf->data != MAP_FAILED) && (zf->tofree == NULL)) { if (zf->ismapped) { munmap(zf->data, zf->length); } zf->data = MAP_FAILED; } #endif if (zf->tofree != NULL) { Tcl_Free((char *) zf->tofree); zf->tofree = zf->data = NULL; } if (zf->chan != NULL) { Tcl_Close(interp, zf->chan); zf->chan = NULL; } } /* *------------------------------------------------------------------------- * * ZipFSOpenCommon, ZipFSOpenArchive, ZipFSOpenBuffer -- * * These functions open a ZIP archive file for reading. * ZipFSOpenArchive attemtps to memory map the archive file. * Otherwise it is read into an allocated memory buffer. * ZipFSOpenBuffer works on a user provided memory buffer, * which optionally is copied to a private buffer. * The common part verifies the ZIP archive header which * must be valid for the function to succeed. When "needZip" * is zero an embedded ZIP archive in an executable file is * accepted. * * Results: * TCL_OK on success, TCL_ERROR otherwise with an error message * placed into the given "interp" if it is not NULL. * * Side effects: * ZIP archive is memory mapped or read into allocated memory, * or taken from a user provided memory buffer; the given ZipFile * struct is filled with information about the ZIP archive file. * *------------------------------------------------------------------------- */ static int ZipFSOpenCommon(Tcl_Interp *interp, int needZip, ZipFile *zf) { int i; unsigned char *p, *q; p = zf->data + zf->length - ZIP_CENTRAL_END_LEN; while (p >= zf->data) { if (*p == (ZIP_CENTRAL_END_SIG & 0xFF)) { if (ZipReadInt(p) == ZIP_CENTRAL_END_SIG) { break; } p -= ZIP_SIG_LEN; } else { --p; } } if (p < zf->data) { if (!needZip) { zf->baseoffs = zf->baseoffsp = zf->length; return TCL_OK; } if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong end signature", -1)); } goto error; } zf->nfiles = ZipReadShort(p + ZIP_CENTRAL_ENTS_OFFS); if (zf->nfiles == 0) { if (!needZip) { zf->baseoffs = zf->baseoffsp = zf->length; return TCL_OK; } if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("empty archive", -1)); } goto error; } q = zf->data + ZipReadInt(p + ZIP_CENTRAL_DIRSTART_OFFS); p -= ZipReadInt(p + ZIP_CENTRAL_DIRSIZE_OFFS); if ((p < zf->data) || (p > (zf->data + zf->length)) || (q < zf->data) || (q > (zf->data + zf->length))) { if (!needZip) { zf->baseoffs = zf->baseoffsp = zf->length; return TCL_OK; } if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("archive directory not found", -1)); } goto error; } zf->baseoffs = zf->baseoffsp = p - q; zf->centoffs = p - zf->data; q = p; for (i = 0; i < zf->nfiles; i++) { int pathlen, comlen, extra; if ((q + ZIP_CENTRAL_HEADER_LEN) > (zf->data + zf->length)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong header length", -1)); } goto error; } if (ZipReadInt(q) != ZIP_CENTRAL_HEADER_SIG) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong header signature", -1)); } goto error; } pathlen = ZipReadShort(q + ZIP_CENTRAL_PATHLEN_OFFS); comlen = ZipReadShort(q + ZIP_CENTRAL_FCOMMENTLEN_OFFS); extra = ZipReadShort(q + ZIP_CENTRAL_EXTRALEN_OFFS); q += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN; } q = zf->data + zf->baseoffs; if ((zf->baseoffs >= 6) && (ZipReadInt(q - 4) == ZIP_PASSWORD_END_SIG)) { i = q[-5]; if (q - 5 - i > zf->data) { zf->pwbuf[0] = i; memcpy(zf->pwbuf + 1, q - 5 - i, i); zf->baseoffsp -= i ? (5 + i) : 0; } } return TCL_OK; error: ZipFSCloseArchive(interp, zf); return TCL_ERROR; } static int ZipFSOpenArchive(Tcl_Interp *interp, const char *zipname, int needZip, ZipFile *zf) { int i; ClientData handle; memset(zf, 0, sizeof(*zf)); #if defined(_WIN32) || defined(_WIN64) zf->data = NULL; zf->mh = INVALID_HANDLE_VALUE; #else zf->data = MAP_FAILED; #endif zf->length = 0; zf->ismapped = 0; zf->nfiles = 0; zf->baseoffs = zf->baseoffsp = 0; zf->tofree = NULL; zf->pwbuf[0] = 0; #ifdef ZIPFS_USE_ZEMBED /* * Mount from embedded memory buffer in special case. */ #if defined(MAC_OSX_TCL) if (strcmp(zipname, Tcl_GetNameOfExecutable()) == 0) { Dl_info dlinfo; unsigned char *data = NULL; unsigned long size = 0; memset(&dlinfo, 0, sizeof(dlinfo)); dladdr(Zipfs_Init, &dlinfo); data = getsectiondata(dlinfo.dli_fbase, "__TEXT", "__zipfs", &size); if ((data != NULL) && (size > 0)) { _binary_zembed_start = data; _binary_zembed_end = _binary_zembed_start + size; zf->chan = NULL; zf->data = _binary_zembed_start; zf->length = _binary_zembed_end - _binary_zembed_start; needZip = 1; goto gotZembed; } } #else if ((_binary_zembed_end - _binary_zembed_start > 0) && (strcmp(zipname, Tcl_GetNameOfExecutable()) == 0)) { zf->chan = NULL; zf->data = (unsigned char *) _binary_zembed_start; zf->length = _binary_zembed_end - _binary_zembed_start; needZip = 1; goto gotZembed; } #endif #endif zf->chan = Tcl_OpenFileChannel(interp, zipname, "r", 0); if (zf->chan == NULL) { return TCL_ERROR; } if (Tcl_GetChannelHandle(zf->chan, TCL_READABLE, &handle) != TCL_OK) { if (Tcl_SetChannelOption(interp, zf->chan, "-translation", "binary") != TCL_OK) { goto error; } if (Tcl_SetChannelOption(interp, zf->chan, "-encoding", "binary") != TCL_OK) { goto error; } zf->length = Tcl_Seek(zf->chan, 0, SEEK_END); if ((zf->length <= 0) || (zf->length > 64 * 1024 * 1024)) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("illegal file size", -1)); } goto error; } Tcl_Seek(zf->chan, 0, SEEK_SET); zf->tofree = zf->data = (unsigned char *) Tcl_AttemptAlloc(zf->length); if (zf->tofree == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); } goto error; } i = Tcl_Read(zf->chan, (char *) zf->data, zf->length); if (i != zf->length) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file read error", -1)); } goto error; } Tcl_Close(interp, zf->chan); zf->chan = NULL; } else { #if defined(_WIN32) || defined(_WIN64) zf->length = GetFileSize((HANDLE) handle, 0); if ((zf->length == INVALID_FILE_SIZE) || (zf->length < ZIP_CENTRAL_END_LEN)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid file size", -1)); } goto error; } zf->mh = CreateFileMapping((HANDLE) handle, 0, PAGE_READONLY, 0, zf->length, 0); if (zf->mh == INVALID_HANDLE_VALUE) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file mapping failed", -1)); } goto error; } zf->data = MapViewOfFile(zf->mh, FILE_MAP_READ, 0, 0, zf->length); if (zf->data == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file mapping failed", -1)); } goto error; } zf->ismapped = 1; #else zf->length = lseek((int) (long) handle, 0, SEEK_END); if ((zf->length == -1) || (zf->length < ZIP_CENTRAL_END_LEN)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid file size", -1)); } goto error; } lseek((int) (long) handle, 0, SEEK_SET); zf->data = (unsigned char *) mmap(0, zf->length, PROT_READ, MAP_FILE | MAP_PRIVATE, (int) (long) handle, 0); if (zf->data == MAP_FAILED) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file mapping failed", -1)); } goto error; } zf->ismapped = 1; #endif } #ifdef ZIPFS_USE_ZEMBED gotZembed: #endif return ZipFSOpenCommon(interp, needZip, zf); error: ZipFSCloseArchive(interp, zf); return TCL_ERROR; } static int ZipFSOpenBuffer(Tcl_Interp *interp, unsigned char *data, int length, int copy, ZipFile *zf) { memset(zf, 0, sizeof(*zf)); #if defined(_WIN32) || defined(_WIN64) zf->mh = INVALID_HANDLE_VALUE; #endif if (copy) { zf->data = (unsigned char *) Tcl_AttemptAlloc(length); if (zf->data == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); return TCL_ERROR; } memcpy(zf->data, data, length); zf->tofree = zf->data; } else { zf->data = data; zf->tofree = NULL; } zf->length = length; zf->ismapped = 0; zf->nfiles = 0; zf->baseoffs = zf->baseoffsp = 0; zf->pwbuf[0] = 0; zf->chan = NULL; return ZipFSOpenCommon(interp, 1, zf); } /* *------------------------------------------------------------------------- * * Zipfs_ListMountInfo -- * * Write information about mounted ZIP file systems to * interp's result. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Zipfs_ListMountInfo(Tcl_Interp *interp) { int i, ret = TCL_OK; ZipFile *zf; Tcl_HashEntry *hPtr; Tcl_HashSearch search; i = 0; hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); while (hPtr != NULL) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (zf != NULL) { if (interp != NULL) { #if HAS_DRIVES Tcl_DString ds; char drvbuf[3]; Tcl_DStringInit(&ds); drvbuf[0] = zf->mntdrv; drvbuf[1] = ':'; drvbuf[2] = '\0'; Tcl_DStringAppend(&ds, drvbuf, -1); Tcl_DStringAppend(&ds, zf->mntpt, zf->mntptlen); Tcl_AppendElement(interp, Tcl_DStringValue(&ds)); Tcl_DStringFree(&ds); #else Tcl_AppendElement(interp, zf->mntpt); #endif Tcl_AppendElement(interp, zf->name); } ++i; } hPtr = Tcl_NextHashEntry(&search); } if (interp == NULL) { ret = (i > 0) ? TCL_OK : TCL_BREAK; } return ret; } /* *------------------------------------------------------------------------- * * Zipfs_MountCommon -- * * Common part of mounting a ZIP archive file. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is read, analyzed and mounted, resources are * allocated. * *------------------------------------------------------------------------- */ static int Zipfs_MountCommon(Tcl_Interp *interp, const char *zipname, const char *mntpt, int isBuffer, ZipFile *zf0) { char *realname; int i, isNew; ZipFile *zf; ZipEntry *z; Tcl_HashEntry *hPtr; Tcl_DString ds, dsm, fpBuf; unsigned char *q; #if HAS_DRIVES char *p; int drive = 0; #endif Tcl_DStringInit(&ds); if (isBuffer) { Tcl_DStringAppend(&ds, zipname, -1); realname = Tcl_DStringValue(&ds); } else { #if HAS_DRIVES realname = AbsolutePath(zipname, NULL, &ds); #else realname = AbsolutePath(zipname, &ds); #endif } /* * Mount point can come from Tcl_GetNameOfExecutable() * which sometimes is a relative or otherwise denormalized path. * But an absolute name is needed as mount point here. */ Tcl_DStringInit(&dsm); #if HAS_DRIVES mntpt = AbsolutePath(mntpt, &drive, &dsm); if (drive == 0) { /* * Enforce a drive letter by using [pwd] information. */ Tcl_DString pwd; Tcl_DStringInit(&pwd); Tcl_GetCwd(NULL, &pwd); if (Tcl_DStringValue(&pwd)[1] == ':') { drive = Tcl_DStringValue(&pwd)[0]; if ((drive >= 'a') && (drive <= 'z')) { drive -= 'a' - 'A'; } } Tcl_DStringFree(&pwd); } #else mntpt = AbsolutePath(mntpt, &dsm); #endif WriteLock(); hPtr = Tcl_CreateHashEntry(&ZipFS.zipHash, realname, &isNew); Tcl_DStringSetLength(&ds, 0); if (!isNew) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (interp != NULL) { #if HAS_DRIVES char drvbuf[3]; drvbuf[0] = zf->mntdrv; drvbuf[1] = ':'; drvbuf[2] = '\0'; Tcl_AppendResult(interp, "already mounted on \"", drvbuf, zf->mntptlen ? zf->mntpt : "/", "\"", (char *) NULL); #else Tcl_AppendResult(interp, "already mounted on \"", zf->mntptlen ? zf->mntpt : "/", "\"", (char *) NULL); #endif } Unlock(); Tcl_DStringFree(&ds); Tcl_DStringFree(&dsm); ZipFSCloseArchive(interp, zf0); return TCL_ERROR; } if (strcmp(mntpt, "/") == 0) { mntpt = ""; } zf = (ZipFile *) Tcl_AttemptAlloc(sizeof(ZipFile) + strlen(mntpt) + 1); if (zf == NULL) { if (interp != NULL) { Tcl_AppendResult(interp, "out of memory", (char *) NULL); } Unlock(); Tcl_DStringFree(&ds); Tcl_DStringFree(&dsm); ZipFSCloseArchive(interp, zf0); return TCL_ERROR; } *zf = *zf0; zf->name = Tcl_GetHashKey(&ZipFS.zipHash, hPtr); strcpy(zf->mntpt, mntpt); zf->mntptlen = strlen(zf->mntpt); #if HAS_DRIVES for (p = zf->mntpt; *p != '\0'; p++) { if (*p == '\\') { *p = '/'; } } zf->mntdrv = drive; #endif zf->entries = NULL; zf->topents = NULL; zf->nopen = 0; Tcl_SetHashValue(hPtr, (ClientData) zf); if (mntpt[0] != '\0') { z = (ZipEntry *) Tcl_Alloc(sizeof(ZipDirEntry)); z->name = NULL; z->tnext = NULL; z->parent = NULL; z->depth = CountSlashes(mntpt); z->zipfile = zf; z->isdir = (zf->baseoffs == 0) ? 1 : -1; /* root marker */ z->isenc = 0; z->offset = zf->baseoffs; z->crc32 = 0; z->timestamp = 0; z->nbyte = z->nbytecompr = 0; z->cmeth = ZIP_COMPMETH_STORED; z->data = NULL; hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, mntpt, &isNew); if (!isNew) { /* * Skip it, but increment number of open files in * referenced file system. */ Tcl_Free((char *) z); z = (ZipEntry *) Tcl_GetHashValue(hPtr); z->zipfile->nopen++; } else { Tcl_SetHashValue(hPtr, (ClientData) z); z->name = Tcl_GetHashKey(&ZipFS.fileHash, hPtr); z->next = zf->entries; zf->entries = z; hPtr = Tcl_CreateHashEntry(&ZipFS.dirHash, (ClientData) z, &isNew); Tcl_SetHashValue(hPtr, (ClientData) z); Tcl_InitHashTable(&((ZipDirEntry *) z)->children, TCL_ONE_WORD_KEYS); } } q = zf->data + zf->centoffs; Tcl_DStringInit(&fpBuf); for (i = 0; i < zf->nfiles; i++) { int pathlen, comlen, extra, isdir = 0, dosTime, dosDate, nbcompr, offs; unsigned char *lq, *gq = NULL; char *fullpath, *path; pathlen = ZipReadShort(q + ZIP_CENTRAL_PATHLEN_OFFS); comlen = ZipReadShort(q + ZIP_CENTRAL_FCOMMENTLEN_OFFS); extra = ZipReadShort(q + ZIP_CENTRAL_EXTRALEN_OFFS); Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, (char *) q + ZIP_CENTRAL_HEADER_LEN, pathlen); path = Tcl_DStringValue(&ds); if ((pathlen > 0) && (path[pathlen - 1] == '/')) { Tcl_DStringSetLength(&ds, pathlen - 1); path = Tcl_DStringValue(&ds); isdir = 1; } if ((strcmp(path, ".") == 0) || (strcmp(path, "..") == 0)) { goto nextent; } lq = zf->data + zf->baseoffs + ZipReadInt(q + ZIP_CENTRAL_LOCALHDR_OFFS); if ((lq < zf->data) || (lq > (zf->data + zf->length))) { goto nextent; } nbcompr = ZipReadInt(lq + ZIP_LOCAL_COMPLEN_OFFS); if (!isdir && (nbcompr == 0) && (ZipReadInt(lq + ZIP_LOCAL_UNCOMPLEN_OFFS) == 0) && (ZipReadInt(lq + ZIP_LOCAL_CRC32_OFFS) == 0)) { gq = q; nbcompr = ZipReadInt(gq + ZIP_CENTRAL_COMPLEN_OFFS); } offs = (lq - zf->data) + ZIP_LOCAL_HEADER_LEN + ZipReadShort(lq + ZIP_LOCAL_PATHLEN_OFFS) + ZipReadShort(lq + ZIP_LOCAL_EXTRALEN_OFFS); if ((offs + nbcompr) > zf->length) { goto nextent; } if (!isdir && (mntpt[0] == '\0') && !CountSlashes(path)) { #ifdef ANDROID /* * When mounting the ZIP archive on the root directory try * to remap top level regular files of the archive to * /assets/.root/... since this directory should not be * in a valid APK due to the leading dot in the file name * component. This trick should make the files * AndroidManifest.xml, resources.arsc, and classes.dex * visible to Tcl. */ Tcl_DString ds2; Tcl_DStringInit(&ds2); Tcl_DStringAppend(&ds2, "assets/.root/", -1); Tcl_DStringAppend(&ds2, path, -1); hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, Tcl_DStringValue(&ds2)); if (hPtr != NULL) { /* should not happen but skip it anyway */ Tcl_DStringFree(&ds2); goto nextent; } Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, Tcl_DStringValue(&ds2), Tcl_DStringLength(&ds2)); path = Tcl_DStringValue(&ds); Tcl_DStringFree(&ds2); #else /* * Regular files skipped when mounting on root. */ goto nextent; #endif } Tcl_DStringSetLength(&fpBuf, 0); fullpath = CanonicalPath(mntpt, path, &fpBuf); z = (ZipEntry *) Tcl_Alloc(isdir ? sizeof(ZipDirEntry) : sizeof(ZipEntry)); z->name = NULL; z->tnext = NULL; z->parent = NULL; z->depth = CountSlashes(fullpath); z->zipfile = zf; z->isdir = isdir; z->isenc = (ZipReadShort(lq + ZIP_LOCAL_FLAGS_OFFS) & 1) && (nbcompr > 12); z->offset = offs; if (gq != NULL) { z->crc32 = ZipReadInt(gq + ZIP_CENTRAL_CRC32_OFFS); dosDate = ZipReadShort(gq + ZIP_CENTRAL_MDATE_OFFS); dosTime = ZipReadShort(gq + ZIP_CENTRAL_MTIME_OFFS); z->timestamp = DosTimeDate(dosDate, dosTime); z->nbyte = ZipReadInt(gq + ZIP_CENTRAL_UNCOMPLEN_OFFS); z->cmeth = ZipReadShort(gq + ZIP_CENTRAL_COMPMETH_OFFS); } else { z->crc32 = ZipReadInt(lq + ZIP_LOCAL_CRC32_OFFS); dosDate = ZipReadShort(lq + ZIP_LOCAL_MDATE_OFFS); dosTime = ZipReadShort(lq + ZIP_LOCAL_MTIME_OFFS); z->timestamp = DosTimeDate(dosDate, dosTime); z->nbyte = ZipReadInt(lq + ZIP_LOCAL_UNCOMPLEN_OFFS); z->cmeth = ZipReadShort(lq + ZIP_LOCAL_COMPMETH_OFFS); } z->nbytecompr = nbcompr; z->data = NULL; hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, fullpath, &isNew); if (!isNew) { /* should not happen but skip it anyway */ Tcl_Free((char *) z); } else { Tcl_SetHashValue(hPtr, (ClientData) z); z->name = Tcl_GetHashKey(&ZipFS.fileHash, hPtr); z->next = zf->entries; zf->entries = z; if (z->isdir) { hPtr = Tcl_CreateHashEntry(&ZipFS.dirHash, (ClientData) z, &isNew); Tcl_SetHashValue(hPtr, (ClientData) z); Tcl_InitHashTable(&((ZipDirEntry *) z)->children, TCL_ONE_WORD_KEYS); } if (z->isdir && (mntpt[0] == '\0') && (z->depth == 1)) { z->tnext = zf->topents; zf->topents = z; } if (z->depth > 1) { char *dir, *end; ZipDirEntry *zd; ZipEntry *addChild = z; Tcl_DStringSetLength(&ds, strlen(z->name) + 8); Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, z->name, -1); dir = Tcl_DStringValue(&ds); end = strrchr(dir, '/'); while ((end != NULL) && (end != dir)) { Tcl_DStringSetLength(&ds, end - dir); hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, dir); if (hPtr != NULL) { zd = (ZipDirEntry *) Tcl_GetHashValue(hPtr); hPtr = Tcl_CreateHashEntry(&zd->children, (ClientData) addChild, &isNew); if (isNew) { addChild->parent = zd; Tcl_SetHashValue(hPtr, (ClientData) addChild); } break; } zd = (ZipDirEntry *) Tcl_Alloc(sizeof(ZipDirEntry)); zd->entry.name = NULL; zd->entry.tnext = NULL; zd->entry.parent = NULL; zd->entry.depth = CountSlashes(dir); zd->entry.zipfile = zf; zd->entry.isdir = 1; zd->entry.isenc = 0; zd->entry.offset = z->offset; zd->entry.crc32 = 0; zd->entry.timestamp = z->timestamp; zd->entry.nbyte = zd->entry.nbytecompr = 0; zd->entry.cmeth = ZIP_COMPMETH_STORED; zd->entry.data = NULL; hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, dir, &isNew); if (!isNew) { /* should not happen but skip it anyway */ Tcl_Free((char *) zd); zd = (ZipDirEntry *) Tcl_GetHashValue(hPtr); } else { Tcl_SetHashValue(hPtr, (ClientData) zd); zd->entry.name = Tcl_GetHashKey(&ZipFS.fileHash, hPtr); zd->entry.next = zf->entries; zf->entries = &zd->entry; hPtr = Tcl_CreateHashEntry(&ZipFS.dirHash, (ClientData) zd, &isNew); Tcl_SetHashValue(hPtr, (ClientData) zd); Tcl_InitHashTable(&zd->children, TCL_ONE_WORD_KEYS); } hPtr = Tcl_CreateHashEntry(&zd->children, (ClientData) addChild, &isNew); if (isNew) { addChild->parent = zd; Tcl_SetHashValue(hPtr, (ClientData) addChild); } if ((mntpt[0] == '\0') && (zd->entry.depth == 1)) { zd->entry.tnext = zf->topents; zf->topents = &zd->entry; } addChild = &zd->entry; end = strrchr(dir, '/'); } } } nextent: q += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN; } Unlock(); Tcl_DStringFree(&fpBuf); Tcl_DStringFree(&ds); Tcl_DStringFree(&dsm); Tcl_FSMountsChanged(NULL); return TCL_OK; } /* *------------------------------------------------------------------------- * * Zipfs_Mount -- * * This procedure is invoked to mount a given ZIP archive file on * a given mountpoint with optional ZIP password. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is read, analyzed and mounted, resources are * allocated. * *------------------------------------------------------------------------- */ int Zipfs_Mount(Tcl_Interp *interp, const char *zipname, const char *mntpt, const char *passwd) { int i, pwlen, ret; ZipFile *zf, zf0; Tcl_HashEntry *hPtr; #if HAS_DRIVES int drive = 0; #endif ReadLock(); if (!ZipFS.initialized) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("not initialized", -1)); } Unlock(); return TCL_ERROR; } if (zipname == NULL) { ret = Zipfs_ListMountInfo(interp); Unlock(); return ret; } if (mntpt == NULL) { char *p; Tcl_DString ds; if (interp == NULL) { Unlock(); return TCL_OK; } Tcl_DStringInit(&ds); #if HAS_DRIVES p = AbsolutePath(zipname, &drive, &ds); #else p = AbsolutePath(zipname, &ds); #endif hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, p); if (hPtr == NULL) { hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, zipname); } if (hPtr != NULL) { zf = Tcl_GetHashValue(hPtr); if (zf != NULL) { #if HAS_DRIVES if (drive == zf->mntdrv) { Tcl_Obj *string; char drvbuf[3]; drvbuf[0] = zf->mntdrv; drvbuf[1] = ':'; drvbuf[2] = '\0'; string = Tcl_NewStringObj(drvbuf, -1); Tcl_AppendToObj(string, zf->mntpt, zf->mntptlen); Tcl_SetObjResult(interp, string); } #else Tcl_SetObjResult(interp, Tcl_NewStringObj(zf->mntpt, zf->mntptlen)); #endif } } Unlock(); Tcl_DStringFree(&ds); return TCL_OK; } Unlock(); pwlen = 0; if (passwd != NULL) { pwlen = strlen(passwd); if ((pwlen > 255) || (strchr(passwd, 0xff) != NULL)) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("illegal password", -1)); } return TCL_ERROR; } } if (ZipFSOpenArchive(interp, zipname, 1, &zf0) != TCL_OK) { return TCL_ERROR; } if ((zf0.pwbuf[0] == 0) && pwlen) { int k = 0; i = pwlen; zf0.pwbuf[k++] = i; while (i > 0) { zf0.pwbuf[k] = (passwd[i - 1] & 0x0f) | pwrot[(passwd[i - 1] >> 4) & 0x0f]; k++; i--; } zf0.pwbuf[k] = '\0'; } ret = Zipfs_MountCommon(interp, zipname, mntpt, 0, &zf0); if (zf0.pwbuf[0]) { memset(&zf0, 0, sizeof(zf0)); } return ret; } /* *------------------------------------------------------------------------- * * Zipfs_MountBuffer -- * * This procedure is invoked to mount a given ZIP archive contained * in a memory buffer on a given mountpoint. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive from memory is analyzed and mounted, resources are * allocated. * *------------------------------------------------------------------------- */ int Zipfs_MountBuffer(Tcl_Interp *interp, const char *mntpt, unsigned char *data, size_t length, int copy) { int ret; ZipFile zf0; char namebuf[64]; ReadLock(); if (!ZipFS.initialized) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("not initialized", -1)); } Unlock(); return TCL_ERROR; } if (mntpt == NULL) { ret = Zipfs_ListMountInfo(interp); Unlock(); return ret; } if ((data == NULL) || (length == 0)) { Unlock(); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("empty data provided", -1)); } return TCL_ERROR; } Unlock(); if (ZipFSOpenBuffer(interp, data, length, 0, &zf0) != TCL_OK) { return TCL_ERROR; } sprintf(namebuf, "memory_%ld_%d", (long) length, ZipFS.idCount++); ret = Zipfs_MountCommon(interp, namebuf, mntpt, 1, &zf0); if (ret == TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewStringObj(namebuf, -1)); } return ret; } /* *------------------------------------------------------------------------- * * Zipfs_Unmount -- * * This procedure is invoked to unmount a given ZIP archive. * * Results: * A standard Tcl result. * * Side effects: * A mounted ZIP archive file is unmounted, resources are free'd. * *------------------------------------------------------------------------- */ int Zipfs_Unmount(Tcl_Interp *interp, const char *zipname) { char *realname; ZipFile *zf; ZipEntry *z, *znext; ZipDirEntry *zd, *zdirs = NULL; Tcl_HashEntry *hPtr; Tcl_DString ds; int ret = TCL_OK, unmounted = 0; #if HAS_DRIVES int drive = 0; #endif Tcl_DStringInit(&ds); #if HAS_DRIVES realname = AbsolutePath(zipname, &drive, &ds); #else realname = AbsolutePath(zipname, &ds); #endif WriteLock(); if (!ZipFS.initialized) { goto done; } hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, realname); if (hPtr == NULL) { #if HASH_DRIVES drive = 0; #endif hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, zipname); if (hPtr == NULL) { /* don't report error */ goto done; } } zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (zf->nopen > 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("filesystem is busy", -1)); } ret = TCL_ERROR; goto done; } Tcl_DeleteHashEntry(hPtr); for (z = zf->entries; z; z = znext) { znext = z->next; hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, z->name); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } hPtr = Tcl_FindHashEntry(&ZipFS.dirHash, (ClientData) z); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } if (z->parent != NULL) { hPtr = Tcl_FindHashEntry(&z->parent->children, (ClientData) z); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } if (z->data != NULL) { Tcl_Free((char *) z->data); } if (z->isdir) { z->next = (ZipEntry *) zdirs; zdirs = (ZipDirEntry *) z; } else { Tcl_Free((char *) z); } } while (zdirs != NULL) { zd = zdirs; zdirs = (ZipDirEntry *) zd->entry.next; Tcl_DeleteHashTable(&zd->children); Tcl_Free((char *) zd); } hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, zf->mntpt); if (hPtr != NULL) { z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (z->zipfile != zf) { z->zipfile->nopen--; } } ZipFSCloseArchive(interp, zf); Tcl_Free((char *) zf); unmounted = 1; done: Unlock(); Tcl_DStringFree(&ds); if (unmounted) { Tcl_FSMountsChanged(NULL); } return ret; } /* *------------------------------------------------------------------------- * * ZipFSMountObjCmd -- * * This procedure is invoked to process the "zipfs::mount" command. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is mounted, resources are allocated. * *------------------------------------------------------------------------- */ static int ZipFSMountObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int optLen; const char *option; if (objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "?-option ?arg? ?mountpoint? ?password?"); return TCL_ERROR; } if (objc > 3) { option = Tcl_GetString(objv[1]); optLen = strlen(option); if ((optLen > 1) && strncmp(option, "-data", 5) == 0) { unsigned char *data; int length; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "-data bytearray mountpoint"); return TCL_ERROR; } data = Tcl_GetByteArrayFromObj(objv[2], &length); return Zipfs_MountBuffer(interp, Tcl_GetString(objv[3]), data, length, 1); } else if ((optLen > 1) && strncmp(option, "-chan", 5) == 0) { unsigned char *data; int length, ret; Tcl_Channel chan; Tcl_Obj *dataObj; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "-chan channelId mountpoint"); return TCL_ERROR; } chan = Tcl_GetChannel(interp, Tcl_GetString(objv[2]), NULL); if (chan == NULL) { return TCL_ERROR; } dataObj = Tcl_NewObj(); Tcl_IncrRefCount(dataObj); if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { Tcl_DecrRefCount(dataObj); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "read error: %s", Tcl_PosixError(interp))); return TCL_ERROR; } data = Tcl_GetByteArrayFromObj(dataObj, &length); ret = Zipfs_MountBuffer(interp, Tcl_GetString(objv[3]), data, length, 1); Tcl_DecrRefCount(dataObj); return ret; } else if ((optLen > 1) && ((strncmp(option, "-file", 5) == 0) || (strcmp(option, "--") == 0))) { return Zipfs_Mount(interp, Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), (objc > 4) ? Tcl_GetString(objv[4]) : NULL); } } else if (objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?zipfile? ?mountpoint? ?password?"); return TCL_ERROR; } return Zipfs_Mount(interp, (objc > 1) ? Tcl_GetString(objv[1]) : NULL, (objc > 2) ? Tcl_GetString(objv[2]) : NULL, (objc > 3) ? Tcl_GetString(objv[3]) : NULL); } /* *------------------------------------------------------------------------- * * ZipFSUnmountObjCmd -- * * This procedure is invoked to process the "zipfs::unmount" command. * * Results: * A standard Tcl result. * * Side effects: * A mounted ZIP archive file is unmounted, resources are free'd. * *------------------------------------------------------------------------- */ static int ZipFSUnmountObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "zipfile"); return TCL_ERROR; } return Zipfs_Unmount(interp, Tcl_GetString(objv[1])); } /* *------------------------------------------------------------------------- * * ZipFSMkKeyObjCmd -- * * This procedure is invoked to process the "zipfs::mkkey" command. * It produces a rotated password to be embedded into an image file. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSMkKeyObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int len, i = 0; char *pw, pwbuf[264]; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "password"); return TCL_ERROR; } pw = Tcl_GetString(objv[1]); len = strlen(pw); if (len == 0) { return TCL_OK; } if ((len > 255) || (strchr(pw, 0xff) != NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("illegal password", -1)); return TCL_ERROR; } while (len > 0) { int ch = pw[len - 1]; pwbuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; i++; len--; } pwbuf[i] = i; ++i; pwbuf[i++] = (char) ZIP_PASSWORD_END_SIG; pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 8); pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 16); pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 24); pwbuf[i] = '\0'; Tcl_AppendResult(interp, pwbuf, (char *) NULL); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipAddFile -- * * This procedure is used by ZipFSMkZipOrImgCmd() to add a single * file to the output ZIP archive file being written. A ZipEntry * struct about the input file is added to the given fileHash table * for later creation of the central ZIP directory. * * Results: * A standard Tcl result. * * Side effects: * Input file is read and (compressed and) written to the output * ZIP archive file. * *------------------------------------------------------------------------- */ static int ZipAddFile(Tcl_Interp *interp, const char *path, const char *name, Tcl_Channel out, const char *passwd, char *buf, int bufsize, Tcl_HashTable *fileHash) { Tcl_Channel in; Tcl_HashEntry *hPtr; ZipEntry *z; z_stream stream; const char *zpath; int nbyte, nbytecompr, len, crc, flush, pos[3], zpathlen, olen; int mtime = 0, isNew, align = 0, cmeth; unsigned long keys[3], keys0[3]; char obuf[4096]; zpath = name; while (zpath != NULL && zpath[0] == '/') { zpath++; } if ((zpath == NULL) || (zpath[0] == '\0')) { return TCL_OK; } zpathlen = strlen(zpath); if (zpathlen + ZIP_CENTRAL_HEADER_LEN > bufsize) { Tcl_AppendResult(interp, "path too long for \"", path, "\"", (char *) NULL); return TCL_ERROR; } in = Tcl_OpenFileChannel(interp, path, "r", 0); if ((in == NULL) || (Tcl_SetChannelOption(interp, in, "-translation", "binary") != TCL_OK) || (Tcl_SetChannelOption(interp, in, "-encoding", "binary") != TCL_OK)) { #if defined(_WIN32) || defined(_WIN64) /* hopefully a directory */ if (strcmp("permission denied", Tcl_PosixError(interp)) == 0) { Tcl_Close(interp, in); return TCL_OK; } #endif Tcl_Close(interp, in); return TCL_ERROR; } else { Tcl_Obj *pathObj = Tcl_NewStringObj(path, -1); Tcl_StatBuf statBuf; Tcl_IncrRefCount(pathObj); if (Tcl_FSStat(pathObj, &statBuf) != -1) { mtime = statBuf.st_mtime; } Tcl_DecrRefCount(pathObj); } Tcl_ResetResult(interp); crc = 0; nbyte = nbytecompr = 0; while ((len = Tcl_Read(in, buf, bufsize)) > 0) { crc = crc32(crc, (unsigned char *) buf, len); nbyte += len; } if (len == -1) { if (nbyte == 0) { if (strcmp("illegal operation on a directory", Tcl_PosixError(interp)) == 0) { Tcl_Close(interp, in); return TCL_OK; } } Tcl_AppendResult(interp, "read error on \"", path, "\"", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } if (Tcl_Seek(in, 0, SEEK_SET) == -1) { Tcl_AppendResult(interp, "seek error on \"", path, "\"", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } pos[0] = Tcl_Tell(out); memset(buf, '\0', ZIP_LOCAL_HEADER_LEN); memcpy(buf + ZIP_LOCAL_HEADER_LEN, zpath, zpathlen); len = zpathlen + ZIP_LOCAL_HEADER_LEN; if (Tcl_Write(out, buf, len) != len) { wrerr: Tcl_AppendResult(interp, "write error", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } if ((len + pos[0]) & 3) { char abuf[8]; /* * Align payload to next 4-byte boundary using a dummy extra * entry similar to the zipalign tool from Android's SDK. */ align = 0xffff; ZipWriteShort(abuf, align); align = 4 + ((len + pos[0]) & 3); ZipWriteShort(abuf + 2, align - 4); ZipWriteInt(abuf + 4, 0x03020100); if (Tcl_Write(out, abuf, align) != align) { goto wrerr; } } if (passwd != NULL) { int i, ch, tmp; unsigned char kvbuf[24]; Tcl_Obj *ret; init_keys(passwd, keys, crc32tab); for (i = 0; i < 12 - 2; i++) { if (Tcl_EvalEx(interp, "expr {int(rand() * 256) % 256}", -1, 0) != TCL_OK) { Tcl_AppendResult(interp, "PRNG error", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } ret = Tcl_GetObjResult(interp); if (Tcl_GetIntFromObj(interp, ret, &ch) != TCL_OK) { Tcl_Close(interp, in); return TCL_ERROR; } kvbuf[i + 12] = (unsigned char) zencode(keys, crc32tab, ch & 0xff, tmp); } Tcl_ResetResult(interp); init_keys(passwd, keys, crc32tab); for (i = 0; i < 12 - 2; i++) { kvbuf[i] = (unsigned char) zencode(keys, crc32tab, kvbuf[i + 12], tmp); } kvbuf[i++] = (unsigned char) zencode(keys, crc32tab, (crc >> 16) & 0xff, tmp); kvbuf[i++] = (unsigned char) zencode(keys, crc32tab, (crc >> 24) & 0xff, tmp); len = Tcl_Write(out, (char *) kvbuf, 12); memset(kvbuf, 0, 24); if (len != 12) { Tcl_AppendResult(interp, "write error", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } memcpy(keys0, keys, sizeof(keys0)); nbytecompr += 12; } Tcl_Flush(out); pos[2] = Tcl_Tell(out); cmeth = ZIP_COMPMETH_DEFLATED; memset(&stream, 0, sizeof(stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; if (deflateInit2(&stream, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) { Tcl_AppendResult(interp, "compression init error on \"", path, "\"", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } do { len = Tcl_Read(in, buf, bufsize); if (len == -1) { Tcl_AppendResult(interp, "read error on \"", path, "\"", (char *) NULL); deflateEnd(&stream); Tcl_Close(interp, in); return TCL_ERROR; } stream.avail_in = len; stream.next_in = (unsigned char *) buf; flush = Tcl_Eof(in) ? Z_FINISH : Z_NO_FLUSH; do { stream.avail_out = sizeof(obuf); stream.next_out = (unsigned char *) obuf; len = deflate(&stream, flush); if (len == Z_STREAM_ERROR) { Tcl_AppendResult(interp, "deflate error on \"", path, "\"", (char *) NULL); deflateEnd(&stream); Tcl_Close(interp, in); return TCL_ERROR; } olen = sizeof(obuf) - stream.avail_out; if (passwd != NULL) { int i, tmp; for (i = 0; i < olen; i++) { obuf[i] = (char) zencode(keys, crc32tab, obuf[i] & 0xff, tmp); } } if (olen && (Tcl_Write(out, obuf, olen) != olen)) { Tcl_AppendResult(interp, "write error", (char *) NULL); deflateEnd(&stream); Tcl_Close(interp, in); return TCL_ERROR; } nbytecompr += olen; if (len == Z_STREAM_END) { flush = Z_FINISH; break; } } while (stream.avail_out == 0); } while (flush != Z_FINISH); deflateEnd(&stream); Tcl_Flush(out); pos[1] = Tcl_Tell(out); if (nbyte - nbytecompr <= 0) { /* * Compressed file larger than input, * write it again uncompressed. */ if ((int) Tcl_Seek(in, 0, SEEK_SET) != 0) { goto seekErr; } if ((int) Tcl_Seek(out, pos[2], SEEK_SET) != pos[2]) { seekErr: Tcl_Close(interp, in); Tcl_AppendResult(interp, "seek error", (char *) NULL); return TCL_ERROR; } nbytecompr = (passwd != NULL) ? 12 : 0; while (1) { len = Tcl_Read(in, buf, bufsize); if (len == -1) { Tcl_AppendResult(interp, "read error on \"", path, "\"", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } else if (len == 0) { break; } if (passwd != NULL) { int i, tmp; for (i = 0; i < len; i++) { buf[i] = (char) zencode(keys0, crc32tab, buf[i] & 0xff, tmp); } } if (Tcl_Write(out, buf, len) != len) { Tcl_AppendResult(interp, "write error", (char *) NULL); Tcl_Close(interp, in); return TCL_ERROR; } nbytecompr += len; } cmeth = ZIP_COMPMETH_STORED; Tcl_Flush(out); pos[1] = Tcl_Tell(out); Tcl_TruncateChannel(out, pos[1]); } Tcl_Close(interp, in); z = (ZipEntry *) Tcl_Alloc(sizeof(ZipEntry)); z->name = NULL; z->tnext = NULL; z->parent = NULL; z->depth = 0; z->zipfile = NULL; z->isdir = 0; z->isenc = (passwd != NULL) ? 1 : 0; z->offset = pos[0]; z->crc32 = crc; z->timestamp = mtime; z->nbyte = nbyte; z->nbytecompr = nbytecompr; z->cmeth = cmeth; z->data = NULL; hPtr = Tcl_CreateHashEntry(fileHash, zpath, &isNew); if (!isNew) { Tcl_AppendResult(interp, "non-unique path name \"", path, "\"", (char *) NULL); Tcl_Free((char *) z); return TCL_ERROR; } else { Tcl_SetHashValue(hPtr, (ClientData) z); z->name = Tcl_GetHashKey(fileHash, hPtr); z->next = NULL; } /* * Write final local header information. */ ZipWriteInt(buf + ZIP_LOCAL_SIG_OFFS, ZIP_LOCAL_HEADER_SIG); ZipWriteShort(buf + ZIP_LOCAL_VERSION_OFFS, ZIP_MIN_VERSION); ZipWriteShort(buf + ZIP_LOCAL_FLAGS_OFFS, z->isenc); ZipWriteShort(buf + ZIP_LOCAL_COMPMETH_OFFS, z->cmeth); ZipWriteShort(buf + ZIP_LOCAL_MTIME_OFFS, ToDosTime(z->timestamp)); ZipWriteShort(buf + ZIP_LOCAL_MDATE_OFFS, ToDosDate(z->timestamp)); ZipWriteInt(buf + ZIP_LOCAL_CRC32_OFFS, z->crc32); ZipWriteInt(buf + ZIP_LOCAL_COMPLEN_OFFS, z->nbytecompr); ZipWriteInt(buf + ZIP_LOCAL_UNCOMPLEN_OFFS, z->nbyte); ZipWriteShort(buf + ZIP_LOCAL_PATHLEN_OFFS, zpathlen); ZipWriteShort(buf + ZIP_LOCAL_EXTRALEN_OFFS, align); if ((int) Tcl_Seek(out, pos[0], SEEK_SET) != pos[0]) { Tcl_DeleteHashEntry(hPtr); Tcl_Free((char *) z); Tcl_AppendResult(interp, "seek error", (char *) NULL); return TCL_ERROR; } if (Tcl_Write(out, buf, ZIP_LOCAL_HEADER_LEN) != ZIP_LOCAL_HEADER_LEN) { Tcl_DeleteHashEntry(hPtr); Tcl_Free((char *) z); Tcl_AppendResult(interp, "write error", (char *) NULL); return TCL_ERROR; } Tcl_Flush(out); if ((int) Tcl_Seek(out, pos[1], SEEK_SET) != pos[1]) { Tcl_DeleteHashEntry(hPtr); Tcl_Free((char *) z); Tcl_AppendResult(interp, "seek error", (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSMkZipOrImgObjCmd -- * * This procedure is creates a new ZIP archive file or image file * given output filename, input directory of files to be archived, * optional password, and optional image to be prepended to the * output ZIP archive file. * * Results: * A standard Tcl result. * * Side effects: * A new ZIP archive file or image file is written. * *------------------------------------------------------------------------- */ static int ZipFSMkZipOrImgObjCmd(ClientData clientData, Tcl_Interp *interp, int isImg, int isList, int objc, Tcl_Obj *const objv[]) { Tcl_Channel out; int len = 0, pwlen = 0, slen = 0, i, count, ret = TCL_ERROR, lobjc, pos[3]; Tcl_Obj **lobjv, *list = NULL; ZipEntry *z; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_HashTable fileHash; char *strip = NULL, *pw = NULL, pwbuf[264], buf[4096]; if (isList) { if ((objc < 3) || (objc > (isImg ? 5 : 4))) { Tcl_WrongNumArgs(interp, 1, objv, isImg ? "outfile inlist ?password infile?" : "outfile inlist ?password?"); return TCL_ERROR; } } else { if ((objc < 3) || (objc > (isImg ? 6 : 5))) { Tcl_WrongNumArgs(interp, 1, objv, isImg ? "outfile indir ?strip? ?password? ?infile?" : "outfile indir ?strip? ?password?"); return TCL_ERROR; } } pwbuf[0] = 0; if (objc > (isList ? 3 : 4)) { pw = Tcl_GetString(objv[isList ? 3 : 4]); pwlen = strlen(pw); if ((pwlen > 255) || (strchr(pw, 0xff) != NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("illegal password", -1)); return TCL_ERROR; } } if (isList) { list = objv[2]; Tcl_IncrRefCount(list); } else { Tcl_Obj *cmd[3]; cmd[1] = Tcl_NewStringObj("::zipfs::find", -1); cmd[2] = objv[2]; cmd[0] = Tcl_NewListObj(2, cmd + 1); Tcl_IncrRefCount(cmd[0]); if (Tcl_EvalObjEx(interp, cmd[0], TCL_EVAL_DIRECT) != TCL_OK) { Tcl_DecrRefCount(cmd[0]); return TCL_ERROR; } Tcl_DecrRefCount(cmd[0]); list = Tcl_GetObjResult(interp); Tcl_IncrRefCount(list); } if (Tcl_ListObjGetElements(interp, list, &lobjc, &lobjv) != TCL_OK) { Tcl_DecrRefCount(list); return TCL_ERROR; } if (isList && (lobjc % 2)) { Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_NewStringObj("need even number of elements", -1)); return TCL_ERROR; } if (lobjc == 0) { Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_NewStringObj("empty archive", -1)); return TCL_ERROR; } out = Tcl_OpenFileChannel(interp, Tcl_GetString(objv[1]), "w", 0755); if ((out == NULL) || (Tcl_SetChannelOption(interp, out, "-translation", "binary") != TCL_OK) || (Tcl_SetChannelOption(interp, out, "-encoding", "binary") != TCL_OK)) { Tcl_DecrRefCount(list); Tcl_Close(interp, out); return TCL_ERROR; } if (pwlen <= 0) { pw = NULL; pwlen = 0; } if (isImg) { ZipFile *zf, zf0; int isMounted = 0; const char *imgName; if (isList) { imgName = (objc > 4) ? Tcl_GetString(objv[4]) : Tcl_GetNameOfExecutable(); } else { imgName = (objc > 5) ? Tcl_GetString(objv[5]) : Tcl_GetNameOfExecutable(); } if (pwlen) { i = 0; len = pwlen; while (len > 0) { int ch = pw[len - 1]; pwbuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; i++; len--; } pwbuf[i] = i; ++i; pwbuf[i++] = (char) ZIP_PASSWORD_END_SIG; pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 8); pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 16); pwbuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 24); pwbuf[i] = '\0'; } /* Check for mounted image */ WriteLock(); hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); while (hPtr != NULL) { if ((zf = (ZipFile *) Tcl_GetHashValue(hPtr)) != NULL) { if (strcmp(zf->name, imgName) == 0) { isMounted = 1; zf->nopen++; break; } } hPtr = Tcl_NextHashEntry(&search); } Unlock(); if (!isMounted) { zf = &zf0; } if (isMounted || (ZipFSOpenArchive(interp, imgName, 0, zf) == TCL_OK)) { i = Tcl_Write(out, (char *) zf->data, zf->baseoffsp); if (i != zf->baseoffsp) { memset(pwbuf, 0, sizeof(pwbuf)); Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_NewStringObj("write error", -1)); Tcl_Close(interp, out); if (zf == &zf0) { ZipFSCloseArchive(interp, zf); } else { WriteLock(); zf->nopen--; Unlock(); } return TCL_ERROR; } if (zf == &zf0) { ZipFSCloseArchive(interp, zf); } else { WriteLock(); zf->nopen--; Unlock(); } } else { int k, n, m; Tcl_Channel in; const char *errMsg = "seek error"; /* * Fall back to read it as plain file which * hopefully is a static tclsh or wish binary * with proper zipfs infrastructure built in. */ Tcl_ResetResult(interp); in = Tcl_OpenFileChannel(interp, imgName, "r", 0644); if (in == NULL) { memset(pwbuf, 0, sizeof(pwbuf)); Tcl_DecrRefCount(list); Tcl_Close(interp, out); return TCL_ERROR; } Tcl_SetChannelOption(interp, in, "-translation", "binary"); Tcl_SetChannelOption(interp, in, "-encoding", "binary"); i = Tcl_Seek(in, 0, SEEK_END); if (i == -1) { cperr: memset(pwbuf, 0, sizeof(pwbuf)); Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, -1)); Tcl_Close(interp, out); Tcl_Close(interp, in); return TCL_ERROR; } Tcl_Seek(in, 0, SEEK_SET); k = 0; while (k < i) { m = i - k; if (m > sizeof(buf)) { m = sizeof(buf); } n = Tcl_Read(in, buf, m); if (n == -1) { errMsg = "read error"; goto cperr; } else if (n == 0) { break; } m = Tcl_Write(out, buf, n); if (m != n) { errMsg = "write error"; goto cperr; } k += m; } Tcl_Close(interp, in); } len = strlen(pwbuf); if (len > 0) { i = Tcl_Write(out, pwbuf, len); if (i != len) { Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_NewStringObj("write error", -1)); Tcl_Close(interp, out); return TCL_ERROR; } } memset(pwbuf, 0, sizeof(pwbuf)); Tcl_Flush(out); } Tcl_InitHashTable(&fileHash, TCL_STRING_KEYS); pos[0] = Tcl_Tell(out); if (!isList && (objc > 3)) { strip = Tcl_GetString(objv[3]); slen = strlen(strip); } for (i = 0; i < lobjc; i += (isList ? 2 : 1)) { const char *path, *name; path = Tcl_GetString(lobjv[i]); if (isList) { name = Tcl_GetString(lobjv[i + 1]); } else { name = path; if (slen > 0) { len = strlen(name); if ((len <= slen) || (strncmp(strip, name, slen) != 0)) { continue; } name += slen; } } while (name[0] == '/') { ++name; } if (name[0] == '\0') { continue; } if (ZipAddFile(interp, path, name, out, pw, buf, sizeof(buf), &fileHash) != TCL_OK) { goto done; } } pos[1] = Tcl_Tell(out); count = 0; for (i = 0; i < lobjc; i += (isList ? 2 : 1)) { const char *path, *name; path = Tcl_GetString(lobjv[i]); if (isList) { name = Tcl_GetString(lobjv[i + 1]); } else { name = path; if (slen > 0) { len = strlen(name); if ((len <= slen) || (strncmp(strip, name, slen) != 0)) { continue; } name += slen; } } while (name[0] == '/') { ++name; } if (name[0] == '\0') { continue; } hPtr = Tcl_FindHashEntry(&fileHash, name); if (hPtr == NULL) { continue; } z = (ZipEntry *) Tcl_GetHashValue(hPtr); len = strlen(z->name); ZipWriteInt(buf + ZIP_CENTRAL_SIG_OFFS, ZIP_CENTRAL_HEADER_SIG); ZipWriteShort(buf + ZIP_CENTRAL_VERSIONMADE_OFFS, ZIP_MIN_VERSION); ZipWriteShort(buf + ZIP_CENTRAL_VERSION_OFFS, ZIP_MIN_VERSION); ZipWriteShort(buf + ZIP_CENTRAL_FLAGS_OFFS, z->isenc ? 1 : 0); ZipWriteShort(buf + ZIP_CENTRAL_COMPMETH_OFFS, z->cmeth); ZipWriteShort(buf + ZIP_CENTRAL_MTIME_OFFS, ToDosTime(z->timestamp)); ZipWriteShort(buf + ZIP_CENTRAL_MDATE_OFFS, ToDosDate(z->timestamp)); ZipWriteInt(buf + ZIP_CENTRAL_CRC32_OFFS, z->crc32); ZipWriteInt(buf + ZIP_CENTRAL_COMPLEN_OFFS, z->nbytecompr); ZipWriteInt(buf + ZIP_CENTRAL_UNCOMPLEN_OFFS, z->nbyte); ZipWriteShort(buf + ZIP_CENTRAL_PATHLEN_OFFS, len); ZipWriteShort(buf + ZIP_CENTRAL_EXTRALEN_OFFS, 0); ZipWriteShort(buf + ZIP_CENTRAL_FCOMMENTLEN_OFFS, 0); ZipWriteShort(buf + ZIP_CENTRAL_DISKFILE_OFFS, 0); ZipWriteShort(buf + ZIP_CENTRAL_IATTR_OFFS, 0); ZipWriteInt(buf + ZIP_CENTRAL_EATTR_OFFS, 0); ZipWriteInt(buf + ZIP_CENTRAL_LOCALHDR_OFFS, z->offset - pos[0]); if ((Tcl_Write(out, buf, ZIP_CENTRAL_HEADER_LEN) != ZIP_CENTRAL_HEADER_LEN) || (Tcl_Write(out, z->name, len) != len)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("write error", -1)); goto done; } count++; } Tcl_Flush(out); pos[2] = Tcl_Tell(out); ZipWriteInt(buf + ZIP_CENTRAL_END_SIG_OFFS, ZIP_CENTRAL_END_SIG); ZipWriteShort(buf + ZIP_CENTRAL_DISKNO_OFFS, 0); ZipWriteShort(buf + ZIP_CENTRAL_DISKDIR_OFFS, 0); ZipWriteShort(buf + ZIP_CENTRAL_ENTS_OFFS, count); ZipWriteShort(buf + ZIP_CENTRAL_TOTALENTS_OFFS, count); ZipWriteInt(buf + ZIP_CENTRAL_DIRSIZE_OFFS, pos[2] - pos[1]); ZipWriteInt(buf + ZIP_CENTRAL_DIRSTART_OFFS, pos[1] - pos[0]); ZipWriteShort(buf + ZIP_CENTRAL_COMMENTLEN_OFFS, 0); if (Tcl_Write(out, buf, ZIP_CENTRAL_END_LEN) != ZIP_CENTRAL_END_LEN) { Tcl_SetObjResult(interp, Tcl_NewStringObj("write error", -1)); goto done; } Tcl_Flush(out); ret = TCL_OK; done: if (ret == TCL_OK) { ret = Tcl_Close(interp, out); } else { Tcl_Close(interp, out); } Tcl_DecrRefCount(list); hPtr = Tcl_FirstHashEntry(&fileHash, &search); while (hPtr != NULL) { z = (ZipEntry *) Tcl_GetHashValue(hPtr); Tcl_Free((char *) z); Tcl_DeleteHashEntry(hPtr); hPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&fileHash); return ret; } /* *------------------------------------------------------------------------- * * ZipFSMkZipObjCmd -- * * This procedure is invoked to process the "zipfs::mkzip" command. * See description of ZipFSMkZipOrImgCmd(). * * Results: * A standard Tcl result. * * Side effects: * See description of ZipFSMkZipOrImgCmd(). * *------------------------------------------------------------------------- */ static int ZipFSMkZipObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return ZipFSMkZipOrImgObjCmd(clientData, interp, 0, 0, objc, objv); } static int ZipFSLMkZipObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return ZipFSMkZipOrImgObjCmd(clientData, interp, 0, 1, objc, objv); } /* *------------------------------------------------------------------------- * * ZipFSMkImgObjCmd -- * * This procedure is invoked to process the "zipfs::mkimg" command. * See description of ZipFSMkZipOrImgCmd(). * * Results: * A standard Tcl result. * * Side effects: * See description of ZipFSMkZipOrImgCmd(). * *------------------------------------------------------------------------- */ static int ZipFSMkImgObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return ZipFSMkZipOrImgObjCmd(clientData, interp, 1, 0, objc, objv); } static int ZipFSLMkImgObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return ZipFSMkZipOrImgObjCmd(clientData, interp, 1, 1, objc, objv); } /* *------------------------------------------------------------------------- * * ZipFSExistsObjCmd -- * * This procedure is invoked to process the "zipfs::exists" command. * It tests for the existence of a file in the ZIP filesystem and * places a boolean into the interp's result. * * Results: * Always TCL_OK. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSExistsObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { char *filename; int exists; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "filename"); return TCL_ERROR; } filename = Tcl_GetStringFromObj(objv[1], 0); ReadLock(); exists = ZipFSLookup(filename) != NULL; Unlock(); Tcl_SetBooleanObj(Tcl_GetObjResult(interp), exists); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSInfoObjCmd -- * * This procedure is invoked to process the "zipfs::info" command. * On success, it returns a Tcl list made up of name of ZIP archive * file, size uncompressed, size compressed, and archive offset of * a file in the ZIP filesystem. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSInfoObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { char *filename; ZipEntry *z; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "filename"); return TCL_ERROR; } filename = Tcl_GetStringFromObj(objv[1], 0); ReadLock(); z = ZipFSLookup(filename); if (z != NULL) { Tcl_Obj *result = Tcl_GetObjResult(interp); Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->zipfile->name, -1)); Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(z->nbyte)); Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(z->nbytecompr)); Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(z->offset)); } Unlock(); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSListObjCmd -- * * This procedure is invoked to process the "zipfs::list" command. * On success, it returns a Tcl list of files of the ZIP filesystem * which match a search pattern (glob or regexp). * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSListObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { char *pattern = NULL; Tcl_RegExp regexp = NULL; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_Obj *result = Tcl_GetObjResult(interp); if (objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?(-glob|-regexp)? ?pattern?"); return TCL_ERROR; } if (objc == 3) { int n; char *what = Tcl_GetStringFromObj(objv[1], &n); if ((n >= 2) && (strncmp(what, "-glob", n) == 0)) { pattern = Tcl_GetString(objv[2]); } else if ((n >= 2) && (strncmp(what, "-regexp", n) == 0)) { regexp = Tcl_RegExpCompile(interp, Tcl_GetString(objv[2])); if (regexp == NULL) { return TCL_ERROR; } } else { Tcl_AppendResult(interp, "unknown option \"", what, "\"", (char *) NULL); return TCL_ERROR; } } else if (objc == 2) { pattern = Tcl_GetStringFromObj(objv[1], 0); } ReadLock(); if (pattern != NULL) { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (Tcl_StringMatch(z->name, pattern)) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } } else if (regexp != NULL) { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (Tcl_RegExpExec(interp, regexp, z->name, z->name)) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } } else { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } Unlock(); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipChannelClose -- * * This function is called to close a channel. * * Results: * Always TCL_OK. * * Side effects: * Resources are free'd. * *------------------------------------------------------------------------- */ static int ZipChannelClose(ClientData instanceData, Tcl_Interp *interp) { ZipChannel *info = (ZipChannel *) instanceData; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); WriteLock(); if (info->iswr) { ZipEntry *z = info->zipentry; unsigned char *newdata; /* * If opened for writing, commit the entire file * to the mounted archive now. */ newdata = (unsigned char *) Tcl_AttemptRealloc((char *) info->ubuf, info->nread); if (newdata != NULL) { if (z->data != NULL) { Tcl_Free((char *) z->data); } z->data = newdata; z->nbyte = z->nbytecompr = info->nbyte; z->cmeth = ZIP_COMPMETH_STORED; z->timestamp = time(NULL); z->isdir = 0; z->isenc = 0; z->offset = 0; z->crc32 = 0; info->tofree = info->ubuf = NULL; } } info->zipfile->nopen--; Unlock(); if (info->tofree != NULL) { Tcl_Free((char *) info->tofree); } if ((tsdPtr != NULL) && tsdPtr->initialized) { Tcl_HashEntry *hPtr; hPtr = Tcl_FindHashEntry(&tsdPtr->chanTab, (ClientData) info); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } Tcl_Free((char *) info); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipChannelRead -- * * This function is called to read data from channel. * * Results: * Number of bytes read or -1 on error with error number set. * * Side effects: * Data is read and file pointer is advanced. * *------------------------------------------------------------------------- */ static int ZipChannelRead(ClientData instanceData, char *buf, int toRead, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; unsigned long nextpos; if (info->isdir < 0) { /* * Special case: when executable combined with ZIP archive file * read data in front of ZIP, i.e. the executable itself. */ nextpos = info->nread + toRead; if (nextpos > info->zipfile->baseoffs) { toRead = info->zipfile->baseoffs - info->nread; nextpos = info->zipfile->baseoffs; } if (toRead == 0) { return 0; } memcpy(buf, info->zipfile->data + info->nread, toRead); info->nread = nextpos; *errloc = 0; return toRead; } if (info->isdir) { *errloc = EISDIR; return -1; } nextpos = info->nread + toRead; if (nextpos > info->nbyte) { toRead = info->nbyte - info->nread; nextpos = info->nbyte; } if (toRead == 0) { return 0; } memcpy(buf, info->ubuf + info->nread, toRead); info->nread = nextpos; *errloc = 0; return toRead; } /* *------------------------------------------------------------------------- * * ZipChannelWrite -- * * This function is called to write data into channel. * * Results: * Number of bytes written or -1 on error with error number set. * * Side effects: * Data is written and file pointer is advanced. * *------------------------------------------------------------------------- */ static int ZipChannelWrite(ClientData instanceData, const char *buf, int toWrite, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; unsigned long nextpos; if (!info->iswr) { *errloc = EINVAL; return -1; } nextpos = info->nread + toWrite; if (nextpos > info->nmax) { toWrite = info->nmax - info->nread; nextpos = info->nmax; } if (toWrite == 0) { return 0; } memcpy(info->ubuf + info->nread, buf, toWrite); info->nread = nextpos; if (info->nread > info->nbyte) { info->nbyte = info->nread; } *errloc = 0; return toWrite; } /* *------------------------------------------------------------------------- * * ZipChannelSeek -- * * This function is called to position file pointer of channel. * * Results: * New file position or -1 on error with error number set. * * Side effects: * File pointer is repositioned according to offset and mode. * *------------------------------------------------------------------------- */ static int ZipChannelSeek(ClientData instanceData, long offset, int mode, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; unsigned long end; if (!info->iswr && (info->isdir < 0)) { /* * Special case: when executable combined with ZIP archive file, * seek within front of ZIP, i.e. the executable itself. */ end = info->zipfile->baseoffs; } else if (info->isdir) { *errloc = EINVAL; return -1; } else { end = info->nbyte; } switch (mode) { case SEEK_CUR: offset += info->nread; break; case SEEK_END: offset += end; break; case SEEK_SET: break; default: *errloc = EINVAL; return -1; } if (offset < 0) { *errloc = EINVAL; return -1; } if (info->iswr) { if ((unsigned long) offset > info->nmax) { *errloc = EINVAL; return -1; } if ((unsigned long) offset > info->nbyte) { info->nbyte = offset; } } else if ((unsigned long) offset > end) { *errloc = EINVAL; return -1; } info->nread = (unsigned long) offset; return info->nread; } /* *------------------------------------------------------------------------- * * ZipChannelWatchChannel -- * * This function is called for event notifications on channel. * * Results: * None. * * Side effects: * None. * *------------------------------------------------------------------------- */ static void ZipChannelWatchChannel(ClientData instanceData, int mask) { ZipEvent *event; ZipChannel *info = (ZipChannel *) instanceData; Tcl_HashEntry *hPtr; int isNew; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); mask &= ~TCL_EXCEPTION; /* not supported at all */ if ((mask & TCL_WRITABLE) && !info->iswr) { mask &= ~TCL_WRITABLE; } info->evmask &= ~(TCL_READABLE | TCL_WRITABLE); info->evmask |= mask & (TCL_READABLE | TCL_WRITABLE); if (info->evmask & (TCL_READABLE | TCL_WRITABLE)) { if ((tsdPtr != NULL) && tsdPtr->initialized) { hPtr = Tcl_CreateHashEntry(&tsdPtr->chanTab, (ClientData) info, &isNew); if (isNew) { Tcl_SetHashValue(hPtr, (ClientData) info); event = ckalloc(sizeof(ZipEvent)); event->header.proc = ZipEventProc; event->info = info; Tcl_QueueEvent((Tcl_Event *) event, TCL_QUEUE_TAIL); } } } else { info->evmask &= ~(ZIPCHANNEL_NONBLOCK); if ((tsdPtr != NULL) && tsdPtr->initialized) { hPtr = Tcl_FindHashEntry(&tsdPtr->chanTab, (ClientData) info); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } } } /* *------------------------------------------------------------------------- * * ZipChannelGetFile -- * * This function is called to retrieve OS handle for channel. * * Results: * Always TCL_ERROR since there's never an OS handle for a * file within a ZIP archive. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipChannelGetFile(ClientData instanceData, int direction, ClientData *handlePtr) { return TCL_ERROR; } #ifdef TCL_CHANNEL_VERSION_4 /* *---------------------------------------------------------------------- * * ZipChannelBlockMode -- * * Set blocking or non-blocking mode on channel. * * Results: * 0 if successful, errno when failed. * * Side effects: * Sets the channel into blocking or non-blocking mode. * *---------------------------------------------------------------------- */ static int ZipChannelBlockMode(ClientData instanceData, int mode) { ZipChannel *info = (ZipChannel *) instanceData; if (mode == TCL_MODE_NONBLOCKING) { info->evmask |= ZIPCHANNEL_NONBLOCK; } else { info->evmask &= ~(ZIPCHANNEL_NONBLOCK); } return 0; } #endif #ifdef TCL_CHANNEL_VERSION_4 /* *---------------------------------------------------------------------- * * ZipChannelThreadAction -- * * Insert or remove any thread local refs to this channel. * * Results: * None. * * Side effects: * Changes thread local info of valid channels. * *---------------------------------------------------------------------- */ static void ZipChannelThreadAction(ClientData instanceData, int action) { ZipChannel *info = (ZipChannel *) instanceData; Tcl_HashEntry *hPtr; int isNew; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); static const Tcl_Time blockTime = { 0, 0 }; if (tsdPtr == NULL) { return; } if (action == TCL_CHANNEL_THREAD_INSERT) { if (info->evmask & (TCL_READABLE | TCL_WRITABLE)) { hPtr = Tcl_CreateHashEntry(&tsdPtr->chanTab, (ClientData) info, &isNew); if (isNew) { Tcl_SetHashValue(hPtr, (ClientData) info); Tcl_SetMaxBlockTime(&blockTime); } } } else { hPtr = Tcl_FindHashEntry(&tsdPtr->chanTab, (ClientData) info); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } } #endif /* * The channel type/driver definition used for ZIP archive members. */ static Tcl_ChannelType ZipChannelType = { "zip", /* Type name. */ #ifdef TCL_CHANNEL_VERSION_4 TCL_CHANNEL_VERSION_4, ZipChannelClose, /* Close channel, clean instance data */ ZipChannelRead, /* Handle read request */ ZipChannelWrite, /* Handle write request */ ZipChannelSeek, /* Move location of access point, NULL'able */ NULL, /* Set options, NULL'able */ NULL, /* Get options, NULL'able */ ZipChannelWatchChannel, /* Initialize notifier */ ZipChannelGetFile, /* Get OS handle from the channel */ NULL, /* 2nd version of close channel, NULL'able */ ZipChannelBlockMode, /* Set blocking mode for raw channel, NULL'able */ NULL, /* Function to flush channel, NULL'able */ NULL, /* Function to handle event, NULL'able */ NULL, /* Wide seek function, NULL'able */ ZipChannelThreadAction, /* Thread action function, NULL'able */ #else NULL, /* Set blocking/nonblocking behaviour, NULL'able */ ZipChannelClose, /* Close channel, clean instance data */ ZipChannelRead, /* Handle read request */ ZipChannelWrite, /* Handle write request */ ZipChannelSeek, /* Move location of access point, NULL'able */ NULL, /* Set options, NULL'able */ NULL, /* Get options, NULL'able */ ZipChannelWatchChannel, /* Initialize notifier */ ZipChannelGetFile, /* Get OS handle from the channel */ #endif }; /* *------------------------------------------------------------------------- * * ZipChannelOpen -- * * This function opens a Tcl_Channel on a file from a mounted ZIP * archive according to given open mode. * * Results: * Tcl_Channel on success, or NULL on error. * * Side effects: * Memory is allocated, the file from the ZIP archive is uncompressed. * *------------------------------------------------------------------------- */ static Tcl_Channel ZipChannelOpen(Tcl_Interp *interp, char *filename, int mode, int permissions) { ZipEntry *z; ZipChannel *info; int i, ch, trunc, wr, flags = 0; unsigned long keys[3]; char cname[128]; ZipChannelInit(); /* Ensure TSD is initialized. */ if ((mode & O_APPEND) || ((ZipFS.wrmax <= 0) && (mode & (O_WRONLY | O_RDWR)))) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unsupported open mode", -1)); } return NULL; } WriteLock(); z = ZipFSLookup(filename); if (z == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file not found", -1)); } goto error; } z->zipfile->nopen++; trunc = (mode & O_TRUNC) != 0; wr = (mode & (O_WRONLY | O_RDWR)) != 0; if ((z->cmeth != ZIP_COMPMETH_STORED) && (z->cmeth != ZIP_COMPMETH_DEFLATED)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unsupported compression method", -1)); } goto error; } if (wr && z->isdir) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unsupported file type", -1)); } goto error; } if (!trunc) { flags |= TCL_READABLE; if (z->isenc && (z->zipfile->pwbuf[0] == 0)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("decryption failed", -1)); } goto error; } else if (wr && (z->data == NULL) && (z->nbyte > ZipFS.wrmax)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file too large", -1)); } goto error; } } else { flags = TCL_WRITABLE; } info = (ZipChannel *) Tcl_AttemptAlloc(sizeof(ZipChannel)); if (info == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); } goto error; } info->zipfile = z->zipfile; info->zipentry = z; info->nread = 0; info->chan = NULL; info->evmask = 0; if (wr) { flags |= TCL_WRITABLE; info->iswr = 1; info->isdir = 0; info->nbyte = 0; info->nmax = ZipFS.wrmax; info->iscompr = 0; info->ubuf = (unsigned char *) Tcl_AttemptAlloc(info->nmax); if (info->ubuf == NULL) { merror0: if (info->ubuf != NULL) { Tcl_Free((char *) info->ubuf); } Tcl_Free((char *) info); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); } goto error; } info->tofree = info->ubuf; memset(info->ubuf, 0, info->nmax); if (trunc) { info->nbyte = 0; } else { if (z->data != NULL) { unsigned int j = z->nbyte; if (j > info->nmax) { j = info->nmax; } memcpy(info->ubuf, z->data, j); info->nbyte = j; } else { unsigned char *zbuf = z->zipfile->data + z->offset; if (z->isenc) { int len = z->zipfile->pwbuf[0] & 0xff; char pwbuf[260]; for (i = 0; i < len; i++) { ch = z->zipfile->pwbuf[len - i]; pwbuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; } pwbuf[i] = '\0'; init_keys(pwbuf, keys, crc32tab); memset(pwbuf, 0, sizeof(pwbuf)); for (i = 0; i < 12; i++) { ch = zbuf[i]; zdecode(keys, crc32tab, ch); } zbuf += i; } if (z->cmeth == ZIP_COMPMETH_DEFLATED) { z_stream stream; int err; unsigned char *cbuf = NULL; memset(&stream, 0, sizeof(stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = z->nbytecompr; if (z->isenc) { unsigned int j; stream.avail_in -= 12; cbuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in); if (cbuf == NULL) { memset(keys, 0, sizeof(keys)); goto merror0; } for (j = 0; j < stream.avail_in; j++) { ch = zbuf[j]; cbuf[j] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); stream.next_in = cbuf; } else { stream.next_in = zbuf; } stream.next_out = info->ubuf; stream.avail_out = info->nmax; if (inflateInit2(&stream, -15) != Z_OK) { goto cerror0; } err = inflate(&stream, Z_SYNC_FLUSH); inflateEnd(&stream); if ((err == Z_STREAM_END) || ((err == Z_OK) && (stream.avail_in == 0))) { if (cbuf != NULL) { Tcl_Free((char *) cbuf); } info->nbyte = z->nbyte; goto wrapchan; } cerror0: if (cbuf != NULL) { Tcl_Free((char *) cbuf); } if (info->ubuf != NULL) { Tcl_Free((char *) info->ubuf); } Tcl_Free((char *) info); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("decompression error", -1)); } goto error; } else if (z->isenc) { for (i = 0; i < z->nbyte - 12; i++) { ch = zbuf[i]; info->ubuf[i] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); info->nbyte = i; } else { memcpy(info->ubuf, zbuf, z->nbyte); info->nbyte = z->nbyte; } } } } else if (z->data != NULL) { flags |= TCL_READABLE; info->iswr = 0; info->iscompr = 0; info->isdir = 0; info->nbyte = z->nbyte; info->nmax = 0; /* * Must copy data since other channel to same archive member * in another thread may write and close its channel and thus * invalidate z->data. */ info->ubuf = (unsigned char *) Tcl_AttemptAlloc(info->nbyte); if (info->ubuf == NULL) { goto merror0; } memcpy(info->ubuf, z->data, info->nbyte); info->tofree = info->ubuf; } else { flags |= TCL_READABLE; info->iswr = 0; info->iscompr = z->cmeth == ZIP_COMPMETH_DEFLATED; info->ubuf = z->zipfile->data + z->offset; info->isdir = z->isdir; info->nbyte = z->nbyte; info->nmax = 0; info->tofree = NULL; if (z->isenc) { int len = z->zipfile->pwbuf[0] & 0xff; char pwbuf[260]; for (i = 0; i < len; i++) { ch = z->zipfile->pwbuf[len - i]; pwbuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; } pwbuf[i] = '\0'; init_keys(pwbuf, keys, crc32tab); memset(pwbuf, 0, sizeof(pwbuf)); for (i = 0; i < 12; i++) { ch = info->ubuf[i]; zdecode(keys, crc32tab, ch); } info->ubuf += i; } if (info->iscompr) { z_stream stream; int err; unsigned char *ubuf = NULL; unsigned int j; memset(&stream, 0, sizeof(stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = z->nbytecompr; if (z->isenc) { stream.avail_in -= 12; ubuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in); if (ubuf == NULL) { info->ubuf = NULL; memset(keys, 0, sizeof(keys)); goto merror; } for (j = 0; j < stream.avail_in; j++) { ch = info->ubuf[j]; ubuf[j] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); stream.next_in = ubuf; } else { stream.next_in = info->ubuf; } stream.next_out = info->ubuf = (unsigned char *) Tcl_AttemptAlloc(info->nbyte); if (info->ubuf == NULL) { merror: if (ubuf != NULL) { Tcl_Free((char *) ubuf); } Tcl_Free((char *) info); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); } goto error; } info->tofree = info->ubuf; stream.avail_out = info->nbyte; if (inflateInit2(&stream, -15) != Z_OK) { goto cerror; } err = inflate(&stream, Z_SYNC_FLUSH); inflateEnd(&stream); if ((err == Z_STREAM_END) || ((err == Z_OK) && (stream.avail_in == 0))) { if (ubuf != NULL) { Tcl_Free((char *) ubuf); } goto wrapchan; } cerror: if (ubuf != NULL) { Tcl_Free((char *) ubuf); } if (info->ubuf != NULL) { Tcl_Free((char *) info->ubuf); } Tcl_Free((char *) info); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("decompression error", -1)); } goto error; } else if (z->isenc) { unsigned char *ubuf = NULL; unsigned int j, len; /* * Decode encrypted but uncompressed file, since we support * Tcl_Seek() on it, and it can be randomly accessed later. */ len = z->nbytecompr - 12; ubuf = (unsigned char *) Tcl_AttemptAlloc(len); if (ubuf == NULL) { memset(keys, 0, sizeof(keys)); Tcl_Free((char *) info); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); } goto error; } for (j = 0; j < len; j++) { ch = info->ubuf[j]; ubuf[j] = zdecode(keys, crc32tab, ch); } memset(keys, 0, sizeof(keys)); info->tofree = info->ubuf = ubuf; } } wrapchan: Unlock(); sprintf(cname, "zipfs_%lx_%d", (unsigned long) z->offset, ZipFS.idCount++); info->chan = Tcl_CreateChannel(&ZipChannelType, cname, (ClientData) info, flags); return info->chan; error: if (z != NULL) { z->zipfile->nopen--; } Unlock(); return NULL; } /* *---------------------------------------------------------------------- * * ZipEventProc -- * * This function is invoked by Tcl_ServiceEvent when a channel * event reaches the front of the event queue. This function * invokes Tcl_NotifyChannel on the ZIP channel. * * Results: * Returns 1 if the event was handled, meaning it should be * removed from the queue. Returns 0 if the event was not handled, * meaning it should stay on the queue. The only time the event * isn't handled is if the TCL_FILE_EVENTS flag bit isn't set. * * Side effects: * Whatever the notifier callback does. * *---------------------------------------------------------------------- */ static int ZipEventProc(Tcl_Event *evPtr, int flags) { ZipEvent *event = (ZipEvent *) evPtr; ZipChannel *info; Tcl_HashEntry *hPtr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); int mask; if ((tsdPtr == NULL) || !tsdPtr->initialized) { return 1; } if (!(flags & TCL_FILE_EVENTS)) { return 0; } hPtr = Tcl_FindHashEntry(&tsdPtr->chanTab, (ClientData) event->info); if (hPtr != NULL) { info = (ZipChannel *) Tcl_GetHashValue(hPtr); info->evmask &= ~ZIPCHANNEL_PENDING; mask = info->evmask & (TCL_READABLE | TCL_WRITABLE); if (mask) { Tcl_NotifyChannel(info->chan, mask); } } return 1; } /* *------------------------------------------------------------------------- * * ZipChannelExitHandler -- * * This function is called to cleanup thread specific channel * information. * * Results: * None. * * Side effects: * None. * *------------------------------------------------------------------------- */ static void ZipChannelExitHandler(ClientData clientData) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->initialized) { Tcl_DeleteHashTable(&tsdPtr->chanTab); tsdPtr->initialized = 0; } } /* *------------------------------------------------------------------------- * * ZipChannelInit -- * * This function is called to setup thread specific channel * information. * * Results: * Pointer to thread specific data. * * Side effects: * None. * *------------------------------------------------------------------------- */ static ThreadSpecificData * ZipChannelInit(void) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_InitHashTable(&tsdPtr->chanTab, TCL_ONE_WORD_KEYS); Tcl_CreateThreadExitHandler(ZipChannelExitHandler, NULL); tsdPtr->initialized = 1; } return tsdPtr; } /* *------------------------------------------------------------------------- * * ZipEntryStat -- * * This function implements the ZIP filesystem specific version * of the library version of stat. * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *------------------------------------------------------------------------- */ static int ZipEntryStat(char *path, Tcl_StatBuf *buf) { ZipEntry *z; int ret = -1; ReadLock(); z = ZipFSLookup(path); if (z == NULL) { goto done; } memset(buf, 0, sizeof(Tcl_StatBuf)); if (z->isdir) { buf->st_mode = S_IFDIR | 0555; } else { buf->st_mode = S_IFREG | 0555; } buf->st_size = z->nbyte; buf->st_mtime = z->timestamp; buf->st_ctime = z->timestamp; buf->st_atime = z->timestamp; ret = 0; done: Unlock(); return ret; } /* *------------------------------------------------------------------------- * * ZipEntryAccess -- * * This function implements the ZIP filesystem specific version * of the library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *------------------------------------------------------------------------- */ static int ZipEntryAccess(char *path, int mode) { ZipEntry *z; if (mode & 3) { return -1; } ReadLock(); z = ZipFSLookup(path); Unlock(); return (z != NULL) ? 0 : -1; } /* *------------------------------------------------------------------------- * * Zip_FSOpenFileChannelProc -- * * Results: * * Side effects: * *------------------------------------------------------------------------- */ static Tcl_Channel Zip_FSOpenFileChannelProc(Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions) { int len; return ZipChannelOpen(interp, Tcl_GetStringFromObj(pathPtr, &len), mode, permissions); } /* *------------------------------------------------------------------------- * * Zip_FSStatProc -- * * This function implements the ZIP filesystem specific version * of the library version of stat. * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *------------------------------------------------------------------------- */ static int Zip_FSStatProc(Tcl_Obj *pathPtr, Tcl_StatBuf *buf) { int len; return ZipEntryStat(Tcl_GetStringFromObj(pathPtr, &len), buf); } /* *------------------------------------------------------------------------- * * Zip_FSAccessProc -- * * This function implements the ZIP filesystem specific version * of the library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *------------------------------------------------------------------------- */ static int Zip_FSAccessProc(Tcl_Obj *pathPtr, int mode) { int len; return ZipEntryAccess(Tcl_GetStringFromObj(pathPtr, &len), mode); } /* *------------------------------------------------------------------------- * * Zip_FSFilesystemSeparatorProc -- * * This function returns the separator to be used for a given path. The * object returned should have a refCount of zero * * Results: * A Tcl object, with a refCount of zero. If the caller needs to retain a * reference to the object, it should call Tcl_IncrRefCount, and should * otherwise free the object. * * Side effects: * None. * *------------------------------------------------------------------------- */ static Tcl_Obj * Zip_FSFilesystemSeparatorProc(Tcl_Obj *pathPtr) { return Tcl_NewStringObj("/", -1); } /* *------------------------------------------------------------------------- * * Zip_FSMatchInDirectoryProc -- * * This routine is used by the globbing code to search a directory for * all files which match a given pattern. * * Results: * The return value is a standard Tcl result indicating whether an * error occurred in globbing. Errors are left in interp, good * results are lappend'ed to resultPtr (which must be a valid object). * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Zip_FSMatchInDirectoryProc(Tcl_Interp* interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; int scnt, len, dirOnly = -1, prefixLen, strip = 0, matchHidden = 0; char *prefix, *path, *p; #if HAS_DRIVES int drive = 0; char drivePrefix[3]; #endif Tcl_DString ds, dsPref; #if HAS_DRIVES if ((pattern != NULL) && (pattern[0] != '\0') && (strchr(drvletters, pattern[0]) != NULL) && (pattern[1] == ':')) { pattern += 2; } #endif if (types != NULL) { dirOnly = (types->type & TCL_GLOB_TYPE_DIR) == TCL_GLOB_TYPE_DIR; } Tcl_DStringInit(&ds); Tcl_DStringInit(&dsPref); prefix = Tcl_GetStringFromObj(pathPtr, &prefixLen); Tcl_DStringAppend(&dsPref, prefix, prefixLen); prefix = Tcl_DStringValue(&dsPref); #if HAS_DRIVES path = AbsolutePath(prefix, NULL, &ds); #else path = AbsolutePath(prefix, &ds); #endif len = Tcl_DStringLength(&ds); if (strcmp(prefix, path) == 0) { prefix = NULL; } else if (strcmp(prefix, path + len - prefixLen) == 0) { strip = len - prefixLen; prefix = NULL; } else { #if HAS_DRIVES if ((strchr(drvletters, prefix[0]) != NULL) && (prefix[1] == ':')) { if (strcmp(prefix + 2, path) == 0) { strncpy(drivePrefix, prefix, 3); drivePrefix[2] = '\0'; prefix = drivePrefix; } } else { strip = len + 1; } #else strip = len + 1; #endif } if (prefix != NULL) { #if HAS_DRIVES if (prefix == drivePrefix) { Tcl_DStringSetLength(&dsPref, 0); Tcl_DStringAppend(&dsPref, drivePrefix, -1); prefixLen = Tcl_DStringLength(&dsPref); } else { Tcl_DStringAppend(&dsPref, "/", 1); prefixLen++; } prefix = Tcl_DStringValue(&dsPref); drive = prefix[0]; if ((drive >= 'a') && (drive <= 'z')) { drive -= 'a' - 'A'; } #else Tcl_DStringAppend(&dsPref, "/", 1); prefixLen++; prefix = Tcl_DStringValue(&dsPref); #endif } if ((pattern != NULL) && ((pattern[0] == '.') || ((pattern[0] == '\\') && (pattern[1] == '.')))) { matchHidden = 1; } ReadLock(); if ((types != NULL) && (types->type == TCL_GLOB_TYPE_MOUNT)) { scnt = CountSlashes(path); if (path[len - 1] == '/') { len--; } else { scnt++; } if ((pattern == NULL) || (pattern[0] == '\0')) { pattern = "*"; } for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr); #if HAS_DRIVES if (drive && (drive != zf->mntdrv)) { continue; } #endif if (zf->mntptlen == 0) { ZipEntry *z = zf->topents; while (z != NULL) { int lenz = strlen(z->name); if ((lenz > len + 1) && (strncmp(z->name, path, len) == 0) && (z->name[len] == '/') && (CountSlashes(z->name) == scnt) && Tcl_StringCaseMatch(z->name + len + 1, pattern, 0)) { if (!matchHidden) { p = strrchr(z->name, '/'); if ((p != NULL) && (p[1] == '.')) { goto nextent; } } if (prefix != NULL) { Tcl_DStringAppend(&dsPref, z->name, lenz); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(Tcl_DStringValue(&dsPref), Tcl_DStringLength(&dsPref))); Tcl_DStringSetLength(&dsPref, prefixLen); } else { Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(z->name, lenz)); } } nextent: z = z->tnext; } } else if ((zf->mntptlen > len + 1) && (strncmp(zf->mntpt, path, len) == 0) && (zf->mntpt[len] == '/') && (CountSlashes(zf->mntpt) == scnt) && Tcl_StringCaseMatch(zf->mntpt + len + 1, pattern, 0)) { if (!matchHidden) { p = strrchr(zf->mntpt, '/'); if ((p != NULL) && (p[1] == '.')) { goto end; } } if (prefix != NULL) { Tcl_DStringAppend(&dsPref, zf->mntpt, zf->mntptlen); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(Tcl_DStringValue(&dsPref), Tcl_DStringLength(&dsPref))); Tcl_DStringSetLength(&dsPref, prefixLen); } else { Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(zf->mntpt, zf->mntptlen)); } } } goto end; } if ((pattern == NULL) || (pattern[0] == '\0')) { hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, path); if (hPtr != NULL) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); #if HAS_DRIVES if (drive && (drive != z->zipfile->mntdrv)) { goto end; } #endif if ((dirOnly < 0) || (!dirOnly && !z->isdir) || (dirOnly && z->isdir)) { if (prefix != NULL) { Tcl_DStringAppend(&dsPref, z->name, -1); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(Tcl_DStringValue(&dsPref), Tcl_DStringLength(&dsPref))); Tcl_DStringSetLength(&dsPref, prefixLen); } else { Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(z->name + strip, -1)); } } } goto end; } hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, path); if (hPtr != NULL) { ZipDirEntry *zd = (ZipDirEntry *) Tcl_GetHashValue(hPtr); if (!zd->entry.isdir) { goto end; } hPtr = Tcl_FirstHashEntry(&zd->children, &search); } Tcl_DStringAppend(&ds, "/", 1); path = Tcl_DStringValue(&ds); len = Tcl_DStringLength(&ds); while ((len > 1) && (path[len - 1] == '/')) { --len; } if ((len > 1) || (path[0] != '/')) { path[len] = '/'; ++len; } Tcl_DStringSetLength(&ds, len); Tcl_DStringAppend(&ds, pattern, -1); path = Tcl_DStringValue(&ds); scnt = CountSlashes(path); for (; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if ((dirOnly >= 0) && ((dirOnly && !z->isdir) || (!dirOnly && z->isdir))) { continue; } #if HAS_DRIVES if (drive && (drive != z->zipfile->mntdrv)) { continue; } #endif if ((z->depth == scnt) && (strncmp(z->name, path, len) == 0) && Tcl_StringCaseMatch(z->name + len, path + len, 0)) { if (!matchHidden && (z->name[len] == '.')) { continue; } if (prefix != NULL) { Tcl_DStringAppend(&dsPref, z->name + strip, -1); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(Tcl_DStringValue(&dsPref), Tcl_DStringLength(&dsPref))); Tcl_DStringSetLength(&dsPref, prefixLen); } else { Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(z->name + strip, -1)); } } } end: Unlock(); Tcl_DStringFree(&dsPref); Tcl_DStringFree(&ds); return TCL_OK; } /* *------------------------------------------------------------------------- * * Zip_FSNormalizePathProc -- * * Function to normalize given path object. * * Results: * Length of final absolute path name. * * Side effects: * Path object gets converted to an absolute path. * *------------------------------------------------------------------------- */ static int Zip_FSNormalizePathProc(Tcl_Interp *interp, Tcl_Obj *pathPtr, int nextCheckpoint) { char *path; Tcl_DString ds; int len; path = Tcl_GetStringFromObj(pathPtr, &len); Tcl_DStringInit(&ds); #if HAS_DRIVES path = AbsolutePath(path, NULL, &ds); #else path = AbsolutePath(path, &ds); #endif nextCheckpoint = Tcl_DStringLength(&ds); Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); Tcl_DStringFree(&ds); return nextCheckpoint; } /* *------------------------------------------------------------------------- * * Zip_FSPathInFilesystemProc -- * * This function determines if the given path object is in the * ZIP filesystem. * * Results: * TCL_OK when the path object is in the ZIP filesystem, -1 otherwise. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Zip_FSPathInFilesystemProc(Tcl_Obj *pathPtr, ClientData *clientDataPtr) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; ZipFile *zf; int ret = -1, len; char *path; Tcl_DString ds; #if HAS_DRIVES int drive = 0; #endif path = Tcl_GetStringFromObj(pathPtr, &len); Tcl_DStringInit(&ds); #if HAS_DRIVES path = AbsolutePath(path, &drive, &ds); #else path = AbsolutePath(path, &ds); #endif len = Tcl_DStringLength(&ds); #if HAS_DRIVES if (len && (strchr(drvletters, path[0]) != NULL) && (path[1] == ':')) { path += 2; len -= 2; } #endif ReadLock(); hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, path); if (hPtr != NULL) { #if HAS_DRIVES ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (drive == z->zipfile->mntdrv) { ret = TCL_OK; goto endloop; } #else ret = TCL_OK; goto endloop; #endif } hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); while (hPtr != NULL) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); #if HAS_DRIVES if (drive != zf->mntdrv) { hPtr = Tcl_NextHashEntry(&search); continue; } #endif if (zf->mntptlen == 0) { ZipEntry *z = zf->topents; while (z != NULL) { int lenz = strlen(z->name); if ((len >= lenz) && (strncmp(path, z->name, lenz) == 0)) { if ((len == lenz) || (path[lenz] == '/')) { ret = TCL_OK; goto endloop; } } z = z->tnext; } } else if ((len >= zf->mntptlen) && (strncmp(path, zf->mntpt, zf->mntptlen) == 0)) { if ((len == zf->mntptlen) || (path[zf->mntptlen] == '/')) { ret = TCL_OK; goto endloop; } } hPtr = Tcl_NextHashEntry(&search); } endloop: Unlock(); Tcl_DStringFree(&ds); return ret; } /* *------------------------------------------------------------------------- * * Zip_FSListVolumesProc -- * * Lists the currently mounted ZIP filesystem volumes. * * Results: * The list of volumes. * * Side effects: * None * *------------------------------------------------------------------------- */ static Tcl_Obj * Zip_FSListVolumesProc(void) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; ZipFile *zf; Tcl_Obj *vols = Tcl_NewObj(), *vol; ReadLock(); hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); while (hPtr != NULL) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); /* * Volumes which overlay root are hidden. */ #if HAS_DRIVES if (zf->mntpt[0]) { vol = Tcl_ObjPrintf("%c:%s", zf->mntdrv, zf->mntpt); Tcl_ListObjAppendElement(NULL, vols, vol); } #else if (zf->mntpt[0]) { vol = Tcl_NewStringObj(zf->mntpt, zf->mntptlen); Tcl_ListObjAppendElement(NULL, vols, vol); } #endif hPtr = Tcl_NextHashEntry(&search); } Unlock(); Tcl_IncrRefCount(vols); return vols; } /* *------------------------------------------------------------------------- * * Zip_FSChdirProc -- * * If the path object refers to a directory within the ZIP * filesystem the current directory is set to this directory. * * Results: * TCL_OK on success, -1 on error with error number set. * * Side effects: * The global cwdPathPtr may change value. * *------------------------------------------------------------------------- */ static int Zip_FSChdirProc(Tcl_Obj *pathPtr) { int len; char *path; Tcl_DString ds; ZipEntry *z; int ret = TCL_OK; #if HAS_DRIVES int drive = 0; #endif path = Tcl_GetStringFromObj(pathPtr, &len); Tcl_DStringInit(&ds); #if HAS_DRIVES path = AbsolutePath(path, &drive, &ds); #else path = AbsolutePath(path, &ds); #endif ReadLock(); z = ZipFSLookup(path); if ((z == NULL) || !z->isdir) { Tcl_SetErrno(ENOENT); ret = -1; } #if HAS_DRIVES if ((z != NULL) && (drive != z->zipfile->mntdrv)) { Tcl_SetErrno(ENOENT); ret = -1; } #endif Unlock(); Tcl_DStringFree(&ds); return ret; } /* *------------------------------------------------------------------------- * * Zip_FSFileAttrStringsProc -- * * This function implements the ZIP filesystem dependent 'file attributes' * subcommand, for listing the set of possible attribute strings. * * Results: * An array of strings * * Side effects: * None. * *------------------------------------------------------------------------- */ static const char *const * Zip_FSFileAttrStringsProc(Tcl_Obj *pathPtr, Tcl_Obj** objPtrRef) { static const char *const attrs[] = { "-uncompsize", "-compsize", "-offset", "-mount", "-archive", "-permissions", NULL, }; return attrs; } /* *------------------------------------------------------------------------- * * Zip_FSFileAttrsGetProc -- * * This function implements the ZIP filesystem specific * 'file attributes' subcommand, for 'get' operations. * * Results: * Standard Tcl return code. The object placed in objPtrRef (if TCL_OK * was returned) is likely to have a refCount of zero. Either way we must * either store it somewhere (e.g. the Tcl result), or Incr/Decr its * refCount to ensure it is properly freed. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Zip_FSFileAttrsGetProc(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) { int len, ret = TCL_OK; char *path; ZipEntry *z; path = Tcl_GetStringFromObj(pathPtr, &len); ReadLock(); z = ZipFSLookup(path); if (z == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("file not found", -1)); } ret = TCL_ERROR; goto done; } switch (index) { case 0: *objPtrRef = Tcl_NewIntObj(z->nbyte); goto done; case 1: *objPtrRef= Tcl_NewIntObj(z->nbytecompr); goto done; case 2: *objPtrRef= Tcl_NewLongObj(z->offset); goto done; case 3: *objPtrRef= Tcl_NewStringObj(z->zipfile->mntpt, -1); goto done; case 4: *objPtrRef= Tcl_NewStringObj(z->zipfile->name, -1); goto done; case 5: *objPtrRef= Tcl_NewStringObj("0555", -1); goto done; } if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unknown attribute", -1)); } ret = TCL_ERROR; done: Unlock(); return ret; } /* *------------------------------------------------------------------------- * * Zip_FSFileAttrsSetProc -- * * This function implements the ZIP filesystem specific * 'file attributes' subcommand, for 'set' operations. * * Results: * Standard Tcl return code. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Zip_FSFileAttrsSetProc(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unsupported operation", -1)); } return TCL_ERROR; } /* *------------------------------------------------------------------------- * * Zip_FSFilesystemPathTypeProc -- * * Results: * * Side effects: * *------------------------------------------------------------------------- */ static Tcl_Obj * Zip_FSFilesystemPathTypeProc(Tcl_Obj *pathPtr) { return Tcl_NewStringObj("zip", -1); } /* *------------------------------------------------------------------------- * * Zip_FSLoadFile -- * * This functions deals with loading native object code. If * the given path object refers to a file within the ZIP * filesystem, an approriate error code is returned to delegate * loading to the caller (by copying the file to temp store * and loading from there). As fallback when the file refers * to the ZIP file system but is not present, it is looked up * relative to the executable and loaded from there when available. * * Results: * TCL_OK on success, TCL_ERROR otherwise with error message left. * * Side effects: * Loads native code into the process address space. * *------------------------------------------------------------------------- */ static int Zip_FSLoadFile(Tcl_Interp *interp, Tcl_Obj *path, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags) { Tcl_FSLoadFileProc2 *loadFileProc; #ifdef ANDROID /* * Force loadFileProc to native implementation since the * package manager already extracted the shared libraries * from the APK at install time. */ loadFileProc = (Tcl_FSLoadFileProc2 *) tclNativeFilesystem.loadFileProc; if (loadFileProc != NULL) { return loadFileProc(interp, path, loadHandle, unloadProcPtr, flags); } Tcl_SetErrno(ENOENT); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } return TCL_ERROR; #else Tcl_Obj *altPath = NULL; int ret = TCL_ERROR; if (Tcl_FSAccess(path, R_OK) == 0) { #ifdef ZIPFS_MEMLOAD ret = MemLoad(path, loadHandle, unloadProcPtr, flags); if (ret == TCL_OK) { return ret; } #endif /* * EXDEV should trigger loading by copying to temp store. */ Tcl_SetErrno(EXDEV); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } return ret; } else { Tcl_Obj *objs[2] = { NULL, NULL }; objs[1] = TclPathPart(interp, path, TCL_PATH_DIRNAME); if ((objs[1] != NULL) && (Zip_FSAccessProc(objs[1], R_OK) == 0)) { const char *execName = Tcl_GetNameOfExecutable(); /* * Shared object is not in ZIP but its path prefix is, * thus try to load from directory where the executable * came from. */ TclDecrRefCount(objs[1]); objs[1] = TclPathPart(interp, path, TCL_PATH_TAIL); /* * Get directory name of executable manually to deal * with cases where [file dirname [info nameofexecutable]] * is equal to [info nameofexecutable] due to VFS effects. */ if (execName != NULL) { const char *p = strrchr(execName, '/'); if (p > execName + 1) { --p; objs[0] = Tcl_NewStringObj(execName, p - execName); } } if (objs[0] == NULL) { objs[0] = TclPathPart(interp, TclGetObjNameOfExecutable(), TCL_PATH_DIRNAME); } if (objs[0] != NULL) { altPath = TclJoinPath(2, objs, 0); if (altPath != NULL) { Tcl_IncrRefCount(altPath); if (Tcl_FSAccess(altPath, R_OK) == 0) { path = altPath; } } } } if (objs[0] != NULL) { Tcl_DecrRefCount(objs[0]); } if (objs[1] != NULL) { Tcl_DecrRefCount(objs[1]); } } loadFileProc = (Tcl_FSLoadFileProc2 *) tclNativeFilesystem.loadFileProc; if (loadFileProc != NULL) { ret = loadFileProc(interp, path, loadHandle, unloadProcPtr, flags); } else { Tcl_SetErrno(ENOENT); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } } if (altPath != NULL) { Tcl_DecrRefCount(altPath); } return ret; #endif } /* * Define the ZIP filesystem dispatch table. */ MODULE_SCOPE const Tcl_Filesystem zipfsFilesystem; const Tcl_Filesystem zipfsFilesystem = { "zipfs", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_2, Zip_FSPathInFilesystemProc, NULL, /* dupInternalRepProc */ NULL, /* freeInternalRepProc */ NULL, /* internalToNormalizedProc */ NULL, /* createInternalRepProc */ Zip_FSNormalizePathProc, Zip_FSFilesystemPathTypeProc, Zip_FSFilesystemSeparatorProc, Zip_FSStatProc, Zip_FSAccessProc, Zip_FSOpenFileChannelProc, Zip_FSMatchInDirectoryProc, NULL, /* utimeProc */ NULL, /* linkProc */ Zip_FSListVolumesProc, Zip_FSFileAttrStringsProc, Zip_FSFileAttrsGetProc, Zip_FSFileAttrsSetProc, NULL, /* createDirectoryProc */ NULL, /* removeDirectoryProc */ NULL, /* deleteFileProc */ NULL, /* copyFileProc */ NULL, /* renameFileProc */ NULL, /* copyDirectoryProc */ NULL, /* lstatProc */ (Tcl_FSLoadFileProc *) Zip_FSLoadFile, NULL, /* getCwdProc */ Zip_FSChdirProc, }; #endif /* HAVE_ZLIB */ /* *------------------------------------------------------------------------- * * Zipfs_doInit -- * * Perform per interpreter initialization of this module. * * Results: * The return value is a standard Tcl result. * * Side effects: * Initializes this module if not already initialized, and adds * module related commands to the given interpreter. * *------------------------------------------------------------------------- */ static int Zipfs_doInit(Tcl_Interp *interp, int safe) { #ifdef HAVE_ZLIB static const EnsembleImplMap initMap[] = { {"mount", ZipFSMountObjCmd, NULL, NULL, NULL, 0}, {"unmount", ZipFSUnmountObjCmd, NULL, NULL, NULL, 0}, {"mkkey", ZipFSMkKeyObjCmd, NULL, NULL, NULL, 0}, {"mkimg", ZipFSMkImgObjCmd, NULL, NULL, NULL, 0}, {"mkzip", ZipFSMkZipObjCmd, NULL, NULL, NULL, 0}, {"lmkimg", ZipFSLMkImgObjCmd, NULL, NULL, NULL, 0}, {"lmkzip", ZipFSLMkZipObjCmd, NULL, NULL, NULL, 0}, {"exists", ZipFSExistsObjCmd, NULL, NULL, NULL, 0}, {"info", ZipFSInfoObjCmd, NULL, NULL, NULL, 0}, {"list", ZipFSListObjCmd, NULL, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; static const EnsembleImplMap initSafeMap[] = { {"exists", ZipFSExistsObjCmd, NULL, NULL, NULL, 0}, {"info", ZipFSInfoObjCmd, NULL, NULL, NULL, 0}, {"list", ZipFSListObjCmd, NULL, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; static const char findproc[] = "proc ::zipfs::find dir {\n" " set result {}\n" " if {[catch {glob -directory $dir -tails -nocomplain * .*} list]} {\n" " return $result\n" " }\n" " foreach file $list {\n" " if {$file eq \".\" || $file eq \"..\"} {\n" " continue\n" " }\n" " set file [file join $dir $file]\n" " lappend result $file\n" " foreach file [::zipfs::find $file] {\n" " lappend result $file\n" " }\n" " }\n" " return [lsort $result]\n" "}\n"; /* one-time initialization */ WriteLock(); if (!ZipFS.initialized) { #ifdef TCL_THREADS static const Tcl_Time t = { 0, 0 }; /* * Inflate condition variable. */ Tcl_MutexLock(&ZipFSMutex); Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, &t); Tcl_MutexUnlock(&ZipFSMutex); #endif Tcl_FSRegister(NULL, &zipfsFilesystem); Tcl_InitHashTable(&ZipFS.zipHash, TCL_STRING_KEYS); Tcl_InitHashTable(&ZipFS.fileHash, TCL_STRING_KEYS); Tcl_InitHashTable(&ZipFS.dirHash, TCL_ONE_WORD_KEYS); ZipFS.initialized = ZipFS.idCount = 1; #if defined(ZIPFS_IN_TCL) || defined(ZIPFS_IN_TK) if (interp != NULL) { Tcl_StaticPackage(interp, "zipfs", Zipfs_Init, Zipfs_SafeInit); } #endif } Unlock(); if (interp != NULL) { if (!safe) { Tcl_CreateObjCommand(interp, "::zipfs::mount", ZipFSMountObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::unmount", ZipFSUnmountObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::mkkey", ZipFSMkKeyObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::mkimg", ZipFSMkImgObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::mkzip", ZipFSMkZipObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::lmkimg", ZipFSLMkImgObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::lmkzip", ZipFSLMkZipObjCmd, 0, 0); Tcl_EvalEx(interp, findproc, -1, TCL_EVAL_GLOBAL); } Tcl_CreateObjCommand(interp, "::zipfs::exists", ZipFSExistsObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::info", ZipFSInfoObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "::zipfs::list", ZipFSListObjCmd, 0, 0); if (!safe) { Tcl_LinkVar(interp, "::zipfs::wrmax", (char *) &ZipFS.wrmax, TCL_LINK_INT); } TclMakeEnsemble(interp, "zipfs", safe ? initSafeMap : initMap); Tcl_PkgProvide(interp, "zipfs", "1.0"); } return TCL_OK; #else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("no zlib available", -1)); } return TCL_ERROR; #endif } /* *------------------------------------------------------------------------- * * Zipfs_Init, Zipfs_SafeInit -- * * These functions are invoked to perform per interpreter initialization * of this module. * * Results: * The return value is a standard Tcl result. * * Side effects: * Initializes this module if not already initialized, and adds * module related commands to the given interpreter. * *------------------------------------------------------------------------- */ int Zipfs_Init(Tcl_Interp *interp) { return Zipfs_doInit(interp, 0); } int Zipfs_SafeInit(Tcl_Interp *interp) { return Zipfs_doInit(interp, 1); } #ifndef HAVE_ZLIB /* *------------------------------------------------------------------------- * * Zipfs_Mount, Zipfs_Unmount -- * * Dummy version when no ZLIB support available. * *------------------------------------------------------------------------- */ int Zipfs_Mount(Tcl_Interp *interp, const char *zipname, const char *mntpt, const char *passwd) { return Zipfs_doInit(interp, 1); } int Zipfs_Unmount(Tcl_Interp *interp, const char *zipname) { return Zipfs_doInit(interp, 1); } #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */