diff options
author | Colin Watson <cjwatson@debian.org> | 2003-08-03 10:16:19 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2003-08-03 10:16:19 +0000 |
commit | 3052e37f9ebbeea677c9503f55dd85c138e2f716 (patch) | |
tree | 5f89049bd47b68b1ef942e307076e7a505242897 /lib/pipeline.c | |
parent | 09fb6e366d93da5e8c6122222da240371b430a79 (diff) |
* lib/pipeline.c: New pipeline library, adapted from that in groff.
The interface isn't complete yet; in particular, it can't be a
replacement for popen() at the moment.
* lib/pipeline.h: New file with pipeline library interface.
* lib/Makefile.in (ALLSRCS, ALL): Build pipeline.
Diffstat (limited to 'lib/pipeline.c')
-rw-r--r-- | lib/pipeline.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/lib/pipeline.c b/lib/pipeline.c new file mode 100644 index 00000000..87a1c38c --- /dev/null +++ b/lib/pipeline.c @@ -0,0 +1,348 @@ +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003 + * Free Software Foundation, Inc. + * Copyright (C) 2003 Colin Watson. + * Written for groff by James Clark (jjc@jclark.com) + * Adapted for man-db by Colin Watson. + * + * This file is part of man-db. + * + * man-db 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. + * + * man-db 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 man-db; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <stdarg.h> +#include <assert.h> + +#ifdef HAVE_STRERROR +#include <string.h> +#else +extern char *strerror (); +#endif + +#include <sys/wait.h> + +#include <locale.h> +#include <libintl.h> +#define _(String) gettext (String) + +#include "manconfig.h" +#include "error.h" +#include "pipeline.h" + +command *command_new (const char *name) +{ + command *cmd = xmalloc (sizeof *cmd); + char *name_copy; + + cmd->name = xstrdup (name); + cmd->argc = 0; + cmd->argv_max = 4; + cmd->argv = xmalloc (cmd->argv_max * sizeof *cmd->argv); + + /* argv[0] is the basename of the command name. */ + name_copy = xstrdup (name); + command_arg (cmd, basename (name_copy)); + free (name_copy); + + return cmd; +} + +command *command_new_argv (const char *name, va_list argv) +{ + command *cmd = command_new (name); + command_argv (cmd, argv); + return cmd; +} + +command *command_new_args (const char *name, ...) +{ + va_list argv; + command *cmd; + + va_start (argv, name); + cmd = command_new_argv (name, argv); + va_end (argv); + + return cmd; +} + +void command_arg (command *cmd, const char *arg) +{ + if (cmd->argc + 1 >= cmd->argv_max) { + cmd->argv_max *= 2; + cmd->argv = xrealloc (cmd->argv, + cmd->argv_max * sizeof *cmd->argv); + } + + cmd->argv[cmd->argc++] = xstrdup (arg); + cmd->argv[cmd->argc] = NULL; +} + +void command_argv (command *cmd, va_list argv) +{ + const char *arg = va_arg (argv, const char *); + + while (arg) { + command_arg (cmd, arg); + arg = va_arg (argv, const char *); + } +} + +void command_args (command *cmd, ...) +{ + va_list argv; + + va_start (argv, cmd); + command_argv (cmd, argv); + va_end (argv); +} + +void command_free (command *cmd) +{ + int i; + + free (cmd->name); + for (i = 0; i < cmd->argc; ++i) + free (cmd->argv[i]); + free (cmd->argv); + free (cmd); +} + +pipeline *pipeline_new (void) +{ + pipeline *p = xmalloc (sizeof *p); + p->ncommands = 0; + p->commands_max = 4; + p->commands = xmalloc (p->commands_max * sizeof *p->commands); + p->pids = NULL; + return p; +} + +pipeline *pipeline_new_commandv (command *cmd1, va_list cmdv) +{ + pipeline *p = pipeline_new (); + pipeline_command (p, cmd1); + pipeline_commandv (p, cmdv); + return p; +} + +pipeline *pipeline_new_commands (command *cmd1, ...) +{ + va_list cmdv; + pipeline *p; + + va_start (cmdv, cmd1); + p = pipeline_new_commandv (cmd1, cmdv); + va_end (cmdv); + + return p; +} + +void pipeline_command (pipeline *p, command *cmd) +{ + if (p->ncommands > p->commands_max) { + p->commands_max *= 2; + p->commands = xrealloc (p->commands, + p->commands_max * sizeof *p->commands); + } + + p->commands[p->ncommands++] = cmd; +} + +void pipeline_command_args (pipeline *p, const char *name, ...) +{ + va_list argv; + command *cmd; + + va_start (argv, name); + cmd = command_new_argv (name, argv); + va_end (argv); + pipeline_command (p, cmd); +} + +void pipeline_commandv (pipeline *p, va_list cmdv) +{ + command *cmd = va_arg (cmdv, command *); + + while (cmd) { + pipeline_command (p, cmd); + cmd = va_arg (cmdv, command *); + } +} + +void pipeline_commands (pipeline *p, ...) +{ + va_list cmdv; + + va_start (cmdv, p); + pipeline_commandv (p, cmdv); + va_end (cmdv); +} + +/* Children exit with this status if execvp fails. */ +#define EXEC_FAILED_EXIT_STATUS 0xff + +void pipeline_start (pipeline *p) +{ + int i; + int last_input = 0; + + assert (!p->pids); /* pipeline not started already */ + + p->pids = xmalloc (p->ncommands * sizeof *p->pids); + + for (i = 0; i < p->ncommands; i++) { + int pdes[2]; + pid_t pid; + + if (i != p->ncommands - 1) { + if (pipe (pdes) < 0) + error (FATAL, errno, _("pipe failed")); + } + pid = fork (); + if (pid < 0) + error (FATAL, errno, _("fork failed")); + if (pid == 0) { + /* child */ + if (last_input != 0) { + if (close (0) < 0) + error (FATAL, errno, + _("close failed")); + if (dup (last_input) < 0) + error (FATAL, errno, _("dup failed")); + if (close (last_input) < 0) + error (FATAL, errno, + _("close failed")); + } + if (i != p->ncommands - 1) { + if (close (1) < 0) + error (FATAL, errno, + _("close failed")); + if (dup (pdes[1]) < 0) + error (FATAL, errno, _("dup failed")); + if (close (pdes[1]) < 0) + error (FATAL, errno, + _("close failed")); + if (close (pdes[0])) + error (FATAL, errno, + _("close failed")); + } + execvp (p->commands[i]->name, p->commands[i]->argv); + error (EXEC_FAILED_EXIT_STATUS, errno, + _("couldn't exec %s"), p->commands[i]->name); + } + /* in the parent */ + if (last_input != 0) { + if (close (last_input) < 0) + error (FATAL, errno, _("close failed")); + } + if (i != p->ncommands - 1) { + if (close (pdes[1]) < 0) + error (FATAL, errno, _("close failed")); + last_input = pdes[0]; + } + p->pids[i] = pid; + } +} + +/* TODO: Perhaps it would be useful to wait on multiple pipelines + * simultaneously? Then you could do: + * + * pipeline_start (p1); + * pipeline_start (p2); + * pipeline_start (p3); + * ... select() on p1's output, p2's input, and p3's input, and glue them + * together ... + * pipeline_wait (p1, p2, p3); + * + * ... and have processes exit as their input is closed by other processes + * exiting, etc. + */ +int pipeline_wait (pipeline *p) +{ + int ret = 0; + int proc_count = p->ncommands; + + assert (p->pids); /* pipeline started */ + + while (proc_count > 0) { + int i; + int status; + + pid_t pid = wait (&status); + + if (pid < 0) + error (FATAL, errno, _("wait failed")); + for (i = 0; i < p->ncommands; i++) + if (p->pids[i] == pid) { + p->pids[i] = -1; + --proc_count; + if (WIFSIGNALED (status)) { + int sig = WTERMSIG (status); +#ifdef SIGPIPE + if (sig != SIGPIPE) +#endif /* SIGPIPE */ + { + error (0, 0, _("%s: %s%s"), + p->commands[i]->name, + xstrsignal (sig), + WCOREDUMP (status) ? + " (core dumped)" : + ""); + ret |= 2; + } + } else if (WIFEXITED (status)) { + int exit_status = WEXITSTATUS (status); + + if (exit_status == + EXEC_FAILED_EXIT_STATUS) + ret |= 4; + else if (exit_status != 0) + ret |= 1; + } else + error (0, 0, "unexpected status %d", + status); + break; + } + } + + free (p->pids); + p->pids = NULL; + + return ret; +} + +void pipeline_free (pipeline *p) +{ + int i; + + for (i = 0; i < p->ncommands; ++i) + command_free (p->commands[i]); + free (p->commands); + if (p->pids) + free (p->pids); + free (p); +} |