1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-20 19:50:24 +02:00

popen: Add 'pipeline' procedure.

* libguile/posix.c (scm_open_process): Remove.
(scm_piped_process): Add to replace open_process.
* module/ice-9/popen.scm (pipe->fdes): Add to convert pipe pair to fdes pair.
(open-process): Add open-process for backwards compatibility.
(pipeline): Add to implement a pipeline using piped-process.
* doc/ref/posix.texi (Pipes): Document it.
* test-suite/tests/popen.test ("open-process")
("piped-process", "piped-process: with output")
("pipeline"): New tests.

Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Rutger van Beusekom 2020-03-02 10:38:57 +01:00 committed by Ludovic Courtès
parent 2c07a32ad8
commit 786fbcd327
4 changed files with 127 additions and 51 deletions

View file

@ -2370,6 +2370,34 @@ processes, and a system-wide limit on the number of pipes, so pipes
should be closed explicitly when no longer needed, rather than letting should be closed explicitly when no longer needed, rather than letting
the garbage collector pick them up at some later time. the garbage collector pick them up at some later time.
@findex pipeline
@deffn {Scheme Procedure} pipeline commands
Execute a @code{pipeline} of @var{commands} --- where each command is a
list of a program and its arguments as strings --- returning an input
port to the end of the pipeline, an output port to the beginning of the
pipeline and a list of PIDs of the processes executing the @var{commands}.
@example
(let ((commands '(("git" "ls-files")
("tar" "-cf-" "-T-")
("sha1sum" "-")))
(pipe-fail? (lambda (pid)
(not
(zero?
(status:exit-val
(cdr
(waitpid pid))))))))
(receive (from to pids) (pipeline commands)
(let* ((sha1 (read-delimited " " from))
(index (list-index pipe-fail? (reverse pids))))
(close to)
(close from)
(if (not index) sha1
(string-append "pipeline failed in command: "
(string-join (list-ref commands index)))))))
@result{} "52f99d234503fca8c84ef94b1005a3a28d8b3bc1"
@end example
@end deffn
@node Networking @node Networking
@subsection Networking @subsection Networking

View file

@ -1372,10 +1372,9 @@ start_child (const char *exec_file, char **exec_argv,
#ifdef HAVE_START_CHILD #ifdef HAVE_START_CHILD
static SCM static SCM
scm_open_process (SCM mode, SCM prog, SCM args) scm_piped_process (SCM prog, SCM args, SCM from, SCM to)
#define FUNC_NAME "open-process" #define FUNC_NAME "piped-process"
{ {
long mode_bits;
int reading, writing; int reading, writing;
int c2p[2]; /* Child to parent. */ int c2p[2]; /* Child to parent. */
int p2c[2]; /* Parent to child. */ int p2c[2]; /* Parent to child. */
@ -1383,41 +1382,24 @@ scm_open_process (SCM mode, SCM prog, SCM args)
int pid; int pid;
char *exec_file; char *exec_file;
char **exec_argv; char **exec_argv;
SCM read_port = SCM_BOOL_F, write_port = SCM_BOOL_F;
exec_file = scm_to_locale_string (prog); exec_file = scm_to_locale_string (prog);
exec_argv = scm_i_allocate_string_pointers (scm_cons (prog, args)); exec_argv = scm_i_allocate_string_pointers (scm_cons (prog, args));
mode_bits = scm_i_mode_bits (mode); reading = scm_is_pair (from);
reading = mode_bits & SCM_RDNG; writing = scm_is_pair (to);
writing = mode_bits & SCM_WRTNG;
if (reading) if (reading)
{ {
if (pipe (c2p)) c2p[0] = scm_to_int (scm_car (from));
{ c2p[1] = scm_to_int (scm_cdr (from));
int errno_save = errno;
free (exec_file);
errno = errno_save;
SCM_SYSERROR;
}
out = c2p[1]; out = c2p[1];
} }
if (writing) if (writing)
{ {
if (pipe (p2c)) p2c[0] = scm_to_int (scm_car (to));
{ p2c[1] = scm_to_int (scm_cdr (to));
int errno_save = errno;
free (exec_file);
if (reading)
{
close (c2p[0]);
close (c2p[1]);
}
errno = errno_save;
SCM_SYSERROR;
}
in = p2c[0]; in = p2c[0];
} }
@ -1453,23 +1435,12 @@ scm_open_process (SCM mode, SCM prog, SCM args)
SCM_SYSERROR; SCM_SYSERROR;
} }
/* There is no sense in catching errors on close(). */
if (reading) if (reading)
{
close (c2p[1]); close (c2p[1]);
read_port = scm_i_fdes_to_port (c2p[0], scm_mode_bits ("r0"),
sym_read_pipe,
SCM_FPORT_OPTION_NOT_SEEKABLE);
}
if (writing) if (writing)
{
close (p2c[0]); close (p2c[0]);
write_port = scm_i_fdes_to_port (p2c[1], scm_mode_bits ("w0"),
sym_write_pipe,
SCM_FPORT_OPTION_NOT_SEEKABLE);
}
return scm_values_3 (read_port, write_port, scm_from_int (pid)); return scm_from_int (pid);
} }
#undef FUNC_NAME #undef FUNC_NAME
@ -1514,8 +1485,8 @@ SCM_DEFINE (scm_system_star, "system*", 0, 0, 1,
"Example: (system* \"echo\" \"foo\" \"bar\")") "Example: (system* \"echo\" \"foo\" \"bar\")")
#define FUNC_NAME s_scm_system_star #define FUNC_NAME s_scm_system_star
{ {
SCM prog, res; SCM prog, pid;
int pid, status, wait_result; int status, wait_result;
if (scm_is_null (args)) if (scm_is_null (args))
SCM_WRONG_NUM_ARGS (); SCM_WRONG_NUM_ARGS ();
@ -1533,9 +1504,8 @@ SCM_DEFINE (scm_system_star, "system*", 0, 0, 1,
SCM_UNDEFINED); SCM_UNDEFINED);
#endif #endif
res = scm_open_process (scm_nullstr, prog, args); pid = scm_piped_process (prog, args, SCM_UNDEFINED, SCM_UNDEFINED);
pid = scm_to_int (scm_c_value_ref (res, 2)); SCM_SYSCALL (wait_result = waitpid (scm_to_int (pid), &status, 0));
SCM_SYSCALL (wait_result = waitpid (pid, &status, 0));
if (wait_result == -1) if (wait_result == -1)
SCM_SYSERROR; SCM_SYSERROR;
@ -2382,7 +2352,7 @@ SCM_DEFINE (scm_gethostname, "gethostname", 0, 0, 0,
static void static void
scm_init_popen (void) scm_init_popen (void)
{ {
scm_c_define_gsubr ("open-process", 2, 0, 1, scm_open_process); scm_c_define_gsubr ("piped-process", 2, 2, 0, scm_piped_process);
} }
#endif /* HAVE_START_CHILD */ #endif /* HAVE_START_CHILD */

View file

@ -22,9 +22,10 @@
#:use-module (rnrs bytevectors) #:use-module (rnrs bytevectors)
#:use-module (ice-9 binary-ports) #:use-module (ice-9 binary-ports)
#:use-module (ice-9 threads) #:use-module (ice-9 threads)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9) #:use-module (srfi srfi-9)
#:export (port/pid-table open-pipe* open-pipe close-pipe open-input-pipe #:export (port/pid-table open-pipe* open-pipe close-pipe open-input-pipe
open-output-pipe open-input-output-pipe)) open-output-pipe open-input-output-pipe pipeline))
(eval-when (expand load eval) (eval-when (expand load eval)
(load-extension (string-append "libguile-" (effective-version)) (load-extension (string-append "libguile-" (effective-version))
@ -84,6 +85,28 @@
(define port/pid-table (make-weak-key-hash-table)) (define port/pid-table (make-weak-key-hash-table))
(define port/pid-table-mutex (make-mutex)) (define port/pid-table-mutex (make-mutex))
(define (pipe->fdes)
(let ((p (pipe)))
(cons (port->fdes (car p))
(port->fdes (cdr p)))))
(define (open-process mode command . args)
"Backwards compatible implementation of the former procedure in
libguile/posix.c (scm_open_process) replaced by
scm_piped_process. Executes the program @var{command} with optional
arguments @var{args} (all strings) in a subprocess. A port to the
process (based on pipes) is created and returned. @var{mode} specifies
whether an input, an output or an input-output port to the process is
created: it should be the value of @code{OPEN_READ}, @code{OPEN_WRITE}
or @code{OPEN_BOTH}."
(let* ((from (and (or (string=? mode OPEN_READ)
(string=? mode OPEN_BOTH)) (pipe->fdes)))
(to (and (or (string=? mode OPEN_WRITE)
(string=? mode OPEN_BOTH)) (pipe->fdes)))
(pid (piped-process command args from to)))
(values (and from (fdes->inport (car from)))
(and to (fdes->outport (cdr to))) pid)))
(define (open-pipe* mode command . args) (define (open-pipe* mode command . args)
"Executes the program @var{command} with optional arguments "Executes the program @var{command} with optional arguments
@var{args} (all strings) in a subprocess. @var{args} (all strings) in a subprocess.
@ -176,3 +199,24 @@ information on how to interpret this value."
"Equivalent to @code{open-pipe} with mode @code{OPEN_BOTH}" "Equivalent to @code{open-pipe} with mode @code{OPEN_BOTH}"
(open-pipe command OPEN_BOTH)) (open-pipe command OPEN_BOTH))
(define (pipeline commands)
"Execute a pipeline of @var(commands) -- where each command is a list of a
program and its arguments as strings -- returning an input port to the
end of the pipeline, an output port to the beginning of the pipeline and
a list of PIDs of the processes executing the @var(commands)."
(let* ((to (pipe->fdes))
(pipes (map (lambda _ (pipe->fdes)) commands))
(pipeline (fold (lambda (from proc prev)
(let* ((to (car prev))
(pids (cdr prev))
(pid (piped-process (car proc)
(cdr proc)
from
to)))
(cons from (cons pid pids))))
`(,to)
pipes
commands))
(from (car pipeline))
(pids (cdr pipeline)))
(values (fdes->inport (car from)) (fdes->outport (cdr to)) pids)))

View file

@ -1,6 +1,6 @@
;;;; popen.test --- exercise ice-9/popen.scm -*- scheme -*- ;;;; popen.test --- exercise ice-9/popen.scm -*- scheme -*-
;;;; ;;;;
;;;; Copyright 2003, 2006, 2010, 2011, 2013, 2014 Free Software Foundation, Inc. ;;;; Copyright 2003, 2006, 2010, 2011, 2013, 2014, 2020 Free Software Foundation, Inc.
;;;; ;;;;
;;;; This library is free software; you can redistribute it and/or ;;;; This library is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU Lesser General Public ;;;; modify it under the terms of the GNU Lesser General Public
@ -17,7 +17,10 @@
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
(define-module (test-suite test-ice-9-popen) (define-module (test-suite test-ice-9-popen)
#:use-module (test-suite lib)) #:use-module (test-suite lib)
#:use-module (ice-9 receive)
#:use-module (ice-9 rdelim))
;; read from PORT until eof is reached, return what's read as a string ;; read from PORT until eof is reached, return what's read as a string
(define (read-string-to-eof port) (define (read-string-to-eof port)
@ -211,3 +214,34 @@ exec 2>~a; read REPLY"
(let ((st (close-pipe (open-output-pipe "exit 1")))) (let ((st (close-pipe (open-output-pipe "exit 1"))))
(and (status:exit-val st) (and (status:exit-val st)
(= 1 (status:exit-val st))))))) (= 1 (status:exit-val st)))))))
;;
;; pipeline related tests
;;
(pass-if "open-process"
(receive (from to pid)
((@@ (ice-9 popen) open-process) OPEN_BOTH "rev")
(display "dlrow olleh" to) (close to)
(and (equal? "hello world" (read-string from))
(= 0 (status:exit-val (cdr (waitpid pid)))))))
(pass-if "piped-process"
(= 42 (status:exit-val
(cdr (waitpid ((@@ (ice-9 popen) piped-process)
"./meta/guile" '("-c" "(exit 42)")))))))
(pass-if "piped-process: with output"
(let* ((p (pipe))
(pid ((@@ (ice-9 popen) piped-process) "echo" '("foo" "bar")
(cons (port->fdes (car p))
(port->fdes (cdr p))))))
(and (equal? "foo bar\n" (read-string (car p)))
(= 0 (status:exit-val (cdr (waitpid pid)))))))
(pass-if "pipeline"
(receive (from to pids)
(pipeline '(("echo" "dlrow olleh") ("rev")))
(and (string=? "hello world\n" (read-string from))
(equal? '(0 0) (map (compose status:exit-val cdr waitpid) pids)))))