summaryrefslogtreecommitdiffstats
path: root/src/roff/groff/pipeline.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/roff/groff/pipeline.c')
-rw-r--r--src/roff/groff/pipeline.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/src/roff/groff/pipeline.c b/src/roff/groff/pipeline.c
new file mode 100644
index 0000000..defafc2
--- /dev/null
+++ b/src/roff/groff/pipeline.c
@@ -0,0 +1,589 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff 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.
+
+groff 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, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_STRERROR
+#include <string.h>
+#else
+extern char *strerror();
+#endif
+
+#ifdef _POSIX_VERSION
+
+#include <sys/wait.h>
+#define PID_T pid_t
+
+#else /* not _POSIX_VERSION */
+
+/* traditional Unix */
+
+#define WIFEXITED(s) (((s) & 0377) == 0)
+#define WIFSTOPPED(s) (((s) & 0377) == 0177)
+#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
+#define WEXITSTATUS(s) (((s) >> 8) & 0377)
+#define WTERMSIG(s) ((s) & 0177)
+#define WSTOPSIG(s) (((s) >> 8) & 0377)
+
+#ifndef WCOREFLAG
+#define WCOREFLAG 0200
+#endif
+
+#define PID_T int
+
+#endif /* not _POSIX_VERSION */
+
+/* SVR4 uses WCOREFLG; Net 2 uses WCOREFLAG. */
+#ifndef WCOREFLAG
+#ifdef WCOREFLG
+#define WCOREFLAG WCOREFLG
+#endif /* WCOREFLG */
+#endif /* not WCOREFLAG */
+
+#ifndef WCOREDUMP
+#ifdef WCOREFLAG
+#define WCOREDUMP(s) ((s) & WCOREFLAG)
+#else /* not WCOREFLAG */
+#define WCOREDUMP(s) (0)
+#endif /* WCOREFLAG */
+#endif /* not WCOREDUMP */
+
+#include "pipeline.h"
+
+/* Prototype */
+int run_pipeline(int, char ***, int);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void c_error(const char *, const char *, const char *,
+ const char *);
+extern void c_fatal(const char *, const char *, const char *,
+ const char *);
+extern const char *i_to_a(int); /* from libgroff */
+
+#ifdef __cplusplus
+}
+#endif
+
+static void sys_fatal(const char *);
+static const char *xstrsignal(int);
+
+
+#if defined(__MSDOS__) \
+ || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)) \
+ || defined(__EMX__)
+
+#include <process.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nonposix.h"
+
+static const char *sh = "sh";
+static const char *cmd = "cmd";
+static const char *command = "command";
+
+extern int strcasecmp(const char *, const char *);
+
+char *sbasename(const char *path)
+{
+ char *base;
+ const char *p1, *p2;
+
+ p1 = path;
+ if ((p2 = strrchr(p1, '\\'))
+ || (p2 = strrchr(p1, '/'))
+ || (p2 = strrchr(p1, ':')))
+ p1 = p2 + 1;
+ if ((p2 = strrchr(p1, '.'))
+ && ((strcasecmp(p2, ".exe") == 0)
+ || (strcasecmp(p2, ".com") == 0)))
+ ;
+ else
+ p2 = p1 + strlen(p1);
+
+ base = malloc((size_t)(p2 - p1));
+ strncpy(base, p1, p2 - p1);
+ *(base + (p2 - p1)) = '\0';
+
+ return(base);
+}
+
+/* Get the name of the system shell */
+char *system_shell_name(void)
+{
+ const char *shell_name;
+
+ /*
+ Use a Unixy shell if it's installed. Use SHELL if set; otherwise,
+ let spawnlp try to find sh; if that fails, use COMSPEC if set; if
+ not, try cmd.exe; if that fails, default to command.com.
+ */
+
+ if ((shell_name = getenv("SHELL")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, sh, sh, "-c", ":", NULL) == 0)
+ shell_name = sh;
+ else if ((shell_name = getenv("COMSPEC")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, cmd, cmd, "/c", ";", NULL) == 0)
+ shell_name = cmd;
+ else
+ shell_name = command;
+
+ return sbasename(shell_name);
+}
+
+const char *system_shell_dash_c(void)
+{
+ char *shell_name;
+ const char *dash_c;
+
+ shell_name = system_shell_name();
+
+ /* Assume that if the shell name ends in 'sh', it's Unixy */
+ if (strcasecmp(shell_name + strlen(shell_name) - strlen("sh"), "sh") == 0)
+ dash_c = "-c";
+ else
+ dash_c = "/c";
+
+ free(shell_name);
+ return dash_c;
+}
+
+int is_system_shell(const char *prog)
+{
+ int result;
+ char *this_prog, *system_shell;
+
+ if (!prog) /* paranoia */
+ return 0;
+
+ this_prog = sbasename(prog);
+ system_shell = system_shell_name();
+
+ result = strcasecmp(this_prog, system_shell) == 0;
+
+ free(this_prog);
+ free(system_shell);
+
+ return result;
+}
+
+#ifdef _WIN32
+
+/*
+ Windows 32 doesn't have fork(), so we need to start asynchronous child
+ processes with spawn() rather than exec(). If there is more than one
+ command, i.e., a pipeline, the parent must set up each child's I/O
+ redirection prior to the spawn. The original stdout must be restored
+ before spawning the last process in the pipeline, and the original
+ stdin must be restored in the parent after spawning the last process
+ and before waiting for any of the children.
+*/
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0; /* pacify some compilers */
+ int save_stdin = 0;
+ int save_stdout = 0;
+ int ret = 0;
+ char err_str[BUFSIZ];
+ PID_T pids[MAX_COMMANDS];
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ /* If no_pipe is set, just run the commands in sequence
+ to show the version numbers */
+ if (ncommands > 1 && !no_pipe) {
+ /* last command doesn't need a new pipe */
+ if (i < ncommands - 1) {
+ if (pipe(pdes) < 0) {
+ sprintf(err_str, "%s: pipe", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ /* 1st command; writer */
+ if (i == 0) {
+ /* save stdin */
+ if ((save_stdin = dup(STDIN_FILENO)) < 0)
+ sys_fatal("dup stdin");
+ /* save stdout */
+ if ((save_stdout = dup(STDOUT_FILENO)) < 0)
+ sys_fatal("dup stdout");
+
+ /* connect stdout to write end of pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /*
+ Save the read end of the pipe so that it can be connected to
+ stdin of the next program in the pipeline during the next
+ pass through the loop.
+ */
+ last_input = pdes[0];
+ }
+ /* reader and writer */
+ else if (i < ncommands - 1) {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, " %s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* connect stdout to write end of new pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ last_input = pdes[0];
+ }
+ /* last command; reader */
+ else {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* restore original stdout */
+ if (dup2(save_stdout, STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(save_stdout))", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* close stdout copy */
+ if (close(save_stdout) < 0) {
+ sprintf(err_str, "%s: close(save_stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ }
+ if ((pid = spawnvp(_P_NOWAIT, commands[i][0], commands[i])) < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ pids[i] = pid;
+ }
+
+ if (ncommands > 1 && !no_pipe) {
+ /* restore original stdin if it was redirected */
+ if (dup2(save_stdin, STDIN_FILENO) < 0) {
+ sprintf(err_str, "dup2(save_stdin))");
+ sys_fatal(err_str);
+ }
+ /* close stdin copy */
+ if (close(save_stdin) < 0) {
+ sprintf(err_str, "close(save_stdin)");
+ sys_fatal(err_str);
+ }
+ }
+
+ for (i = 0; i < ncommands; i++) {
+ int status;
+ PID_T pid;
+
+ pid = pids[i];
+ if ((pid = WAIT(&status, pid, _WAIT_CHILD)) < 0) {
+ sprintf(err_str, "%s: wait", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ else if (status != 0)
+ ret |= 1;
+ }
+ return ret;
+}
+
+#else /* not _WIN32 */
+
+/* MS-DOS doesn't have 'fork', so we need to simulate the pipe by
+ running the programs in sequence with standard streams redirected to
+ and from temporary files.
+*/
+
+
+/* A signal handler that just records that a signal has happened. */
+static int child_interrupted;
+
+static RETSIGTYPE signal_catcher(int signo)
+{
+ child_interrupted++;
+}
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int save_stdin = dup(0);
+ int save_stdout = dup(1);
+ char *tmpfiles[2];
+ int infile = 0;
+ int outfile = 1;
+ int i, f, ret = 0;
+
+ /* Choose names for a pair of temporary files to implement the pipeline.
+ Microsoft's 'tempnam' uses the directory specified by 'getenv("TMP")'
+ if it exists; in case it doesn't, try the GROFF alternatives, or
+ 'getenv("TEMP")' as last resort -- at least one of these had better
+ be set, since Microsoft's default has a high probability of failure. */
+ char *tmpdir;
+ if ((tmpdir = getenv("GROFF_TMPDIR")) == NULL
+ && (tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = getenv("TEMP");
+
+ /* Don't use 'tmpnam' here: Microsoft's implementation yields unusable
+ file names if current directory is on network share with read-only
+ root. */
+ tmpfiles[0] = tempnam(tmpdir, NULL);
+ tmpfiles[1] = tempnam(tmpdir, NULL);
+
+ for (i = 0; i < ncommands; i++) {
+ int exit_status;
+ RETSIGTYPE (*prev_handler)(int);
+
+ if (i && !no_pipe) {
+ /* redirect stdin from temp file */
+ f = open(tmpfiles[infile], O_RDONLY|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdin");
+ if (dup2(f, 0) < 0)
+ sys_fatal("dup2 stdin");
+ if (close(f) < 0)
+ sys_fatal("close stdin");
+ }
+ if ((i < ncommands - 1) && !no_pipe) {
+ /* redirect stdout to temp file */
+ f = open(tmpfiles[outfile], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdout");
+ if (dup2(f, 1) < 0)
+ sys_fatal("dup2 stdout");
+ if (close(f) < 0)
+ sys_fatal("close stdout");
+ }
+ else if (dup2(save_stdout, 1) < 0)
+ sys_fatal("restore stdout");
+
+ /* run the program */
+ child_interrupted = 0;
+ prev_handler = signal(SIGINT, signal_catcher);
+ exit_status = spawnvp(P_WAIT, commands[i][0], commands[i]);
+ signal(SIGINT, prev_handler);
+ if (child_interrupted) {
+ c_error("%1: Interrupted", commands[i][0], (char *)0, (char *)0);
+ ret |= 2;
+ }
+ else if (exit_status < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ ret |= 4;
+ }
+ if (exit_status != 0)
+ ret |= 1;
+ /* There's no sense to continue with the pipe if one of the
+ programs has ended abnormally, is there? */
+ if (ret != 0)
+ break;
+ /* swap temp files: make output of this program be input for the next */
+ infile = 1 - infile;
+ outfile = 1 - outfile;
+ }
+ if (dup2(save_stdin, 0) < 0)
+ sys_fatal("restore stdin");
+ unlink(tmpfiles[0]);
+ unlink(tmpfiles[1]);
+ return ret;
+}
+
+#endif /* not _WIN32 */
+
+#else /* not __MSDOS__, not _WIN32 */
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0;
+ PID_T pids[MAX_COMMANDS];
+ int ret = 0;
+ int proc_count = ncommands;
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (pipe(pdes) < 0)
+ sys_fatal("pipe");
+ }
+ pid = fork();
+ if (pid < 0)
+ sys_fatal("fork");
+ if (pid == 0) {
+ /* child */
+ if (last_input != 0) {
+ if (close(0) < 0)
+ sys_fatal("close");
+ if (dup(last_input) < 0)
+ sys_fatal("dup");
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(1) < 0)
+ sys_fatal("close");
+ if (dup(pdes[1]) < 0)
+ sys_fatal("dup");
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ if (close(pdes[0]))
+ sys_fatal("close");
+ }
+ execvp(commands[i][0], commands[i]);
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ /* in the parent */
+ if (last_input != 0) {
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ last_input = pdes[0];
+ }
+ pids[i] = pid;
+ }
+ while (proc_count > 0) {
+ int status;
+ PID_T pid = wait(&status);
+
+ if (pid < 0)
+ sys_fatal("wait");
+ for (i = 0; i < ncommands; i++)
+ if (pids[i] == pid) {
+ pids[i] = -1;
+ --proc_count;
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+#ifdef SIGPIPE
+ if (sig == SIGPIPE) {
+ if (i == ncommands - 1) {
+ /* This works around a problem that occurred when using the
+ rerasterize action in gxditview. What seemed to be
+ happening (on SunOS 4.1.1) was that pclose() closed the
+ pipe and waited for groff, gtroff got a SIGPIPE, but
+ gpic blocked writing to gtroff, and so groff blocked
+ waiting for gpic and gxditview blocked waiting for
+ groff. I don't understand why gpic wasn't getting a
+ SIGPIPE. */
+ int j;
+
+ for (j = 0; j < ncommands; j++)
+ if (pids[j] > 0)
+ (void)kill(pids[j], SIGPIPE);
+ }
+ }
+ else
+#endif /* SIGPIPE */
+ {
+ c_error("%1: %2%3",
+ commands[i][0],
+ 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
+ c_error("unexpected status %1", i_to_a(status), (char *)0,
+ (char *)0);
+ break;
+ }
+ }
+ return ret;
+}
+
+#endif /* not __MSDOS__, not _WIN32 */
+
+static void sys_fatal(const char *s)
+{
+ c_fatal("%1: %2", s, strerror(errno), (char *)0);
+}
+
+static const char *xstrsignal(int n)
+{
+ static char buf[sizeof("Signal ") + 1 + sizeof(int) * 3];
+
+#ifdef NSIG
+#if HAVE_DECL_STRSIGNAL
+ if (n >= 0 && n < NSIG && strsignal(n) != 0)
+ return strsignal(n);
+#else
+#if HAVE_DECL_SYS_SIGLIST
+ if (n >= 0 && n < NSIG && sys_siglist[n] != 0)
+ return sys_siglist[n];
+#endif /* HAVE_DECL_SYS_SIGLIST */
+#endif /* HAVE_DECL_STRSIGNAL */
+#endif /* NSIG */
+ sprintf(buf, "Signal %d", n);
+ return buf;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: