/*
 * zipfs.c --
 *
 *	Implementation of the ZIP filesystem used in AndroWish.
 *
 * Copyright (c) 2013-2015 Christian Werner <chw@ch-werner.de>
 *
 * 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 <sys/mman.h>
#endif
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>
#include <fcntl.h>

#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 zip_read_int(p)         				\
    ((p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24))
#define zip_read_short(p)       				\
    ((p)[0] | ((p)[1] << 8))

#define zip_write_int(p, v)					\
    (p)[0] = (v) & 0xff; (p)[1] = ((v) >> 8) & 0xff;		\
    (p)[2] = ((v) >> 16) & 0xff; (p)[3] = ((v) >> 24) & 0xff;
#define zip_write_short(p, v)					\
    (p)[0] = (v) & 0xff; (p)[1] = ((v) >> 8) & 0xff;

/*
 * 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 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 */
    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 */
    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 */
} ZipEntry;

/*
 * 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 */
    int iscompr;              /* True if data is compressed */
    int isdir;	              /* Set to 1 if directory */
    int isenc;                /* True if data is encrypted */
    int iswr;                 /* True if open for writing */
    unsigned long keys[3];    /* Key for decryption */
} ZipChannel;

/*
 * Global variables.
 *
 * Most are kept in single ZipFS struct. When build with threading
 * support this struct is protected by the ZipFSMutex (see below).
 *
 * The "fileHash" component is the process wide global table of all known
 * ZIP archive members in all mounted ZIP archives.
 *
 * The "zipHash" components is the process wide global table of all mounted
 * ZIP archive files.
 */

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 fileHash;	/* File name to ZipEntry mapping */
    Tcl_HashTable zipHash;	/* Mount to ZipFile mapping */
} 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,
};

/*
 *-------------------------------------------------------------------------
 *
 * ReadLock, WriteLock, Unlock --
 *
 *	POSIX like rwlock functions to support multiple readers
 *	and single writer on internal structs.
 *
 *-------------------------------------------------------------------------
 */

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_year = (((dosDate & 0xfe00) >> 9) + 80);
    tm.tm_mon = ((dosDate & 0x1e0) >> 5) - 1;
    tm.tm_mday = dosDate & 0x1f;
    tm.tm_hour = (dosTime & 0xf800) >> 11;
    tm.tm_min = (dosTime & 0x7e) >> 5;
    tm.tm_sec = (dosTime & 0x1f) << 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;
}

/*
 *-------------------------------------------------------------------------
 *
 * 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 != 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);
    while (hPtr != NULL) {
	if ((zf = (ZipFile *) Tcl_GetHashValue(hPtr)) != NULL) {
#if HAS_DRIVES
	    if (drive != zf->mntdrv) {
		hPtr = Tcl_NextHashEntry(&search);
		continue;
	    }
#endif
	    if (strcmp(zf->mntpt, realname) == 0) {
		match = 1;
		break;
	    }
	}
	hPtr = Tcl_NextHashEntry(&search);
    }
    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)) {
	UnmapViewOfFile(zf->data);
	zf->data = NULL;
    }
    if (zf->mh != INVALID_HANDLE_VALUE) {
	CloseHandle(zf->mh);
    }
#else
    if ((zf->data != MAP_FAILED) && (zf->tofree == NULL)) {
	munmap(zf->data, zf->length);
	zf->data = MAP_FAILED;
    }
#endif
    if (zf->tofree != NULL) {
	Tcl_Free((char *) zf->tofree);
	zf->tofree = NULL;
    }
    Tcl_Close(interp, zf->chan);
    zf->chan = NULL;
}

/*
 *-------------------------------------------------------------------------
 *
 * ZipFSOpenArchive --
 *
 *	This function opens a ZIP archive file for reading. An attempt
 *	is made to memory map that file. Otherwise it is read into
 *	an allocated memory buffer. The ZIP archive header is verified
 *	and 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,
 *	the given ZipFile struct is filled with information about
 *	the ZIP archive file.
 *
 *-------------------------------------------------------------------------
 */

static int
ZipFSOpenArchive(Tcl_Interp *interp, const char *zipname, int needZip,
		 ZipFile *zf)
{
    int i;
    ClientData handle;
    unsigned char *p, *q;

#if defined(_WIN32) || defined(_WIN64)
    zf->data = NULL;
    zf->mh = INVALID_HANDLE_VALUE;
#else
    zf->data = MAP_FAILED;
#endif
    zf->length = 0;
    zf->nfiles = 0;
    zf->baseoffs = zf->baseoffsp = 0;
    zf->tofree = NULL;
    zf->pwbuf[0] = 0;
    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;
	}
#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;
	}
#endif
    }
    p = zf->data + zf->length - ZIP_CENTRAL_END_LEN;
    while (p >= zf->data) {
	if (*p == (ZIP_CENTRAL_END_SIG & 0xFF)) {
	    if (zip_read_int(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 = zip_read_short(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 + zip_read_int(p + ZIP_CENTRAL_DIRSTART_OFFS);
    p -= zip_read_int(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 (zip_read_int(q) != ZIP_CENTRAL_HEADER_SIG) {
	    if (interp != NULL) {
		Tcl_SetObjResult(interp,
		    Tcl_NewStringObj("wrong header signature", -1));
	    }
	    goto error;
	}
	pathlen = zip_read_short(q + ZIP_CENTRAL_PATHLEN_OFFS);
	comlen = zip_read_short(q + ZIP_CENTRAL_FCOMMENTLEN_OFFS);
	extra = zip_read_short(q + ZIP_CENTRAL_EXTRALEN_OFFS);
	q += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN;
    }
    q = zf->data + zf->baseoffs;
    if ((zf->baseoffs >= 6) &&
	(zip_read_int(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;
}

/*
 *-------------------------------------------------------------------------
 *
 * 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)
{
    char *realname, *p;
    int i, pwlen, isNew;
    ZipFile *zf, zf0;
    ZipEntry *z;
    Tcl_HashEntry *hPtr;
    Tcl_DString ds, fpBuf;
    unsigned char *q;
#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) {
	Tcl_HashSearch search;
	int ret = TCL_OK;

	i = 0;
	hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search);
	while (hPtr != NULL) {
	    if ((zf = (ZipFile *) Tcl_GetHashValue(hPtr)) != NULL) {
		if (interp != NULL) {
		    Tcl_AppendElement(interp, zf->mntpt);
		    Tcl_AppendElement(interp, zf->name);
		}
		++i;
	    }
	    hPtr = Tcl_NextHashEntry(&search);
	}
	if (interp == NULL) {
	    ret = (i > 0) ? TCL_OK : TCL_BREAK;
	}
	Unlock();
	return ret;
    }
    if (mntpt == NULL) {
	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) {
	    if ((zf = Tcl_GetHashValue(hPtr)) != NULL) {
#if HAS_DRIVES
		if (drive == zf->mntdrv)
#endif
		{
		    Tcl_SetObjResult(interp, Tcl_NewStringObj(zf->mntpt, -1));
		}
	    }
	}
	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;
    }
    Tcl_DStringInit(&ds);
#if HAS_DRIVES
    realname = AbsolutePath(zipname, NULL, &ds);
#else
    realname = AbsolutePath(zipname, &ds);
#endif
    WriteLock();
    hPtr = Tcl_CreateHashEntry(&ZipFS.zipHash, realname, &isNew);
    Tcl_DStringSetLength(&ds, 0);
    if (!isNew) {
	zf = (ZipFile *) Tcl_GetHashValue(hPtr);
	if (interp != NULL) {
	    Tcl_AppendResult(interp, "already mounted on \"", zf->mntptlen ?
			     zf->mntpt : "/", "\"", (char *) NULL);
	}
	Unlock();
	Tcl_DStringFree(&ds);
	ZipFSCloseArchive(interp, &zf0);
	return TCL_ERROR;
    }
#if HAS_DRIVES
    if ((mntpt[0] != '\0') && (mntpt[1] == ':') &&
	(strchr(drvletters, mntpt[0]) != NULL)) {
	drive = mntpt[0];
	if ((drive >= 'a') && (drive <= 'z')) {
	    drive -= 'a' - 'A';
	}
	mntpt += 2;
    }
#endif
    if (strcmp(mntpt, "/") == 0) {
	mntpt = "";
    }
    zf = (ZipFile *) Tcl_AttemptAlloc(sizeof (*zf) + strlen(mntpt) + 1);
    if (zf == NULL) {
	if (interp != NULL) {
	    Tcl_AppendResult(interp, "out of memory", (char *) NULL);
	}
	Unlock();
	Tcl_DStringFree(&ds);
	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 ((zf->pwbuf[0] == 0) && pwlen) {
	int k = 0;

	i = pwlen;
	zf->pwbuf[k++] = i;
	while (i > 0) {
	    zf->pwbuf[k] = (passwd[i - 1] & 0x0f) |
		pwrot[(passwd[i - 1] >> 4) & 0x0f];
	    k++;
	    i--;
	}
	zf->pwbuf[k] = '\0';
    }
    if (mntpt[0] != '\0') {
	z = (ZipEntry *) Tcl_Alloc(sizeof (*z));
	z->name = NULL;
	z->tnext = NULL;
	z->depth = CountSlashes(mntpt);
	z->zipfile = zf;
	z->isdir = 1;
	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 */
	    Tcl_Free((char *) z);
	} else {
	    Tcl_SetHashValue(hPtr, (ClientData) z);
	    z->name = Tcl_GetHashKey(&ZipFS.fileHash, hPtr);
	    z->next = zf->entries;
	    zf->entries = z;
	}
    }
    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 = zip_read_short(q + ZIP_CENTRAL_PATHLEN_OFFS);
	comlen = zip_read_short(q + ZIP_CENTRAL_FCOMMENTLEN_OFFS);
	extra = zip_read_short(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 +
	     zip_read_int(q + ZIP_CENTRAL_LOCALHDR_OFFS);
	if ((lq < zf->data) || (lq > (zf->data + zf->length))) {
	    goto nextent;
	}
	nbcompr = zip_read_int(lq + ZIP_LOCAL_COMPLEN_OFFS);
	if (!isdir && (nbcompr == 0) &&
	    (zip_read_int(lq + ZIP_LOCAL_UNCOMPLEN_OFFS) == 0) &&
	    (zip_read_int(lq + ZIP_LOCAL_CRC32_OFFS) == 0)) {
	    gq = q;
	    nbcompr = zip_read_int(gq + ZIP_CENTRAL_COMPLEN_OFFS);
	}
	offs = (lq - zf->data)
	     + ZIP_LOCAL_HEADER_LEN
	     + zip_read_short(lq + ZIP_LOCAL_PATHLEN_OFFS)
	     + zip_read_short(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(sizeof (*z));
	z->name = NULL;
	z->tnext = NULL;
	z->depth = CountSlashes(fullpath);
	z->zipfile = zf;
	z->isdir = isdir;
	z->isenc = (zip_read_short(lq + ZIP_LOCAL_FLAGS_OFFS) & 1)
	    && (nbcompr > 12);
	z->offset = offs;
	if (gq != NULL) {
	    z->crc32 = zip_read_int(gq + ZIP_CENTRAL_CRC32_OFFS);
	    dosDate = zip_read_short(gq + ZIP_CENTRAL_MDATE_OFFS);
	    dosTime = zip_read_short(gq + ZIP_CENTRAL_MTIME_OFFS);
	    z->timestamp = DosTimeDate(dosDate, dosTime);
	    z->nbyte = zip_read_int(gq + ZIP_CENTRAL_UNCOMPLEN_OFFS);
	    z->cmeth = zip_read_short(gq + ZIP_CENTRAL_COMPMETH_OFFS);
	} else {
	    z->crc32 = zip_read_int(lq + ZIP_LOCAL_CRC32_OFFS);
	    dosDate = zip_read_short(lq + ZIP_LOCAL_MDATE_OFFS);
	    dosTime = zip_read_short(lq + ZIP_LOCAL_MTIME_OFFS);
	    z->timestamp = DosTimeDate(dosDate, dosTime);
	    z->nbyte = zip_read_int(lq + ZIP_LOCAL_UNCOMPLEN_OFFS);
	    z->cmeth = zip_read_short(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 (isdir && (mntpt[0] == '\0') && (z->depth == 1)) {
		z->tnext = zf->topents;
		zf->topents = z;
	    }
	    if (!z->isdir && (z->depth > 1)) {
		char *dir, *end;
		ZipEntry *zd;

		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) {
			break;
		    }
		    zd = (ZipEntry *) Tcl_Alloc(sizeof (*zd));
		    zd->name = NULL;
		    zd->tnext = NULL;
		    zd->depth = CountSlashes(dir);
		    zd->zipfile = zf;
		    zd->isdir = 1;
		    zd->isenc = 0;
		    zd->offset = z->offset;
		    zd->crc32 = 0;
		    zd->timestamp = z->timestamp;
		    zd->nbyte = zd->nbytecompr = 0;
		    zd->cmeth = ZIP_COMPMETH_STORED;
		    zd->data = NULL;
		    hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, dir, &isNew);
		    if (!isNew) {
			/* should not happen but skip it anyway */
			Tcl_Free((char *) zd);
		    } else {
			Tcl_SetHashValue(hPtr, (ClientData) zd);
			zd->name = Tcl_GetHashKey(&ZipFS.fileHash, hPtr);
			zd->next = zf->entries;
			zf->entries = zd;
			if ((mntpt[0] == '\0') && (zd->depth == 1)) {
			    zd->tnext = zf->topents;
			    zf->topents = zd;
			}
		    }
		    end = strrchr(dir, '/');
		}
	    }
	}
nextent:
	q += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN;
    }
    Tcl_DStringFree(&fpBuf);
    Tcl_DStringFree(&ds);
    Unlock();
    Tcl_FSMountsChanged(NULL);
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * 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;
    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) {
	/* don't report error */
	goto done;
    }
    zf = (ZipFile *) Tcl_GetHashValue(hPtr);
#if HAS_DRIVES
    if (drive != zf->mntdrv) {
	/* don't report error */
	goto done;
    }
#endif
    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);
	}
	if (z->data != NULL) {
	    Tcl_Free((char *) z->data);
	}
	Tcl_Free((char *) z);
    }
    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[])
{
    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 = 4 + ((len + pos[0]) & 3);
	zip_write_short(abuf, 0xffff);
	zip_write_short(abuf + 2, align - 4);
	zip_write_int(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_Eval(interp, "expr int(rand() * 256) % 256") != 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, 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, tmp);
	kvbuf[i++] = (unsigned char) zencode(keys, crc32tab, crc >> 24, 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], 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;
	} 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], 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 (*z));
    z->name = NULL;
    z->tnext = 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.
     */
    zip_write_int(buf + ZIP_LOCAL_SIG_OFFS, ZIP_LOCAL_HEADER_SIG);
    zip_write_short(buf + ZIP_LOCAL_VERSION_OFFS, ZIP_MIN_VERSION);
    zip_write_short(buf + ZIP_LOCAL_FLAGS_OFFS, z->isenc);
    zip_write_short(buf + ZIP_LOCAL_COMPMETH_OFFS, z->cmeth);
    zip_write_short(buf + ZIP_LOCAL_MTIME_OFFS, ToDosTime(z->timestamp));
    zip_write_short(buf + ZIP_LOCAL_MDATE_OFFS, ToDosDate(z->timestamp));
    zip_write_int(buf + ZIP_LOCAL_CRC32_OFFS, z->crc32);
    zip_write_int(buf + ZIP_LOCAL_COMPLEN_OFFS, z->nbytecompr);
    zip_write_int(buf + ZIP_LOCAL_UNCOMPLEN_OFFS, z->nbyte);
    zip_write_short(buf + ZIP_LOCAL_PATHLEN_OFFS, zpathlen);
    zip_write_short(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 (isImg) {
	ZipFile zf0;
	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 (ZipFSOpenArchive(interp, imgName, 0, &zf0) != TCL_OK) {
	    Tcl_DecrRefCount(list);
	    Tcl_Close(interp, out);
	    return TCL_ERROR;
	}
	if ((pw != NULL) && 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';
	}
	i = Tcl_Write(out, (char *) zf0.data, zf0.baseoffsp);
	if (i != zf0.baseoffsp) {
	    Tcl_DecrRefCount(list);
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("write error", -1));
	    Tcl_Close(interp, out);
	    ZipFSCloseArchive(interp, &zf0);
	    return TCL_ERROR;
	}
	ZipFSCloseArchive(interp, &zf0);
	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);
	zip_write_int(buf + ZIP_CENTRAL_SIG_OFFS, ZIP_CENTRAL_HEADER_SIG);
	zip_write_short(buf + ZIP_CENTRAL_VERSIONMADE_OFFS, ZIP_MIN_VERSION);
	zip_write_short(buf + ZIP_CENTRAL_VERSION_OFFS, ZIP_MIN_VERSION);
	zip_write_short(buf + ZIP_CENTRAL_FLAGS_OFFS, z->isenc ? 1 : 0);
	zip_write_short(buf + ZIP_CENTRAL_COMPMETH_OFFS, z->cmeth);
	zip_write_short(buf + ZIP_CENTRAL_MTIME_OFFS, ToDosTime(z->timestamp));
	zip_write_short(buf + ZIP_CENTRAL_MDATE_OFFS, ToDosDate(z->timestamp));
	zip_write_int(buf + ZIP_CENTRAL_CRC32_OFFS, z->crc32);
	zip_write_int(buf + ZIP_CENTRAL_COMPLEN_OFFS, z->nbytecompr);
	zip_write_int(buf + ZIP_CENTRAL_UNCOMPLEN_OFFS, z->nbyte);
	zip_write_short(buf + ZIP_CENTRAL_PATHLEN_OFFS, len);
	zip_write_short(buf + ZIP_CENTRAL_EXTRALEN_OFFS, 0);
	zip_write_short(buf + ZIP_CENTRAL_FCOMMENTLEN_OFFS, 0);
	zip_write_short(buf + ZIP_CENTRAL_DISKFILE_OFFS, 0);
	zip_write_short(buf + ZIP_CENTRAL_IATTR_OFFS, 0);
	zip_write_int(buf + ZIP_CENTRAL_EATTR_OFFS, 0);
	zip_write_int(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);
    zip_write_int(buf + ZIP_CENTRAL_END_SIG_OFFS, ZIP_CENTRAL_END_SIG);
    zip_write_short(buf + ZIP_CENTRAL_DISKNO_OFFS, 0);
    zip_write_short(buf + ZIP_CENTRAL_DISKDIR_OFFS, 0);
    zip_write_short(buf + ZIP_CENTRAL_ENTS_OFFS, count);
    zip_write_short(buf + ZIP_CENTRAL_TOTALENTS_OFFS, count);
    zip_write_int(buf + ZIP_CENTRAL_DIRSIZE_OFFS, pos[2] - pos[1]);
    zip_write_int(buf + ZIP_CENTRAL_DIRSTART_OFFS, pos[1] - pos[0]);
    zip_write_short(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_FirstHashEntry(&fileHash, &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;

    if (info->iscompr && (info->ubuf != NULL)) {
	Tcl_Free((char *) info->ubuf);
	info->ubuf = NULL;
    }
    if (info->isenc) {
	info->isenc = 0;
	memset(info->keys, 0, sizeof (info->keys));
    }
    if (info->iswr) {
	ZipEntry *z = info->zipentry;
	unsigned char *newdata;

	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;
	} else {
	    Tcl_Free((char *) info->ubuf);
	}
    }
    WriteLock();
    info->zipfile->nopen--;
    Unlock();
    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) {
	*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;
    }
    if (info->isenc) {
	int i, ch;

	for (i = 0; i < toRead; i++) {
	    ch = info->ubuf[i + info->nread];
	    buf[i] = zdecode(info->keys, crc32tab, ch);
	}
    } else {
	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;

    if (info->isdir) {
	*errloc = EINVAL;
	return -1;
    }
    switch (mode) {
    case SEEK_CUR:
	offset += info->nread;
	break;
    case SEEK_END:
	offset += info->nbyte;
	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 > info->nbyte) {
	*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)
{
    return;
}

/*
 *-------------------------------------------------------------------------
 *
 * 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;
}

/*
 * 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 */
    NULL,                   /* 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 */
    NULL,                   /* 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;
    char cname[128];

    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;
    }
    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 (*info));
    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;
    if (wr) {
	flags |= TCL_WRITABLE;
	info->iswr = 1;
	info->isdir = 0;
	info->nmax = ZipFS.wrmax;
	info->iscompr = 0;
	info->isenc = 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;
	}
	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];
		    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, info->keys, crc32tab);
		    memset(pwbuf, 0, sizeof (pwbuf));
		    for (i = 0; i < 12; i++) {
			ch = info->ubuf[i];
			zdecode(info->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) {
			    goto merror0;
			}
			for (j = 0; j < stream.avail_in; j++) {
			    ch = info->ubuf[j];
			    cbuf[j] = zdecode(info->keys, crc32tab, ch);
			}
			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) {
			    memset(info->keys, 0, sizeof (info->keys));
			    Tcl_Free((char *) cbuf);
			}
			goto wrapchan;
		    }
cerror0:
		    if (cbuf != NULL) {
			memset(info->keys, 0, sizeof (info->keys));
			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(info->keys, crc32tab, ch);
		    }
		} else {
		    memcpy(info->ubuf, zbuf, z->nbyte);
		}
		memset(info->keys, 0, sizeof (info->keys));
		goto wrapchan;
	    }
	}
    } else if (z->data != NULL) {
	flags |= TCL_READABLE;
	info->iswr = 0;
	info->iscompr = 0;
	info->isdir = 0;
	info->isenc = 0;
	info->nbyte = z->nbyte;
	info->nmax = 0;
	info->ubuf = z->data;
    } 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->isenc = z->isenc;
	info->nbyte = z->nbyte;
	info->nmax = 0;
	if (info->isenc) {
	    int len = z->zipfile->pwbuf[0];
	    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, info->keys, crc32tab);
	    memset(pwbuf, 0, sizeof (pwbuf));
	    for (i = 0; i < 12; i++) {
		ch = info->ubuf[i];
		zdecode(info->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 (info->isenc) {
		stream.avail_in -= 12;
		ubuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in);
		if (ubuf == NULL) {
		    info->ubuf = NULL;
		    goto merror;
		}
		for (j = 0; j < stream.avail_in; j++) {
		    ch = info->ubuf[j];
		    ubuf[j] = zdecode(info->keys, crc32tab, ch);
		}
		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) {
		    info->isenc = 0;
		    memset(info->keys, 0, sizeof (info->keys));
		    Tcl_Free((char *) ubuf);
		}
		Tcl_Free((char *) info);
		if (interp != NULL) {
		    Tcl_SetObjResult(interp,
			Tcl_NewStringObj("out of memory", -1));
		}
		goto error;
	    }
	    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) {
		    info->isenc = 0;
		    memset(info->keys, 0, sizeof (info->keys));
		    Tcl_Free((char *) ubuf);
		}
		goto wrapchan;
	    }
cerror:
	    if (ubuf != NULL) {
		info->isenc = 0;
		memset(info->keys, 0, sizeof (info->keys));
		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;
	}
    }
wrapchan:
    sprintf(cname, "zipfs_%lx_%d", (unsigned long) z->offset, ZipFS.idCount++);
    z->zipfile->nopen++;
    Unlock();
    return Tcl_CreateChannel(&ZipChannelType, cname, (ClientData) info, flags);

error:
    Unlock();
    return NULL;
}

/*
 *-------------------------------------------------------------------------
 *
 * 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, l, dirOnly = -1, prefixLen, strip = 0;
    char *pat, *prefix, *path;
#if HAS_DRIVES
    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 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);
#else
	Tcl_DStringAppend(&dsPref, "/", 1);
	prefixLen++;
	prefix = Tcl_DStringValue(&dsPref);
#endif
    }
    ReadLock();
    if ((types != NULL) && (types->type == TCL_GLOB_TYPE_MOUNT)) {
	l = CountSlashes(path);
	if (path[len - 1] == '/') {
	    len--;
	} else {
	    l++;
	}
	if ((pattern == NULL) || (pattern[0] == '\0')) {
	    pattern = "*";
	}
	hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search);
	while (hPtr != NULL) {
	    ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr);

	    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) == l) &&
			Tcl_StringCaseMatch(z->name + len + 1, pattern, 0)) {
			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));
			}
		    }
		    z = z->tnext;
		}
	    } else if ((zf->mntptlen > len + 1) &&
		       (strncmp(zf->mntpt, path, len) == 0) &&
		       (zf->mntpt[len] == '/') &&
		       (CountSlashes(zf->mntpt) == l) &&
		       Tcl_StringCaseMatch(zf->mntpt + len + 1, pattern, 0)) {
		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));
		}
	    }
	    hPtr = Tcl_NextHashEntry(&search);
	}
	goto end;
    }
    if ((pattern == NULL) || (pattern[0] == '\0')) {
	hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, path);
	if (hPtr != NULL) {
	    ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr);

	    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, -1));
		}
	    }
	}
	goto end;
    }
    l = strlen(pattern);
    pat = Tcl_Alloc(len + l + 2);
    memcpy(pat, path, len);
    while ((len > 1) && (pat[len - 1] == '/')) {
	--len;
    }
    if ((len > 1) || (pat[0] != '/')) {
	pat[len] = '/';
	++len;
    }
    memcpy(pat + len, pattern, l + 1);
    scnt = CountSlashes(pat);
    for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search);
	 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
	ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr);

	if ((dirOnly >= 0) &&
	    ((dirOnly && !z->isdir) || (!dirOnly && z->isdir))) {
	    continue;
	}
	if ((z->depth == scnt) && Tcl_StringCaseMatch(z->name, pat, 0)) {
	    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));
	    }
	}
    }
    Tcl_Free(pat);
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)) {
		    ret = TCL_OK;
		    goto endloop;
		}
		z = z->tnext;
	    }
	} else if ((len >= zf->mntptlen) &&
		   (strncmp(path, zf->mntpt, zf->mntptlen) == 0)) {
	    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);
#if HAS_DRIVES
	vol = Tcl_ObjPrintf("%c:%s", zf->mntdrv, zf->mntpt);
#else
	vol = Tcl_NewStringObj(zf->mntpt, zf->mntptlen);
#endif
	Tcl_ListObjAppendList(NULL, vols, vol);
	Tcl_DecrRefCount(vol);
	hPtr = Tcl_NextHashEntry(&search);
    }
    Unlock();
    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, -1 otherwise with error number set.
 *
 * 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 manger 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);
    return -1;
#else
    Tcl_Obj *altPath = NULL;
    int ret = -1;

    if (Tcl_FSAccess(path, R_OK) == 0) {
	/*
	 * EXDEV should trigger loading by copying to temp store.
	 */
	Tcl_SetErrno(EXDEV);
	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)) {
	    /*
	     * 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);
	    objs[0] = TclPathPart(interp, TclGetObjNameOfExecutable(),
				  TCL_PATH_DIRNAME);
	    if (objs[0] != NULL) {
		altPath = TclJoinPath(2, objs);
		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 (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 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.fileHash, TCL_STRING_KEYS);
	Tcl_InitHashTable(&ZipFS.zipHash, TCL_STRING_KEYS);
	ZipFS.initialized = ZipFS.idCount = 1;
#if defined(ZIPFS_IN_TCL) || defined(ZIPFS_IN_TK)
	Tcl_StaticPackage(interp, "zipfs", Zipfs_Init, Zipfs_SafeInit);
#endif
    }
    Unlock();
#if !defined(ZIPFS_IN_TCL) && !defined(ZIPFS_IN_TK)
    Tcl_PkgProvide(interp, "zipfs", "1.0");
#endif
    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_GlobalEval(interp, findproc);
    }
    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);
    }
    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:
 */
