processes: Allow 'less' to properly estimate line length.
[jackhill/guix/guix.git] / guix / zlib.scm
index 51e5e9e..3bd0ad8 100644 (file)
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -92,7 +92,8 @@ closes FD."
   (let ((proc (zlib-procedure int "gzread" (list '* '* unsigned-int))))
     (lambda* (gzfile bv #:optional (start 0) (count (bytevector-length bv)))
       "Read up to COUNT bytes from GZFILE into BV at offset START.  Return the
-number of uncompressed bytes actually read."
+number of uncompressed bytes actually read; it is zero if COUNT is zero or if
+the end-of-stream has been reached."
       (let ((ret (proc (gzip-file->pointer gzfile)
                        (bytevector->pointer bv start)
                        count)))
@@ -148,38 +149,40 @@ the number of uncompressed bytes written, a strictly positive integer."
   ;; Z_DEFAULT_COMPRESSION.
   -1)
 
-(define (close-procedure gzfile port)
-  "Return a procedure that closes GZFILE, ensuring its underlying PORT is
-closed even if closing GZFILE triggers an exception."
-  (lambda ()
-    (catch 'zlib-error
-      (lambda ()
-        ;; 'gzclose' closes the underlying file descriptor.  'close-port'
-        ;; calls close(2), gets EBADF, which is ignores.
-        (gzclose gzfile)
-        (close-port port))
-      (lambda args
-        ;; Make sure PORT is closed despite the zlib error.
-        (close-port port)
-        (apply throw args)))))
-
 (define* (make-gzip-input-port port #:key (buffer-size %default-buffer-size))
   "Return an input port that decompresses data read from PORT, a file port.
 PORT is automatically closed when the resulting port is closed.  BUFFER-SIZE
 is the size in bytes of the internal buffer, 8 KiB by default; using a larger
-buffer increases decompression speed."
+buffer increases decompression speed.  An error is thrown if PORT contains
+buffered input, which would be lost (and is lost anyway)."
   (define gzfile
-    (gzdopen (fileno port) "r"))
+    (match (drain-input port)
+      (""                                         ;PORT's buffer is empty
+       ;; 'gzclose' will eventually close the file descriptor beneath PORT.
+       ;; 'close-port' on PORT would get EBADF if 'gzclose' already closed it,
+       ;; so that's no good; revealed ports are no good either because they
+       ;; leak (see <https://bugs.gnu.org/28784>); calling 'close-port' after
+       ;; 'gzclose' doesn't work either because it leads to a race condition
+       ;; (see <https://bugs.gnu.org/29335>).  So we dup and close PORT right
+       ;; away.
+       (gzdopen (dup (fileno port)) "r"))
+      (_
+       ;; This is unrecoverable but it's better than having the buffered input
+       ;; be lost, leading to unclear end-of-file or corrupt-data errors down
+       ;; the path.
+       (throw 'zlib-error 'make-gzip-input-port
+              "port contains buffered input" port))))
 
   (define (read! bv start count)
-    ;; XXX: Can 'gzread!' return zero even though we haven't reached the EOF?
     (gzread! gzfile bv start count))
 
   (unless (= buffer-size %default-buffer-size)
     (gzbuffer! gzfile buffer-size))
 
+  (close-port port)                               ;we no longer need it
   (make-custom-binary-input-port "gzip-input" read! #f #f
-                                 (close-procedure gzfile port)))
+                                 (lambda ()
+                                   (gzclose gzfile))))
 
 (define* (make-gzip-output-port port
                                 #:key
@@ -189,8 +192,10 @@ buffer increases decompression speed."
 a file port, as its sink.  PORT is automatically closed when the resulting
 port is closed."
   (define gzfile
-    (gzdopen (fileno port)
-             (string-append "w" (number->string level))))
+    (begin
+      (force-output port)                         ;empty PORT's buffer
+      (gzdopen (dup (fileno port))
+               (string-append "w" (number->string level)))))
 
   (define (write! bv start count)
     (gzwrite gzfile bv start count))
@@ -198,8 +203,10 @@ port is closed."
   (unless (= buffer-size %default-buffer-size)
     (gzbuffer! gzfile buffer-size))
 
+  (close-port port)
   (make-custom-binary-output-port "gzip-output" write! #f #f
-                                  (close-procedure gzfile port)))
+                                  (lambda ()
+                                    (gzclose gzfile))))
 
 (define* (call-with-gzip-input-port port proc
                                     #:key (buffer-size %default-buffer-size))