203 lines
7.9 KiB
Scheme
203 lines
7.9 KiB
Scheme
;;; Gash --- Guile As SHell
|
||
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
|
||
;;; Copyright © 2013, 2014, 2015 Mark H Weaver <mhw@netris.org>
|
||
;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
|
||
;;; Copyright © 2014 Ian Denhardt <ian@zenhack.net>
|
||
;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
|
||
;;; Copyright © 2015 David Thompson <davet@gnu.org>
|
||
;;; Copyright © 2017 Efraim Flashner <efraim@flashner.co.il>
|
||
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
|
||
;;; Copyright © 2018 Marius Bakke <mbakke@fastmail.com>
|
||
;;; Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
|
||
;;;
|
||
;;; This file is part of Gash.
|
||
;;;
|
||
;;; Gash is free software; you can redistribute it and/or modify it
|
||
;;; under the terms of the GNU General Public License as published by
|
||
;;; the Free Software Foundation; either version 3 of the License, or (at
|
||
;;; your option) any later version.
|
||
;;;
|
||
;;; Gash is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
;;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||
;;; details.
|
||
;;;
|
||
;;; You should have received a copy of the GNU General Public License
|
||
;;; along with Gash. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
;;; Commentary:
|
||
|
||
;;; The initial guix-utils.scm was taken from Guix.
|
||
|
||
;;; Code:
|
||
|
||
(define-module (gash guix-utils)
|
||
#:use-module (srfi srfi-1)
|
||
#:use-module (srfi srfi-11)
|
||
#:use-module (ice-9 format)
|
||
#:autoload (ice-9 popen) (open-pipe*)
|
||
#:use-module ((gash shell-utils) #:select (dump-port))
|
||
#:use-module (ice-9 match)
|
||
#:use-module (gash config)
|
||
#:export (filtered-port
|
||
compressed-port
|
||
decompressed-port
|
||
call-with-decompressed-port
|
||
compressed-output-port
|
||
call-with-compressed-output-port))
|
||
|
||
|
||
;;;
|
||
;;; Filtering & pipes.
|
||
;;;
|
||
|
||
(define (filtered-port command input)
|
||
"Return an input port where data drained from INPUT is filtered through
|
||
COMMAND (a list). In addition, return a list of PIDs that the caller must
|
||
wait. When INPUT is a file port, it must be unbuffered; otherwise, any
|
||
buffered data is lost."
|
||
(let loop ((input input)
|
||
(pids '()))
|
||
(if (file-port? input)
|
||
(match (pipe)
|
||
((in . out)
|
||
(match (primitive-fork)
|
||
(0
|
||
(dynamic-wind
|
||
(const #f)
|
||
(lambda ()
|
||
(close-port in)
|
||
(close-port (current-input-port))
|
||
(dup2 (fileno input) 0)
|
||
(close-port (current-output-port))
|
||
(dup2 (fileno out) 1)
|
||
(catch 'system-error
|
||
(lambda ()
|
||
(apply execl (car command) command))
|
||
(lambda args
|
||
(format (current-error-port)
|
||
"filtered-port: failed to execute '~{~a ~}': ~a~%"
|
||
command (strerror (system-error-errno args))))))
|
||
(lambda ()
|
||
(primitive-_exit 1))))
|
||
(child
|
||
(close-port out)
|
||
(values in (cons child pids))))))
|
||
|
||
;; INPUT is not a file port, so fork just for the sake of tunneling it
|
||
;; through a file port.
|
||
(match (pipe)
|
||
((in . out)
|
||
(match (primitive-fork)
|
||
(0
|
||
(dynamic-wind
|
||
(const #t)
|
||
(lambda ()
|
||
(close-port in)
|
||
(dump-port input out))
|
||
(lambda ()
|
||
(close-port input)
|
||
(false-if-exception (close out))
|
||
(primitive-_exit 0))))
|
||
(child
|
||
(close-port input)
|
||
(close-port out)
|
||
(loop in (cons child pids)))))))))
|
||
|
||
(define (decompressed-port compression input)
|
||
"Return an input port where INPUT is decompressed according to COMPRESSION,
|
||
a symbol such as 'xz."
|
||
(match compression
|
||
((or #f 'none) (values input '()))
|
||
('bzip2 (filtered-port `(,%bzip2 "-dc") input))
|
||
('compress (filtered-port `(,%compress "-dc") input))
|
||
('xz (filtered-port `(,%xz "-dc" "-T0") input))
|
||
('gzip (filtered-port `(,%gzip "-dc") input))
|
||
(else (error "unsupported compression scheme" compression))))
|
||
|
||
(define (compressed-port compression input)
|
||
"Return an input port where INPUT is decompressed according to COMPRESSION,
|
||
a symbol such as 'xz."
|
||
(match compression
|
||
((or #f 'none) (values input '()))
|
||
('bzip2 (filtered-port `(,%bzip2 "-c") input))
|
||
('compress (filtered-port `(,%compress "-c") input))
|
||
('xz (filtered-port `(,%xz "-c" "-T0") input))
|
||
('gzip (filtered-port `(,%gzip "-c") input))
|
||
(else (error "unsupported compression scheme" compression))))
|
||
|
||
(define (call-with-decompressed-port compression port proc)
|
||
"Call PROC with a wrapper around PORT, a file port, that decompresses data
|
||
read from PORT according to COMPRESSION, a symbol such as 'xz."
|
||
(let-values (((decompressed pids)
|
||
(decompressed-port compression port)))
|
||
(dynamic-wind
|
||
(const #f)
|
||
(lambda ()
|
||
(proc decompressed))
|
||
(lambda ()
|
||
(close-port decompressed)
|
||
(unless (every (compose zero? cdr waitpid) pids)
|
||
(error "decompressed-port failure" pids))))))
|
||
|
||
(define (filtered-output-port command output)
|
||
"Return an output port. Data written to that port is filtered through
|
||
COMMAND and written to OUTPUT, an output file port. In addition, return a
|
||
list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
|
||
data is lost."
|
||
(match (pipe)
|
||
((in . out)
|
||
(match (primitive-fork)
|
||
(0
|
||
(dynamic-wind
|
||
(const #f)
|
||
(lambda ()
|
||
(close-port out)
|
||
(close-port (current-input-port))
|
||
(dup2 (fileno in) 0)
|
||
(close-port (current-output-port))
|
||
(dup2 (fileno output) 1)
|
||
(catch 'system-error
|
||
(lambda ()
|
||
(apply execl (car command) command))
|
||
(lambda args
|
||
(format (current-error-port)
|
||
"filtered-output-port: failed to execute '~{~a ~}': ~a~%"
|
||
command (strerror (system-error-errno args))))))
|
||
(lambda ()
|
||
(primitive-_exit 1))))
|
||
(child
|
||
(close-port in)
|
||
(values out (list child)))))))
|
||
|
||
(define* (compressed-output-port compression output
|
||
#:key (options '()))
|
||
"Return an output port whose input is compressed according to COMPRESSION,
|
||
a symbol such as 'xz, and then written to OUTPUT. In addition return a list
|
||
of PIDs to wait for. OPTIONS is a list of strings passed to the compression
|
||
program--e.g., '(\"--fast\")."
|
||
(match compression
|
||
((or #f 'none) (values output '()))
|
||
('bzip2 (filtered-output-port `(,%bzip2 "-c" ,@options) output))
|
||
('compress (filtered-output-port `(,%compress "-c" ,@options) output))
|
||
('xz (filtered-output-port `(,%xz "-c" "-T0" ,@options) output))
|
||
('gzip (filtered-output-port `(,%gzip "-c" ,@options) output))
|
||
(else (error "unsupported compression scheme" compression))))
|
||
|
||
(define* (call-with-compressed-output-port compression port proc
|
||
#:key (options '()))
|
||
"Call PROC with a wrapper around PORT, a file port, that compresses data
|
||
that goes to PORT according to COMPRESSION, a symbol such as 'xz. OPTIONS is
|
||
a list of command-line arguments passed to the compression program."
|
||
(let-values (((compressed pids)
|
||
(compressed-output-port compression port
|
||
#:options options)))
|
||
(dynamic-wind
|
||
(const #f)
|
||
(lambda ()
|
||
(proc compressed))
|
||
(lambda ()
|
||
(close-port compressed)
|
||
(unless (every (compose zero? cdr waitpid) pids)
|
||
(error "compressed-output-port failure" pids))))))
|