summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclIO.c3
-rw-r--r--generic/tclInt.h5
-rw-r--r--generic/tclZipfs.c139
-rw-r--r--tests/zipfs.test13
4 files changed, 98 insertions, 62 deletions
diff --git a/generic/tclIO.c b/generic/tclIO.c
index 0c91428..4dc2de2 100644
--- a/generic/tclIO.c
+++ b/generic/tclIO.c
@@ -698,8 +698,9 @@ TclFinalizeIOSubsystem(void)
FreeBinaryEncoding();
TclpFinalizeSockets();
TclpFinalizePipes();
+ TclZipfsFinalize();
}
-
+
/*
*----------------------------------------------------------------------
*
diff --git a/generic/tclInt.h b/generic/tclInt.h
index a003d25..5f84621 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -3598,8 +3598,9 @@ MODULE_SCOPE void * TclpThreadGetGlobalTSD(void *tsdKeyPtr);
MODULE_SCOPE void TclErrorStackResetIf(Tcl_Interp *interp,
const char *msg, Tcl_Size length);
/* Tip 430 */
-MODULE_SCOPE int TclZipfs_Init(Tcl_Interp *interp);
-MODULE_SCOPE int TclIsZipfsPath(const char *path);
+MODULE_SCOPE int TclZipfs_Init(Tcl_Interp *interp);
+MODULE_SCOPE int TclIsZipfsPath(const char *path);
+MODULE_SCOPE void TclZipfsFinalize(void);
/*
* Many parsing tasks need a common definition of whitespace.
diff --git a/generic/tclZipfs.c b/generic/tclZipfs.c
index 53eac94..5450784 100644
--- a/generic/tclZipfs.c
+++ b/generic/tclZipfs.c
@@ -16,7 +16,7 @@
* projects.
*
* Helpful docs:
- * https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT
+ * https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT
* https://libzip.org/specifications/appnote_iz.txt
*/
@@ -319,6 +319,7 @@ static int InitWritableChannel(Tcl_Interp *interp,
ZipChannel *info, ZipEntry *z, int trunc);
static int ListMountPoints(Tcl_Interp *interp);
static int ContainsMountPoint(const char *path, int pathLen);
+static void CleanupMount(ZipFile *zf);
static void SerializeCentralDirectoryEntry(
const unsigned char *start,
const unsigned char *end, unsigned char *buf,
@@ -364,10 +365,7 @@ static int ZipFSLoadFile(Tcl_Interp *interp, Tcl_Obj *path,
Tcl_FSUnloadFileProc **unloadProcPtr, int flags);
static int ZipMapArchive(Tcl_Interp *interp, ZipFile *zf,
void *handle);
-static void ZipfsExitHandler(void *clientData);
-static void ZipfsMountExitHandler(void *clientData);
static void ZipfsSetup(void);
-static void ZipfsFinalize(void);
static int ZipChannelClose(void *instanceData,
Tcl_Interp *interp, int flags);
static Tcl_DriverGetHandleProc ZipChannelGetFile;
@@ -1663,9 +1661,15 @@ ZipFSOpenArchive(
ZIPFS_POSIX_ERROR(interp, "file read error");
goto error;
}
- Tcl_Close(interp, zf->chan);
- zf->chan = NULL;
}
+ /*
+ * Close the Tcl channel. If the file was mapped, the mapping is
+ * unaffected. It is important to close the channel otherwise there is a
+ * potential chicken and egg issue at finalization time as the channels
+ * are closed before the file systems are dismounted.
+ */
+ Tcl_Close(interp, zf->chan);
+ zf->chan = NULL;
return ZipFSFindTOC(interp, needZip, zf);
/*
@@ -1885,7 +1889,6 @@ ZipFSCatalogFilesystem(
*/
zf->mountPoint = (char *) Tcl_GetHashKey(&ZipFS.zipHash, hPtr);
- Tcl_CreateExitHandler(ZipfsMountExitHandler, zf);
zf->mountPointLen = strlen(zf->mountPoint);
zf->nameLength = strlen(zipname);
@@ -2133,7 +2136,6 @@ ZipfsSetup(void)
Tcl_Alloc(strlen(ZIPFS_FALLBACK_ENCODING) + 1);
strcpy(ZipFS.fallbackEntryEncoding, ZIPFS_FALLBACK_ENCODING);
ZipFS.initialized = 1;
- Tcl_CreateExitHandler(ZipfsExitHandler, NULL);
}
/*
@@ -2187,6 +2189,42 @@ ListMountPoints(
}
/*
+ *------------------------------------------------------------------------
+ *
+ * CleanupMount --
+ *
+ * Releases all resources associated with a mounted archive. There
+ * must not be any open files in the archive.
+ *
+ * Caller MUST be holding WriteLock() before calling this function.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory associated with the mounted archive is deallocated.
+ *------------------------------------------------------------------------
+ */
+static void
+CleanupMount(ZipFile *zf) /* Mount point */
+{
+ ZipEntry *z, *znext;
+ Tcl_HashEntry *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) {
+ Tcl_Free(z->data);
+ }
+ Tcl_Free(z);
+ }
+ zf->entries = NULL;
+}
+
+/*
*-------------------------------------------------------------------------
*
* DescribeMounted --
@@ -2328,6 +2366,7 @@ TclZipfs_MountBuffer(
{
ZipFile *zf;
+ /* TODO - how come a *read* lock suffices for initialzing ? */
ReadLock();
if (!ZipFS.initialized) {
ZipfsSetup();
@@ -2418,7 +2457,6 @@ TclZipfs_Unmount(
const char *mountPoint) /* Mount point path. */
{
ZipFile *zf;
- ZipEntry *z, *znext;
Tcl_HashEntry *hPtr;
Tcl_DString dsm;
int ret = TCL_OK, unmounted = 0;
@@ -2456,19 +2494,9 @@ TclZipfs_Unmount(
* still cleaning things up.
*/
- 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) {
- Tcl_Free(z->data);
- }
- Tcl_Free(z);
- }
+ CleanupMount(zf);
ZipFSCloseArchive(interp, zf);
- Tcl_DeleteExitHandler(ZipfsMountExitHandler, zf);
+
Tcl_Free(zf);
unmounted = 1;
@@ -6171,53 +6199,46 @@ ZipfsAppHookFindTclInit(
}
#endif
-static void
-ZipfsExitHandler(
- TCL_UNUSED(void *)
-)
+void TclZipfsFinalize(void)
{
+ /*
+ * Finalization steps:
+ * For every mounted archive, if it no longer has any open handles
+ * clean up the mount and associated zip file entries.
+ * If there are no more mounted archives, clean up and free the
+ * ZipFS.fileHash and ZipFS.zipHash tables.
+ */
+ WriteLock();
+
Tcl_HashEntry *hPtr;
- Tcl_HashSearch search;
- if (ZipFS.initialized != -1) {
- hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search);
- if (hPtr == NULL) {
- ZipfsFinalize();
- } else {
- /* ZipFS.fallbackEntryEncoding was already freed by
- * ZipfsMountExitHandler
- */
+ Tcl_HashSearch zipSearch;
+ for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &zipSearch); hPtr;
+ hPtr = Tcl_NextHashEntry(&zipSearch)) {
+ ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr);
+ if (zf->numOpen == 0) {
+ Tcl_DeleteHashEntry(hPtr);
+ CleanupMount(zf);
+ ZipFSCloseArchive(NULL, zf);
+ Tcl_Free(zf);
}
}
-}
-
-static void
-ZipfsFinalize(void) {
- Tcl_FSUnregister(&zipfsFilesystem);
- Tcl_DeleteHashTable(&ZipFS.fileHash);
- Tcl_Free(ZipFS.fallbackEntryEncoding);
- ZipFS.initialized = -1;
-}
-
-static void
-ZipfsMountExitHandler(
- void *clientData)
-{
- Tcl_HashEntry *hPtr;
- Tcl_HashSearch search;
-
- ZipFile *zf = (ZipFile *) clientData;
-
- if (TCL_OK != TclZipfs_Unmount(NULL, zf->mountPoint)) {
- Tcl_Panic("tried to unmount busy filesystem");
- }
- hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search);
+ hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &zipSearch);
if (hPtr == NULL) {
- ZipfsFinalize();
+ hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &zipSearch);
+ if (hPtr == NULL) {
+ /* Both hash tables empty. Free all resources */
+ Tcl_FSUnregister(&zipfsFilesystem);
+ Tcl_DeleteHashTable(&ZipFS.fileHash);
+ Tcl_DeleteHashTable(&ZipFS.zipHash);
+ Tcl_Free(ZipFS.fallbackEntryEncoding);
+ ZipFS.initialized = 0;
+ }
}
+ Unlock();
}
-
+
/*
*-------------------------------------------------------------------------
*
diff --git a/tests/zipfs.test b/tests/zipfs.test
index 44b16cf..3c17a26 100644
--- a/tests/zipfs.test
+++ b/tests/zipfs.test
@@ -1508,6 +1508,19 @@ namespace eval test_ns_zipfs {
list [catch {read $fd} message] [close $fd] $message
close $fd
} -result {file size error (may be zip64)} -returnCodes error
+
+ test bug-8259d74a64 "Crash exiting with open files" -setup {
+ set path [zippath test.zip]
+ set script "zipfs mount $path /\n"
+ append script {open [zipfs root]test} \n
+ append script "exit\n"
+ } -body {
+ set fd [open |[info nameofexecutable] r+]
+ puts $fd $script
+ flush $fd
+ read $fd
+ close $fd
+ } -result ""
}