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:
|