Subject: Fixed bug in pushed transforms with full internal buffers not writing out.

From: dkf <[email-masked]>
Date: Tue, 6 Sep 2016 10:44:45 +0000

---
 generic/tclZlib.c | 48 +++++++++++++++++++++++++++++++++---------------
 tests/zlib.test   | 23 +++++++++++++++++++++++
 2 files changed, 56 insertions(+), 15 deletions(-)

diff --git a/generic/tclZlib.c b/generic/tclZlib.c
index dac47cf..c9d7b88 100644
--- a/generic/tclZlib.c
+++ b/generic/tclZlib.c
@@ -2864,7 +2864,7 @@ ZlibTransformClose(
     Tcl_Interp *interp)
 {
     ZlibChannelData *cd = instanceData;
-    int e, result = TCL_OK;
+    int e, written, result = TCL_OK;
 
     /*
      * Delete the support timer.
@@ -2882,6 +2882,17 @@ ZlibTransformClose(
 	    cd->outStream.next_out = (Bytef *) cd->outBuffer;
 	    cd->outStream.avail_out = (unsigned) cd->outAllocated;
 	    e = deflate(&cd->outStream, Z_FINISH);
+	    written = cd->outAllocated - cd->outStream.avail_out;
+
+	    /*
+	     * Can't be sure that deflate() won't declare the buffer to be
+	     * full (with Z_BUF_ERROR) so handle that case.
+	     */
+
+	    if (e == Z_BUF_ERROR) {
+		e = Z_OK;
+		written = cd->outAllocated;
+	    }
 	    if (e != Z_OK && e != Z_STREAM_END) {
 		/* TODO: is this the right way to do errors on close? */
 		if (!TclInThreadExit()) {
@@ -2890,20 +2901,17 @@ ZlibTransformClose(
 		result = TCL_ERROR;
 		break;
 	    }
-	    if (cd->outStream.avail_out != (unsigned) cd->outAllocated) {
-		if (Tcl_WriteRaw(cd->parent, cd->outBuffer,
-			cd->outAllocated - cd->outStream.avail_out) < 0) {
-		    /* TODO: is this the right way to do errors on close?
-		     * Note: when close is called from FinalizeIOSubsystem
-		     * then interp may be NULL */
-		    if (!TclInThreadExit() && interp) {
-			Tcl_SetObjResult(interp, Tcl_ObjPrintf(
-				"error while finalizing file: %s",
-				Tcl_PosixError(interp)));
-		    }
-		    result = TCL_ERROR;
-		    break;
+	    if (written && Tcl_WriteRaw(cd->parent, cd->outBuffer, written) < 0) {
+		/* TODO: is this the right way to do errors on close?
+		 * Note: when close is called from FinalizeIOSubsystem then
+		 * interp may be NULL */
+		if (!TclInThreadExit() && interp) {
+		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+			    "error while finalizing file: %s",
+			    Tcl_PosixError(interp)));
 		}
+		result = TCL_ERROR;
+		break;
 	    }
 	} while (e != Z_STREAM_END);
 	(void) deflateEnd(&cd->outStream);
@@ -3084,7 +3092,17 @@ ZlibTransformOutput(
 	e = deflate(&cd->outStream, Z_NO_FLUSH);
 	produced = cd->outAllocated - cd->outStream.avail_out;
 
-	if (e == Z_OK && produced > 0) {
+	if ((e == Z_OK && produced > 0) || e == Z_BUF_ERROR) {
+	    /*
+	     * deflate() indicates that it is out of space by returning
+	     * Z_BUF_ERROR; in that case, we must write the whole buffer out
+	     * and retry to compress what is left.
+	     */
+
+	    if (e == Z_BUF_ERROR) {
+		produced = cd->outAllocated;
+		e = Z_OK;
+	    }
 	    if (Tcl_WriteRaw(cd->parent, cd->outBuffer, produced) < 0) {
 		*errorCodePtr = Tcl_GetErrno();
 		return -1;
diff --git a/tests/zlib.test b/tests/zlib.test
index 8a040d8..15dbb34 100644
--- a/tests/zlib.test
+++ b/tests/zlib.test
@@ -917,6 +917,29 @@ test zlib-12.1 {Tk Bug 9eb55debc5} -constraints zlib -setup {
 } -cleanup {
     $stream close
 } -result {12026 18000}
+test zlib-12.2 {Patrick Dunnigan's issue} -constraints zlib -setup {
+    set filesrc [makeFile {} test.input]
+    set filedst [makeFile {} test.output]
+    set f [open $filesrc "wb"]
+    for {set i 0} {$i < 10000} {incr i} {
+	puts -nonewline $f "x"
+    }
+    close $f
+} -body {
+    set fin [open $filesrc "rb"]
+    set fout [open $filedst "wb"]
+    set header [dict create filename "test.input" time 0]
+    try {
+	fcopy $fin [zlib push gzip $fout -header $header]
+    } finally {
+	close $fin
+	close $fout
+    }
+    file size $filedst
+} -cleanup {
+    removeFile $filesrc
+    removeFile $filedst
+} -result 4152
 
 ::tcltest::cleanupTests
 return
-- 
cgit v0.12