;;; 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.
;;;
(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)))
;; 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
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))
(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))