From 8676605ad2bdae3b99ef520b7eddb638237c6429 Mon Sep 17 00:00:00 2001 From: Roland Illig Date: Sat, 25 Sep 2004 21:21:09 +0000 Subject: [PATCH] * pipethrough.c: Added pipethrough, a function to execute child processes in a simple way. * pipethrough.h: The interface for the function. * pipethrough.3: The manpage (will be installed in maintainer-mode). * Makefile.am: Added the files to SRCS. --- src/ChangeLog | 8 + src/Makefile.am | 9 +- src/pipethrough.3 | 64 +++++++ src/pipethrough.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++ src/pipethrough.h | 31 ++++ 5 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/pipethrough.3 create mode 100644 src/pipethrough.c create mode 100644 src/pipethrough.h diff --git a/src/ChangeLog b/src/ChangeLog index 6e3c61746..c57306445 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,11 @@ +2004-09-25 Roland Illig + + * pipethrough.c: Added pipethrough, a function to execute child + processes in a simple way. + * pipethrough.h: The interface for the function. + * pipethrough.3: The manpage (will be installed in maintainer-mode). + * Makefile.am: Added the files to SRCS. + 2004-09-25 Roland Illig * view.c (display): Recognize "+\bo" as a list item in nroff mode. diff --git a/src/Makefile.am b/src/Makefile.am index 680f161d0..d1e96156b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -59,7 +59,12 @@ SRCS = achown.c achown.h background.c background.h boxes.c boxes.h \ slint.c subshell.c subshell.h textconf.c textconf.h \ tree.c tree.h treestore.c treestore.h tty.h user.c user.h \ util.c util.h utilunix.c view.c view.h vfsdummy.h widget.c \ - widget.h win.c win.h wtools.c wtools.h + widget.h win.c win.h wtools.c wtools.h \ + pipethrough.c pipethrough.h + +if MAINTAINER_MODE +man_MANS= pipethrough.3 +endif if CHARSET mc_SOURCES = $(SRCS) $(CHARSET_SRC) @@ -67,7 +72,7 @@ else mc_SOURCES = $(SRCS) endif -EXTRA_DIST = ChangeLog OChangeLog man2hlp.c $(CHARSET_SRC) +EXTRA_DIST = ChangeLog OChangeLog man2hlp.c $(CHARSET_SRC) $(man_MANS) install-exec-hook: $(MAKE) install_mcview diff --git a/src/pipethrough.3 b/src/pipethrough.3 new file mode 100644 index 000000000..5c0934069 --- /dev/null +++ b/src/pipethrough.3 @@ -0,0 +1,64 @@ +.\" $NetBSD: mdoc.template,v 1.5 2002/01/12 02:24:29 wiz Exp $ +.\" +.\" Copyright notice +.\" +.\" The uncommented requests are required for all man pages. +.\" The commented requests should be uncommented and used where appropriate. +.Dd September 24, 2004 +.Dt pipethrough 3 +.Os +.Sh NAME +pipethrough \- pipe a buffer through a child command and receive two +buffers containing the stdout and stderr output of the child process. +.Sh LIBRARY +none -- must be included directly. +.Sh SYNOPSIS +.Bd -literal +#include + +extern int pipethrough(const char *command, + const struct pipe_inbuffer *stdin_buf, + /*@out@*/ struct pipe_outbuffer *stdout_buf, + /*@out@*/ struct pipe_outbuffer *stderr_buf, + /*@out@*/ int *status); +.Ed +.Sh DESCRIPTION +The pipethrough function executes the \fIcommand\fR via the shell. The +command's input is the data provided in \fIstdin_buf\fR. The output buffers +\fIstdout_buf\fR and \fIstderr_buf\fR will be filled with the output of the +command. After usage they must be freed using pipe_outbuffer_finalize(3). +.Sh RETURN VALUES +On success, pipethrough returns 0. On failure it returns -1 and sets +\fIerrno\fR to the first error that occurred. Later errors are discarded +silently. +.Sh ENVIRONMENT +.Bl -tag -width Fl +.It SHELL +The shell invoked by pipethrough. Defaults to /bin/sh. +.El +.Pp +.Sh FILES +.Bl -tag -width Fl +.It /bin/sh +The default shell. +.El +.Pp +.Sh EXAMPLES +TODO +.Sh ERRORS +The various errors that can occur during execution of close(2), dup2(2), +read(2), write(2), execl(3). +.\" .Sh SEE ALSO +.\" Cross-references should be ordered by section (low to high), then in +.\" alphabetical order. +.Sh STANDARDS +pipethrough does not appear in any standard I know of. +.\" .Sh HISTORY +.Sh AUTHORS +Roland Illig +.Aq roland.illig@gmx.de . +.Sh BUGS +None known yet. If you find bugs, please report them to the authors. +.Sh SECURITY CONSIDERATIONS +The \fIcommand\fR must be a properly quoted shell command. That is, +don't include file names without quoting them. diff --git a/src/pipethrough.c b/src/pipethrough.c new file mode 100644 index 000000000..cab20a969 --- /dev/null +++ b/src/pipethrough.c @@ -0,0 +1,443 @@ +/* + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Roland Illig , 2004. +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#define PIPE_RD 0 +#define PIPE_WR 1 + +/* Internal data types *************************************************/ + +struct writer_buffer { + /*@temp@*/ const void *data; + size_t size; + size_t pos; + int fd; + int lasterror; +}; + +struct reader_buffer { + /*@null@*/ /*@only@*/ void *data; + size_t size; + size_t maxsize; + int fd; + int lasterror; +}; + +struct select_request { + int n; + fd_set readfds; + fd_set writefds; +}; + +/* Helper functions ****************************************************/ + +static int closeref(int *fd) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *fd; @*/ +{ + int result = close(*fd); + *fd = -1; + return result; +} + +static void propagate(int *dest, int src) + /*@modifies *dest; @*/ +{ + if (*dest == 0) { + *dest = src; + } +} + +/* reader_buffer -- implementation *************************************/ + +static void reader_buffer_init(/*@out@*/ struct reader_buffer *buf, int fd) + /*@modifies *buf; @*/ +{ + buf->data = NULL; + buf->size = 0; + buf->maxsize = 0; + buf->fd = fd; + buf->lasterror = 0; +} + +/* SPlint cannot describe realloc(3) correctly. */ +/*@-usereleased@*/ /*@-compdef@*/ /*@-branchstate@*/ /*@-compmempass@*/ +static ssize_t reader_buffer_read(struct reader_buffer *buf) + /*@globals internalState, errno; @*/ + /*@modifies internalState, errno, *buf; @*/ +{ + ssize_t res; + + if (buf->size == buf->maxsize) { + size_t newsize = buf->maxsize + 4096; + /*@only@*/ void *newdata; + if (buf->data == NULL) { + newdata = malloc(newsize); + } else { + newdata = realloc(buf->data, newsize); + } + if (newdata == NULL) { + return (ssize_t) -1; + } + buf->data = newdata; + newdata = NULL; + buf->maxsize = newsize; + } + res = read(buf->fd, &(((char *) buf->data)[buf->size]), + buf->maxsize - buf->size); + if (res == (ssize_t) -1) { + return res; + } + buf->size += (ssize_t) res; + return res; +} +/*@=usereleased@*/ /*@=compdef@*/ /*@=branchstate@*/ /*@=compmempass@*/ + +static void reader_buffer_handle(struct reader_buffer *buf) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *buf; @*/ +{ + ssize_t rd = reader_buffer_read(buf); + if (rd == (ssize_t) -1) { + propagate(&buf->lasterror, errno); + } + if (rd <= 0) { + if (closeref(&(buf->fd)) == -1) { + propagate(&buf->lasterror, errno); + } + } +} + +/*@-mustfreeonly@*/ +static void reader_buffer_finalize(/*@special@*/ struct reader_buffer *buf) + /*@modifies *buf; @*/ + /*@releases buf->data; @*/ +{ + if (buf->data != NULL) { + free(buf->data); + buf->data = NULL; + } + buf->size = 0; + buf->maxsize = 0; + buf->fd = -1; +} +/*@=mustfreeonly@*/ + +/* writer_buffer -- implementation *************************************/ + +static void writer_buffer_init(/*@out@*/ struct writer_buffer *buf, int fd, + const void *data, size_t size) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *buf; @*/ +{ + buf->fd = fd; + buf->data = data; + buf->size = size; + buf->pos = 0; + buf->lasterror = 0; + if (size == 0) { + if (closeref(&(buf->fd)) == -1) { + propagate(&buf->lasterror, errno); + } + } +} + +static void writer_buffer_handle(struct writer_buffer *buf) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *buf; @*/ +{ + typedef void (*my_sighandler_fn) (int); + my_sighandler_fn old_sigpipe = signal(SIGPIPE, SIG_IGN); + ssize_t wr = write(buf->fd, &(((const char *) buf->data)[buf->pos]), + buf->size - buf->pos); + if (wr == (ssize_t) -1) { + propagate(&buf->lasterror, errno); + } else { + buf->pos += wr; + } + if (wr == (ssize_t) -1 || buf->pos == buf->size) { + if (closeref(&(buf->fd)) == -1) { + propagate(&buf->lasterror, errno); + } + } + (void) signal(SIGPIPE, old_sigpipe); +} + +static void writer_buffer_finalize(/*@special@*/ struct writer_buffer *buf) + /*@modifies *buf; @*/ +{ + buf->data = NULL; + buf->size = 0; + buf->pos = 0; + buf->fd = -1; +} + +/* select_request -- implementation ************************************/ + +static void select_request_init(/*@out@*/ struct select_request *sr) + /*@modifies *sr; @*/ +{ + FD_ZERO(&(sr->readfds)); + FD_ZERO(&(sr->writefds)); + sr->n = 0; +} + +static void select_request_add_reader(struct select_request *sr, + const struct reader_buffer *buf) + /*@modifies *sr; @*/ +{ + if (buf->fd != -1) { + FD_SET(buf->fd, &(sr->readfds)); + if (buf->fd + 1 > sr->n) { + sr->n = buf->fd + 1; + } + } +} + +static void select_request_add_writer(struct select_request *sr, + const struct writer_buffer *buf) + /*@modifies *sr; @*/ +{ + if (buf->fd != -1) { + FD_SET(buf->fd, &(sr->writefds)); + if (buf->fd + 1 > sr->n) { + sr->n = buf->fd + 1; + } + } +} + +static void select_request_handle_writer(const struct select_request *sr, + struct writer_buffer *buf) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *buf; @*/ +{ + if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->writefds))) { + writer_buffer_handle(buf); + } +} + +static void select_request_handle_reader(const struct select_request *sr, + struct reader_buffer *buf) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, *buf; @*/ +{ + if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->readfds))) { + reader_buffer_handle(buf); + } +} + +/* pipethrough -- helper functions *************************************/ + +static void pipethrough_child(const char *command, + const int *stdin_pipe, + const int *stdout_pipe, + const int *stderr_pipe) + /*@globals internalState, fileSystem, errno, stderr; @*/ + /*@modifies internalState, fileSystem, errno, *stderr; @*/ +{ + const char *shell = getenv("SHELL"); + if (shell == NULL) { + shell = "/bin/sh"; + } + + if (close(STDIN_FILENO) == -1 + || dup2(stdin_pipe[PIPE_RD], STDIN_FILENO) == -1 + || close(stdin_pipe[PIPE_RD]) == -1 + || close(stdin_pipe[PIPE_WR]) == -1 + || close(STDOUT_FILENO) == -1 + || dup2(stdout_pipe[PIPE_WR], STDOUT_FILENO) == -1 + || close(stdout_pipe[PIPE_RD]) == -1 + || close(stdout_pipe[PIPE_WR]) == -1 + || close(STDERR_FILENO) == -1 + || dup2(stderr_pipe[PIPE_WR], STDERR_FILENO) == -1 + || close(stderr_pipe[PIPE_RD]) == -1 + || close(stderr_pipe[PIPE_WR]) == -1 + || execl(shell, shell, "-c", command, NULL) == -1) { + perror("pipethrough_child"); + (void) fflush(stderr); + _exit(EXIT_FAILURE); + } +} + +static int pipethrough_parent(int stdin_fd, + int stdout_fd, + int stderr_fd, + const struct pipe_inbuffer *stdin_buf, + /*@out@*/ struct pipe_outbuffer *stdout_buf, + /*@out@*/ struct pipe_outbuffer *stderr_buf) + /*@globals internalState, fileSystem, errno; @*/ + /*@modifies internalState, fileSystem, errno, + *stdout_buf, *stderr_buf; @*/ +{ + struct writer_buffer stdin_wbuf; + struct reader_buffer stdout_rbuf; + struct reader_buffer stderr_rbuf; + struct select_request sr; + int select_res; + int firsterror; + + firsterror = 0; + writer_buffer_init(&stdin_wbuf, stdin_fd, stdin_buf->data, + stdin_buf->size); + propagate(&firsterror, stdin_wbuf.lasterror); + reader_buffer_init(&stdout_rbuf, stdout_fd); + propagate(&firsterror, stdout_rbuf.lasterror); + reader_buffer_init(&stderr_rbuf, stderr_fd); + propagate(&firsterror, stdout_rbuf.lasterror); + + while (stdin_wbuf.fd != -1 || stdout_rbuf.fd != -1 + || stderr_rbuf.fd != -1) { + select_request_init(&sr); + + retry_select: + select_request_add_writer(&sr, &stdin_wbuf); + select_request_add_reader(&sr, &stdout_rbuf); + select_request_add_reader(&sr, &stderr_rbuf); + select_res = select(sr.n, &(sr.readfds), &(sr.writefds), + NULL, NULL); + if (select_res == -1) { + if (errno == EINTR) { + goto retry_select; + } else { + propagate(&firsterror, errno); + goto cleanup_select; + } + } + select_request_handle_writer(&sr, &stdin_wbuf); + propagate(&firsterror, stdin_wbuf.lasterror); + select_request_handle_reader(&sr, &stdout_rbuf); + propagate(&firsterror, stdout_rbuf.lasterror); + select_request_handle_reader(&sr, &stderr_rbuf); + propagate(&firsterror, stdout_rbuf.lasterror); + } + stdout_buf->data = stdout_rbuf.data; + stdout_rbuf.data = NULL; + stdout_buf->size = stdout_rbuf.size; + stderr_buf->data = stderr_rbuf.data; + stderr_rbuf.data = NULL; + stderr_buf->size = stderr_rbuf.size; + return 0; + +cleanup_select: + writer_buffer_finalize(&stdin_wbuf); + reader_buffer_finalize(&stdout_rbuf); + reader_buffer_finalize(&stderr_rbuf); + errno = firsterror; + return -1; +} + +/* pipethrough -- implementation ***************************************/ + +extern int pipethrough(const char *command, + const struct pipe_inbuffer *stdin_buf, + /*@out@*/ struct pipe_outbuffer *stdout_buf, + /*@out@*/ struct pipe_outbuffer *stderr_buf, + /*@out@*/ int *status) + /*@globals internalState, fileSystem, errno, stderr; @*/ + /*@modifies internalState, fileSystem, errno, *stderr, + *stdout_buf, *stderr_buf, *status; @*/ +{ + int retval = -1; + pid_t pid; + int stdin_pipe[2] = { -1, -1 }; + int stdout_pipe[2] = { -1, -1 }; + int stderr_pipe[2] = { -1, -1 }; + int firsterror = 0; + + if (pipe(stdin_pipe) == -1 + || pipe(stdout_pipe) == -1 + || pipe(stderr_pipe) == -1) { + propagate(&firsterror, errno); + goto cleanup; + } + + if ((pid = fork()) == (pid_t) -1) { + propagate(&firsterror, errno); + goto cleanup; + } + + if (pid == 0) { + pipethrough_child(command, stdin_pipe, stdout_pipe, + stderr_pipe); + /*@notreached@*/ + } + + if (closeref(&stdin_pipe[PIPE_RD]) == -1 + || closeref(&stdout_pipe[PIPE_WR]) == -1 + || closeref(&stderr_pipe[PIPE_WR]) == -1) { + propagate(&firsterror, errno); + goto cleanup; + } + + if (pipethrough_parent(stdin_pipe[PIPE_WR], stdout_pipe[PIPE_RD], + stderr_pipe[PIPE_RD], stdin_buf, + stdout_buf, stderr_buf) == -1) { + propagate(&firsterror, errno); + } else { + retval = 0; + } + + /*@-branchstate@*/ + if (waitpid(pid, status, 0) == (pid_t) -1) { + propagate(&firsterror, errno); + pipe_outbuffer_finalize(stdout_buf); + pipe_outbuffer_finalize(stderr_buf); + retval = -1; + } + /*@=branchstate@*/ + +cleanup: + (void) close(stderr_pipe[PIPE_RD]); + (void) close(stderr_pipe[PIPE_WR]); + (void) close(stdout_pipe[PIPE_RD]); + (void) close(stdout_pipe[PIPE_WR]); + (void) close(stdin_pipe[PIPE_RD]); + (void) close(stdin_pipe[PIPE_WR]); + if (retval == -1) { + errno = firsterror; + } + return retval; +} + +/* pipe_outbuffer -- implementation ************************************/ + +/*@-mustfreeonly@*/ /*@-nullstate@*/ +extern void pipe_outbuffer_finalize(struct pipe_outbuffer *buf) + /*@modifies *buf; @*/ + /*@releases buf->data; @*/ + /*@ensures isnull buf->data; @*/ +{ + if (buf->data != NULL) { + free(buf->data); + buf->data = NULL; + } + buf->size = 0; +} +/*@=mustfreeonly@*/ /*@=nullstate@*/ diff --git a/src/pipethrough.h b/src/pipethrough.h new file mode 100644 index 000000000..d8f845dea --- /dev/null +++ b/src/pipethrough.h @@ -0,0 +1,31 @@ +#ifndef PIPETHROUGH_H +#define PIPETHROUGH_H + +/*@-protoparamname@*/ + +struct pipe_inbuffer { + /*@observer@*/ const void *data; + size_t size; +}; + +struct pipe_outbuffer { + /*@only@*/ /*@null@*/ void *data; + size_t size; +}; + +extern int pipethrough(const char *command, + const struct pipe_inbuffer *stdin_buf, + /*@out@*/ struct pipe_outbuffer *stdout_buf, + /*@out@*/ struct pipe_outbuffer *stderr_buf, + /*@out@*/ int *status) +/*@globals internalState, fileSystem, errno, stderr; @*/ +/*@modifies internalState, fileSystem, errno, *stderr, *stdout_buf, *stderr_buf, *status; @*/; + +extern void pipe_outbuffer_finalize(/*@special@*/ struct pipe_outbuffer *buf) +/*@modifies *buf; @*/ +/*@releases buf->data; @*/ +/*@ensures isnull buf->data; @*/; + +/*@=protoparamname@*/ + +#endif