LCOV - code coverage report
Current view: top level - roff/groff - pipeline.c (source / functions) Hit Total Coverage
Test: GNU roff Lines: 42 72 58.3 %
Date: 2026-01-16 17:51:41 Functions: 1 2 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Copyright 1989-2025 Free Software Foundation, Inc.
       2             :      Written by James Clark (jjc@jclark.com)
       3             : 
       4             : This file is part of groff, the GNU roff typesetting system.
       5             : 
       6             : groff is free software; you can redistribute it and/or modify it under
       7             : the terms of the GNU General Public License as published by the Free
       8             : Software Foundation, either version 3 of the License, or
       9             : (at your option) any later version.
      10             : 
      11             : groff is distributed in the hope that it will be useful, but WITHOUT ANY
      12             : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13             : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14             : for more details.
      15             : 
      16             : You should have received a copy of the GNU General Public License
      17             : along with this program.  If not, see <http://www.gnu.org/licenses/>. */
      18             : 
      19             : #ifdef HAVE_CONFIG_H
      20             : #include <config.h>
      21             : #endif
      22             : 
      23             : #include <errno.h>
      24             : #include <stdbool.h>
      25             : #include <stdio.h> // sprintf()
      26             : #include <string.h> // strerror(), strsignal()
      27             : 
      28             : #include <signal.h> // kill(), SIGINT, signal()
      29             : 
      30             : // needed for dup(), open(), pipe(), STDIN_FILENO, STDOUT_FILENO,
      31             : // unlink()
      32             : #include "posix.h"
      33             : #include "nonposix.h"
      34             : 
      35             : #ifdef _POSIX_VERSION
      36             : 
      37             : #include <sys/wait.h>
      38             : #define PID_T pid_t
      39             : 
      40             : #else /* not _POSIX_VERSION */
      41             : 
      42             : /* traditional Unix */
      43             : 
      44             : #define WIFEXITED(s) (((s) & 0377) == 0)
      45             : #define WIFSTOPPED(s) (((s) & 0377) == 0177)
      46             : #define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
      47             : #define WEXITSTATUS(s) (((s) >> 8) & 0377)
      48             : #define WTERMSIG(s) ((s) & 0177)
      49             : #define WSTOPSIG(s) (((s) >> 8) & 0377)
      50             : 
      51             : #ifndef WCOREFLAG
      52             : #define WCOREFLAG 0200
      53             : #endif
      54             : 
      55             : #define PID_T int
      56             : 
      57             : #endif /* not _POSIX_VERSION */
      58             : 
      59             : /* SVR4 uses WCOREFLG; Net 2 uses WCOREFLAG. */
      60             : #ifndef WCOREFLAG
      61             : #ifdef WCOREFLG
      62             : #define WCOREFLAG WCOREFLG
      63             : #endif /* WCOREFLG */
      64             : #endif /* not WCOREFLAG */
      65             : 
      66             : #ifndef WCOREDUMP
      67             : #ifdef WCOREFLAG
      68             : #define WCOREDUMP(s) ((s) & WCOREFLAG)
      69             : #else /* not WCOREFLAG */
      70             : #define WCOREDUMP(s) (0)
      71             : #endif /* WCOREFLAG */
      72             : #endif /* not WCOREDUMP */
      73             : 
      74             : #include "pipeline.h"
      75             : 
      76             : /* Prototype */
      77             : int run_pipeline(int, char ***, bool);
      78             : 
      79             : #ifdef __cplusplus
      80             : extern "C" {
      81             : #endif
      82             : 
      83             : extern void c_error(const char *, const char *, const char *,
      84             :                     const char *);
      85             : extern void c_fatal(const char *, const char *, const char *,
      86             :                     const char *);
      87             : extern const char *i_to_a(int);         /* from libgroff */
      88             : 
      89             : #ifdef __cplusplus
      90             : }
      91             : #endif
      92             : 
      93             : static void sys_fatal(const char *);
      94             : 
      95             : #if defined(__MSDOS__) \
      96             :     || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)) \
      97             :     || defined(__EMX__)
      98             : 
      99             : static const char *sh = "sh";
     100             : static const char *cmd = "cmd";
     101             : static const char *command = "command";
     102             : 
     103             : char *sbasename(const char *path)
     104             : {
     105             :   char *base;
     106             :   const char *p1, *p2;
     107             : 
     108             :   p1 = path;
     109             :   if ((p2 = strrchr(p1, '\\'))
     110             :       || (p2 = strrchr(p1, '/'))
     111             :       || (p2 = strrchr(p1, ':')))
     112             :     p1 = p2 + 1;
     113             :   if ((p2 = strrchr(p1, '.'))
     114             :       && ((strcasecmp(p2, ".exe") == 0)
     115             :           || (strcasecmp(p2, ".com") == 0)))
     116             :     ;
     117             :   else
     118             :     p2 = p1 + strlen(p1);
     119             : 
     120             :   base = malloc((size_t)(p2 - p1));
     121             :   strncpy(base, p1, p2 - p1);
     122             :   *(base + (p2 - p1)) = '\0';
     123             : 
     124             :   return(base);
     125             : }
     126             : 
     127             : /* Get the name of the system shell */
     128             : char *system_shell_name(void)
     129             : {
     130             :   const char *shell_name;
     131             : 
     132             :   /*
     133             :      Use a Unixy shell if it's installed.  Use SHELL if set; otherwise,
     134             :      let spawnlp try to find sh; if that fails, use COMSPEC if set; if
     135             :      not, try cmd.exe; if that fails, default to command.com.
     136             :    */
     137             : 
     138             :   if ((shell_name = getenv("SHELL")) != NULL)
     139             :     ;
     140             :   else if (spawnlp(_P_WAIT, sh, sh, "-c", ":", NULL) == 0)
     141             :     shell_name = sh;
     142             :   else if ((shell_name = getenv("COMSPEC")) != NULL)
     143             :     ;
     144             :   else if (spawnlp(_P_WAIT, cmd, cmd, "/c", ";", NULL) == 0)
     145             :     shell_name = cmd;
     146             :   else
     147             :     shell_name = command;
     148             : 
     149             :   return sbasename(shell_name);
     150             : }
     151             : 
     152             : const char *system_shell_dash_c(void)
     153             : {
     154             :   char *shell_name;
     155             :   const char *dash_c;
     156             : 
     157             :   shell_name = system_shell_name();
     158             : 
     159             :   /* Assume that if the shell name ends in 'sh', it's Unixy */
     160             :   if (strcasecmp(shell_name + strlen(shell_name) - strlen("sh"), "sh") == 0)
     161             :     dash_c = "-c";
     162             :   else
     163             :     dash_c = "/c";
     164             : 
     165             :   free(shell_name);
     166             :   return dash_c;
     167             : }
     168             : 
     169             : int is_system_shell(const char *prog)
     170             : {
     171             :   int result;
     172             :   char *this_prog, *system_shell;
     173             : 
     174             :   if (!prog)    /* paranoia */
     175             :     return 0;
     176             : 
     177             :   this_prog = sbasename(prog);
     178             :   system_shell = system_shell_name();
     179             : 
     180             :   result = strcasecmp(this_prog, system_shell) == 0;
     181             : 
     182             :   free(this_prog);
     183             :   free(system_shell);
     184             : 
     185             :   return result;
     186             : }
     187             : 
     188             : #ifdef _WIN32
     189             : 
     190             : /*
     191             :   Windows 32 doesn't have fork(), so we need to start asynchronous child
     192             :   processes with spawn() rather than exec().  If there is more than one
     193             :   command, i.e., a pipeline, the parent must set up each child's I/O
     194             :   redirection prior to the spawn.  The original stdout must be restored
     195             :   before spawning the last process in the pipeline, and the original
     196             :   stdin must be restored in the parent after spawning the last process
     197             :   and before waiting for any of the children.
     198             : */
     199             : 
     200             : int run_pipeline(int ncommands, char ***commands, bool no_pipe)
     201             : {
     202             :   int i;
     203             :   int last_input = 0;   /* pacify some compilers */
     204             :   int save_stdin = 0;
     205             :   int save_stdout = 0;
     206             :   int ret = 0;
     207             :   char err_str[BUFSIZ];
     208             :   PID_T pids[MAX_COMMANDS];
     209             : 
     210             :   for (i = 0; i < ncommands; i++) {
     211             :     int pdes[2];
     212             :     PID_T pid;
     213             : 
     214             :     /* If no_pipe is set, just run the commands in sequence
     215             :        to show the version numbers */
     216             :     if (ncommands > 1 && !no_pipe) {
     217             :       /* last command doesn't need a new pipe */
     218             :       if (i < ncommands - 1) {
     219             :         if (pipe(pdes) < 0) {
     220             :           sprintf(err_str, "%s: pipe", commands[i][0]);
     221             :           sys_fatal(err_str);
     222             :         }
     223             :       }
     224             :       /* 1st command; writer */
     225             :       if (i == 0) {
     226             :         /* save stdin */
     227             :         if ((save_stdin = dup(STDIN_FILENO)) < 0)
     228             :           sys_fatal("dup stdin");
     229             :         /* save stdout */
     230             :         if ((save_stdout = dup(STDOUT_FILENO)) < 0)
     231             :           sys_fatal("dup stdout");
     232             : 
     233             :         /* connect stdout to write end of pipe */
     234             :         if (dup2(pdes[1], STDOUT_FILENO) < 0) {
     235             :           sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
     236             :           sys_fatal(err_str);
     237             :         }
     238             :         if (close(pdes[1]) < 0) {
     239             :           sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
     240             :           sys_fatal(err_str);
     241             :         }
     242             :         /*
     243             :            Save the read end of the pipe so that it can be connected to
     244             :            stdin of the next program in the pipeline during the next
     245             :            pass through the loop.
     246             :         */
     247             :         last_input = pdes[0];
     248             :       }
     249             :       /* reader and writer */
     250             :       else if (i < ncommands - 1) {
     251             :         /* connect stdin to read end of last pipe */
     252             :         if (dup2(last_input, STDIN_FILENO) < 0) {
     253             :           sprintf(err_str, " %s: dup2(stdin)", commands[i][0]);
     254             :           sys_fatal(err_str);
     255             :         }
     256             :         if (close(last_input) < 0) {
     257             :           sprintf(err_str, "%s: close(last_input)", commands[i][0]);
     258             :           sys_fatal(err_str);
     259             :         }
     260             :         /* connect stdout to write end of new pipe */
     261             :         if (dup2(pdes[1], STDOUT_FILENO) < 0) {
     262             :           sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
     263             :           sys_fatal(err_str);
     264             :         }
     265             :         if (close(pdes[1]) < 0) {
     266             :           sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
     267             :           sys_fatal(err_str);
     268             :         }
     269             :         last_input = pdes[0];
     270             :       }
     271             :       /* last command; reader */
     272             :       else {
     273             :         /* connect stdin to read end of last pipe */
     274             :         if (dup2(last_input, STDIN_FILENO) < 0) {
     275             :           sprintf(err_str, "%s: dup2(stdin)", commands[i][0]);
     276             :           sys_fatal(err_str);
     277             :         }
     278             :         if (close(last_input) < 0) {
     279             :           sprintf(err_str, "%s: close(last_input)", commands[i][0]);
     280             :           sys_fatal(err_str);
     281             :         }
     282             :         /* restore original stdout */
     283             :         if (dup2(save_stdout, STDOUT_FILENO) < 0) {
     284             :           sprintf(err_str, "%s: dup2(save_stdout))", commands[i][0]);
     285             :           sys_fatal(err_str);
     286             :         }
     287             :         /* close stdout copy */
     288             :         if (close(save_stdout) < 0) {
     289             :           sprintf(err_str, "%s: close(save_stdout)", commands[i][0]);
     290             :           sys_fatal(err_str);
     291             :         }
     292             :       }
     293             :     }
     294             :     if ((pid = spawnvp(_P_NOWAIT, commands[i][0], commands[i])) < 0) {
     295             :       c_error("couldn't exec %1: %2",
     296             :               commands[i][0], strerror(errno), (char *)0);
     297             :       _exit(EXEC_FAILED_EXIT_STATUS);
     298             :     }
     299             :     pids[i] = pid;
     300             :   }
     301             : 
     302             :   if (ncommands > 1 && !no_pipe) {
     303             :     /* restore original stdin if it was redirected */
     304             :     if (dup2(save_stdin, STDIN_FILENO) < 0) {
     305             :       sprintf(err_str, "dup2(save_stdin))");
     306             :       sys_fatal(err_str);
     307             :     }
     308             :     /* close stdin copy */
     309             :     if (close(save_stdin) < 0) {
     310             :       sprintf(err_str, "close(save_stdin)");
     311             :       sys_fatal(err_str);
     312             :     }
     313             :   }
     314             : 
     315             :   for (i = 0; i < ncommands; i++) {
     316             :     int status;
     317             :     PID_T pid;
     318             : 
     319             :     pid = pids[i];
     320             :     if ((pid = WAIT(&status, pid, _WAIT_CHILD)) < 0) {
     321             :       sprintf(err_str, "%s: wait", commands[i][0]);
     322             :       sys_fatal(err_str);
     323             :     }
     324             :     else if (status != 0)
     325             :       ret |= 1;
     326             :   }
     327             :   return ret;
     328             : }
     329             : 
     330             : #else  /* not _WIN32 but __MSDOS__, _UWIN, __CYWGIN__, or __EMX__ */
     331             : 
     332             : /* MS-DOS doesn't have 'fork', so we need to simulate the pipe by
     333             :    running the programs in sequence with standard streams redirected to
     334             :    and from temporary files.
     335             : */
     336             : 
     337             : 
     338             : /* A signal handler that just records that a signal has happened.  */
     339             : static int child_interrupted;
     340             : 
     341             : static RETSIGTYPE signal_catcher(int signo)
     342             : {
     343             :   child_interrupted++;
     344             : }
     345             : 
     346             : int run_pipeline(int ncommands, char ***commands, bool no_pipe)
     347             : {
     348             :   int save_stdin = dup(0);
     349             :   int save_stdout = dup(1);
     350             :   char *tmpfiles[2];
     351             :   int infile  = 0;
     352             :   int outfile = 1;
     353             :   int i, f, ret = 0;
     354             : 
     355             :   /* Choose names for a pair of temporary files to implement the pipeline.
     356             :      Microsoft's 'tempnam' uses the directory specified by 'getenv("TMP")'
     357             :      if it exists; in case it doesn't, try the GROFF alternatives, or
     358             :      'getenv("TEMP")' as last resort -- at least one of these had better
     359             :      be set, since Microsoft's default has a high probability of failure. */
     360             :   char *tmpdir;
     361             :   if ((tmpdir = getenv("GROFF_TMPDIR")) == NULL
     362             :       && (tmpdir = getenv("TMPDIR")) == NULL)
     363             :     tmpdir = getenv("TEMP");
     364             : 
     365             :   /* Don't use 'tmpnam' here: Microsoft's implementation yields unusable
     366             :      file names if current directory is on network share with read-only
     367             :      root. */
     368             :   tmpfiles[0] = tempnam(tmpdir, NULL);
     369             :   tmpfiles[1] = tempnam(tmpdir, NULL);
     370             : 
     371             :   for (i = 0; i < ncommands; i++) {
     372             :     int exit_status;
     373             :     RETSIGTYPE (*prev_handler)(int);
     374             : 
     375             :     if (i && !no_pipe) {
     376             :       /* redirect stdin from temp file */
     377             :       f = open(tmpfiles[infile], O_RDONLY|O_BINARY, 0666);
     378             :       if (f < 0)
     379             :         sys_fatal("open stdin");
     380             :       if (dup2(f, 0) < 0)
     381             :         sys_fatal("dup2 stdin");
     382             :       if (close(f) < 0)
     383             :         sys_fatal("close stdin");
     384             :     }
     385             :     if ((i < ncommands - 1) && !no_pipe) {
     386             :       /* redirect stdout to temp file */
     387             :       f = open(tmpfiles[outfile], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
     388             :       if (f < 0)
     389             :         sys_fatal("open stdout");
     390             :       if (dup2(f, 1) < 0)
     391             :         sys_fatal("dup2 stdout");
     392             :       if (close(f) < 0)
     393             :         sys_fatal("close stdout");
     394             :     }
     395             :     else if (dup2(save_stdout, 1) < 0)
     396             :       sys_fatal("restore stdout");
     397             : 
     398             :     /* run the program */
     399             :     child_interrupted = 0;
     400             :     prev_handler = signal(SIGINT, signal_catcher);
     401             :     exit_status = spawnvp(P_WAIT, commands[i][0], commands[i]);
     402             :     signal(SIGINT, prev_handler);
     403             :     if (child_interrupted) {
     404             :       c_error("%1: Interrupted", commands[i][0], (char *)0, (char *)0);
     405             :       ret |= 2;
     406             :     }
     407             :     else if (exit_status < 0) {
     408             :       c_error("couldn't exec %1: %2",
     409             :               commands[i][0], strerror(errno), (char *)0);
     410             :       ret |= 4;
     411             :     }
     412             :     if (exit_status != 0)
     413             :       ret |= 1;
     414             :     /* There's no sense to continue with the pipe if one of the
     415             :        programs has ended abnormally, is there? */
     416             :     if (ret != 0)
     417             :       break;
     418             :     /* swap temp files: make output of this program be input for the next */
     419             :     infile = 1 - infile;
     420             :     outfile = 1 - outfile;
     421             :   }
     422             :   if (dup2(save_stdin, 0) < 0)
     423             :     sys_fatal("restore stdin");
     424             :   unlink(tmpfiles[0]);
     425             :   unlink(tmpfiles[1]);
     426             :   return ret;
     427             : }
     428             : 
     429             : #endif /* not _WIN32 */
     430             : 
     431             : #else /* not __MSDOS__, not _WIN32 */
     432             : 
     433        1437 : int run_pipeline(int ncommands, char ***commands, bool no_pipe)
     434             : {
     435             :   int i;
     436        1437 :   int last_input = 0;
     437             :   PID_T pids[MAX_COMMANDS];
     438        1437 :   int ret = 0;
     439        1437 :   int proc_count = ncommands;
     440             : 
     441        4287 :   for (i = 0; i < ncommands; i++) {
     442             :     int pdes[2];
     443             :     PID_T pid;
     444             : 
     445        2850 :     if ((i != ncommands - 1) && !no_pipe) {
     446        1411 :       if (pipe(pdes) < 0)
     447           0 :         sys_fatal("pipe");
     448             :     }
     449        2850 :     pid = fork();
     450        5700 :     if (pid < 0)
     451           0 :       sys_fatal("fork");
     452        5700 :     if (pid == 0) {
     453             :       /* child */
     454        2850 :       if (last_input != 0) {
     455        1411 :         if (close(0) < 0)
     456           0 :           sys_fatal("close");
     457        1411 :         if (dup(last_input) < 0)
     458           0 :           sys_fatal("dup");
     459        1411 :         if (close(last_input) < 0)
     460           0 :           sys_fatal("close");
     461             :       }
     462        2850 :       if ((i != ncommands - 1) && !no_pipe) {
     463        1411 :         if (close(1) < 0)
     464           0 :           sys_fatal("close");
     465        1411 :         if (dup(pdes[1]) < 0)
     466           0 :           sys_fatal("dup");
     467        1411 :         if (close(pdes[1]) < 0)
     468           0 :           sys_fatal("close");
     469        1411 :         if (close(pdes[0]))
     470           0 :           sys_fatal("close");
     471             :       }
     472        2850 :       execvp(commands[i][0], commands[i]);
     473           0 :       c_error("couldn't exec %1: %2",
     474        2850 :               commands[i][0], strerror(errno), (char *)0);
     475           0 :       _exit(EXEC_FAILED_EXIT_STATUS);
     476             :     }
     477             :     /* in the parent */
     478        2850 :     if (last_input != 0) {
     479        1411 :       if (close(last_input) < 0)
     480           0 :         sys_fatal("close");
     481             :     }
     482        2850 :     if ((i != ncommands - 1) && !no_pipe) {
     483        1411 :       if (close(pdes[1]) < 0)
     484           0 :         sys_fatal("close");
     485        1411 :       last_input = pdes[0];
     486             :     }
     487        2850 :     pids[i] = pid;
     488             :   }
     489        4287 :   while (proc_count > 0) {
     490             :     int status;
     491        2850 :     PID_T pid = wait(&status);
     492             : 
     493        2850 :     if (pid < 0)
     494           0 :       sys_fatal("wait");
     495        4773 :     for (i = 0; i < ncommands; i++)
     496        4773 :       if (pids[i] == pid) {
     497        2850 :         pids[i] = -1;
     498        2850 :         --proc_count;
     499        2850 :         if (WIFSIGNALED(status)) {
     500           0 :           ret |= 2;
     501           0 :           int sig = WTERMSIG(status);
     502             : #ifdef SIGPIPE
     503           0 :           if (sig == SIGPIPE) {
     504           0 :             if (i == ncommands - 1) {
     505             :               /* This works around a problem that occurred when using the
     506             :                  rerasterize action in gxditview.  What seemed to be
     507             :                  happening (on SunOS 4.1.1) was that pclose() closed the
     508             :                  pipe and waited for groff, gtroff got a SIGPIPE, but
     509             :                  gpic blocked writing to gtroff, and so groff blocked
     510             :                  waiting for gpic and gxditview blocked waiting for
     511             :                  groff.  I don't understand why gpic wasn't getting a
     512             :                  SIGPIPE. */
     513             :               int j;
     514             : 
     515           0 :               for (j = 0; j < ncommands; j++)
     516           0 :                 if (pids[j] > 0)
     517           0 :                   (void)kill(pids[j], SIGPIPE);
     518             :             }
     519             :           }
     520             :           else
     521             : #endif /* SIGPIPE */
     522           0 :             c_error("%1: %2%3",
     523           0 :                     commands[i][0],
     524           0 :                     strsignal(sig),
     525           0 :                     WCOREDUMP(status) ? " (core dumped)" : "");
     526             :         }
     527        2850 :         else if (WIFEXITED(status)) {
     528        2850 :           int exit_status = WEXITSTATUS(status);
     529             : 
     530        2850 :           if (exit_status == EXEC_FAILED_EXIT_STATUS)
     531           0 :             ret |= 4;
     532        2850 :           else if (exit_status != 0)
     533          16 :             ret |= 1;
     534             :         }
     535             :         else
     536           0 :           c_error("unexpected status %1", i_to_a(status), (char *)0,
     537             :                   (char *)0);
     538        2850 :         break;
     539             :       }
     540             :   }
     541        1437 :   return ret;
     542             : }
     543             : 
     544             : #endif /* not __MSDOS__, not _WIN32 */
     545             : 
     546           0 : static void sys_fatal(const char *s)
     547             : {
     548           0 :   c_fatal("%1: %2", s, strerror(errno), (char *)0);
     549           0 : }
     550             : 
     551             : // Local Variables:
     552             : // fill-column: 72
     553             : // mode: C
     554             : // End:
     555             : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:

Generated by: LCOV version 1.14