3 * Dump custom Lisp images for faster script execution
5 * (c) 2020 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
12 * Runlisp is free software: you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the
14 * Free Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
17 * Runlisp is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * You should have received a copy of the GNU General Public License
23 * along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
26 /*----- Header files ------------------------------------------------------*/
43 #include <sys/select.h>
53 /*----- Static data -------------------------------------------------------*/
55 #define MAXLINE 16384u
70 struct treap_node _node
;
77 struct linebuf out
, err
;
79 #define JOB_NAME(job) TREAP_NODE_KEY(job)
80 #define JOB_NAMELEN(job) TREAP_NODE_KEYLEN(job)
82 static struct treap jobs
= TREAP_INIT
;
83 static struct job
*job_ready
, *job_run
, *job_dead
;
84 static unsigned nrun
, maxrun
= 1;
88 static int sig_pipe
[2] = { -1, -1 };
89 static sigset_t caught
, pending
;
90 static int sigloss
= -1;
92 static unsigned flags
= 0;
93 #define AF_BOGUS 0x0001u
94 #define AF_SETCONF 0x0002u
95 #define AF_DRYRUN 0x0004u
96 #define AF_ALL 0x0008u
97 #define AF_FORCE 0x0010u
98 #define AF_CHECKINST 0x0020u
100 /*----- Main code ---------------------------------------------------------*/
102 static PRINTF_LIKE(1, 2) void bad(const char *msg
, ...)
103 { va_list ap
; va_start(ap
, msg
); vmoan(msg
, ap
); va_end(ap
); rc
= 2; }
105 static const char *tmpdir
;
107 static void set_tmpdir(void)
109 struct dstr d
= DSTR_INIT
;
113 dstr_putf(&d
, "%s/runlisp.%d.", my_getenv("TMPDIR", "/tmp"), getpid());
116 d
.len
= n
; dstr_putf(&d
, "%d", rand());
117 if (!mkdir(d
.p
, 0700)) break;
118 else if (errno
!= EEXIST
)
119 lose("failed to create temporary directory `%s': %s",
120 d
.p
, strerror(errno
));
121 else if (++i
>= 32) {
122 dstr_puts(&d
, "???");
123 lose("failed to create temporary directory `%s': too many attempts",
127 tmpdir
= xstrndup(d
.p
, d
.len
); dstr_release(&d
);
130 static void recursive_delete_(struct dstr
*dd
)
137 dir
= opendir(dd
->p
);
139 lose("failed to open directory `%s' for cleanup: %s",
140 dd
->p
, strerror(errno
));
144 d
= readdir(dir
); if (!d
) break;
145 if (d
->d_name
[0] == '.' && (!d
->d_name
[1] ||
146 (d
->d_name
[1] == '.' && !d
->d_name
[2])))
148 dd
->len
= n
; dstr_puts(dd
, d
->d_name
);
150 else if (errno
== EISDIR
) recursive_delete_(dd
);
151 else lose("failed to delete file `%s': %s", dd
->p
, strerror(errno
));
156 lose("failed to delete directory `%s': %s", dd
->p
, strerror(errno
));
159 static void recursive_delete(const char *path
)
161 struct dstr d
= DSTR_INIT
;
162 dstr_puts(&d
, path
); recursive_delete_(&d
); dstr_release(&d
);
165 static void cleanup(void)
166 { if (tmpdir
) { recursive_delete(tmpdir
); tmpdir
= 0; } }
168 static int configure_fd(const char *what
, int fd
, int nonblock
, int cloexec
)
172 if (nonblock
!= -1) {
173 fl
= fcntl(fd
, F_GETFL
); if (fl
< 0) goto fail
;
174 if (nonblock
) nfl
= fl
| O_NONBLOCK
;
175 else nfl
= fl
&~O_NONBLOCK
;
176 if (fl
!= nfl
&& fcntl(fd
, F_SETFL
, nfl
)) goto fail
;
180 fl
= fcntl(fd
, F_GETFD
); if (fl
< 0) goto fail
;
181 if (cloexec
) nfl
= fl
| FD_CLOEXEC
;
182 else nfl
= fl
&~FD_CLOEXEC
;
183 if (fl
!= nfl
&& fcntl(fd
, F_SETFD
, nfl
)) goto fail
;
189 bad("failed to configure %s descriptor: %s", what
, strerror(errno
));
193 static void handle_signal(int sig
)
198 sigprocmask(SIG_BLOCK
, &caught
, &old
);
199 sigaddset(&pending
, sig
);
200 sigprocmask(SIG_SETMASK
, &old
, 0);
202 DISCARD(write(sig_pipe
[1], &x
, 1));
206 static void add_job(struct job
***tail_inout
, unsigned f
,
207 const char *name
, size_t len
)
210 struct treap_path path
;
211 struct config_section
*sect
;
212 struct config_var
*dump_var
, *cmd_var
;
213 struct dstr d
= DSTR_INIT
;
214 struct argv av
= ARGV_INIT
;
217 job
= treap_probe(&jobs
, name
, len
, &path
);
220 moan("ignoring duplicate Lisp `%s'", JOB_NAME(job
));
225 sect
= config_find_section_n(&config
, 0, name
, len
);
226 if (!sect
) lose("unknown Lisp implementation `%.*s'", (int)len
, name
);
227 name
= CONFIG_SECTION_NAME(sect
);
228 dump_var
= config_find_var(&config
, sect
, 0, "dump-image");
231 lose("don't know how to dump images for Lisp implementation `%s'",
235 cmd_var
= config_find_var(&config
, sect
, 0, "command");
237 lose("no `command' defined for Lisp implementation `%s'", name
);
239 config_subst_split_var(&config
, sect
, dump_var
, &av
);
240 if (!av
.n
) lose("empty command for Lisp implementation `%s'", name
);
242 if (flags
&AF_CHECKINST
) {
244 fef
= (verbose
>= 2 ? FEF_VERBOSE
: 0);
245 config_subst_var(&config
, sect
, cmd_var
, &d
);
246 if (!found_in_path_p(d
.p
, fef
) ||
247 (STRCMP(d
.p
, !=, av
.v
[0]) && !found_in_path_p(av
.v
[0], fef
))) {
248 if (verbose
>= 2) moan("skipping Lisp implementation `%s'", name
);
253 if (!(flags
&AF_FORCE
)) {
255 config_subst_string(&config
, sect
, "<internal>", "${@IMAGE}", &d
);
256 if (!access(d
.p
, F_OK
)) {
258 moan("image `%s' already exists: skipping `%s'", d
.p
, name
);
263 job
= xmalloc(sizeof(*job
));
266 job
->out
.fd
= -1; job
->out
.buf
= 0;
267 job
->err
.fd
= -1; job
->err
.buf
= 0;
268 job
->av
= av
; argv_init(&av
);
269 treap_insert(&jobs
, &path
, &job
->_node
, name
, len
);
270 **tail_inout
= job
; *tail_inout
= &job
->next
;
272 dstr_release(&d
); argv_release(&av
);
275 static void release_job(struct job
*job
)
277 if (job
->kid
> 0) kill(job
->kid
, SIGKILL
); /* ?? */
278 if (job
->log
&& job
->log
!= stdout
) fclose(job
->log
);
279 free(job
->out
.buf
); if (job
->out
.fd
>= 0) close(job
->out
.fd
);
280 free(job
->err
.buf
); if (job
->err
.fd
>= 0) close(job
->err
.fd
);
284 static void finish_job(struct job
*job
)
290 fprintf(job
->log
, "%-13s > ", JOB_NAME(job
));
291 if (WIFEXITED(job
->exit
)) {
292 if (!WEXITSTATUS(job
->exit
))
293 { fputs("completed successfully\n", job
->log
); ok
= 1; }
295 fprintf(job
->log
, "failed with exit status %d\n",
296 WEXITSTATUS(job
->exit
));
297 } else if (WIFSIGNALED(job
->exit
))
298 fprintf(job
->log
, "killed by signal %d (%s%s)", WTERMSIG(job
->exit
),
299 #if defined(HAVE_STRSIGNAL)
300 strsignal(WTERMSIG(job
->exit
)),
301 #elif defined(HAVE_DECL_SYS_SIGLIST)
302 sys_siglist
[WTERMSIG(job
->exit
)],
307 WCOREDUMP(job
->exit
) ?
"; core dumped" :
311 fprintf(job
->log
, "exited with incomprehensible status %06o\n",
314 if (!ok
&& verbose
< 2) {
317 n
= fread(buf
, 1, sizeof(buf
), job
->log
);
318 if (n
) fwrite(buf
, 1, n
, stdout
);
319 if (n
< sizeof(buf
)) break;
326 static int find_newline(struct linebuf
*buf
, size_t *linesz_out
)
330 if (buf
->off
+ buf
->len
<= MAXLINE
) {
331 nl
= memchr(buf
->buf
+ buf
->off
, '\n', buf
->len
);
332 if (nl
) { *linesz_out
= (nl
- buf
->buf
) - buf
->off
; return (0); }
334 nl
= memchr(buf
->buf
+ buf
->off
, '\n', MAXLINE
- buf
->off
);
335 if (nl
) { *linesz_out
= (nl
- buf
->buf
) - buf
->off
; return (0); }
336 nl
= memchr(buf
->buf
, '\n', buf
->len
- (MAXLINE
- buf
->off
));
338 { *linesz_out
= (nl
- buf
->buf
) + (MAXLINE
- buf
->off
); return (0); }
343 static void write_line(struct job
*job
, struct linebuf
*buf
,
344 size_t n
, char marker
, const char *tail
)
346 fprintf(job
->log
, "%-13s %c ", JOB_NAME(job
), marker
);
347 if (buf
->off
+ n
<= MAXLINE
)
348 fwrite(buf
->buf
+ buf
->off
, 1, n
, job
->log
);
350 fwrite(buf
->buf
+ buf
->off
, 1, MAXLINE
- buf
->off
, job
->log
);
351 fwrite(buf
->buf
, 1, n
- (MAXLINE
- buf
->off
), job
->log
);
353 fputs(tail
, job
->log
);
356 static void prefix_lines(struct job
*job
, struct linebuf
*buf
, char marker
)
358 struct iovec iov
[2]; int niov
;
362 assert(buf
->len
< MAXLINE
);
364 iov
[0].iov_base
= buf
->buf
+ buf
->len
;
365 iov
[0].iov_len
= MAXLINE
- buf
->len
;
367 } else if (buf
->off
+ buf
->len
>= MAXLINE
) {
368 iov
[0].iov_base
= buf
->buf
+ buf
->off
+ buf
->len
- MAXLINE
;
369 iov
[0].iov_len
= MAXLINE
- buf
->len
;
372 iov
[0].iov_base
= buf
->buf
+ buf
->off
+ buf
->len
;
373 iov
[0].iov_len
= MAXLINE
- (buf
->off
+ buf
->len
);
374 iov
[1].iov_base
= buf
->buf
;
375 iov
[1].iov_len
= buf
->off
;
379 n
= readv(buf
->fd
, iov
, niov
);
381 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
) return;
382 lose("failed to read job `%s' output stream: %s",
383 JOB_NAME(job
), strerror(errno
));
387 while (!find_newline(buf
, &linesz
)) {
388 write_line(job
, buf
, linesz
, marker
, "\n");
389 buf
->len
-= linesz
+ 1;
390 buf
->off
+= linesz
+ 1; if (buf
->off
>= MAXLINE
) buf
->off
-= MAXLINE
;
394 else if (buf
->len
== MAXLINE
) {
395 write_line(job
, buf
, MAXLINE
, marker
, " [...]\n");
396 buf
->off
= buf
->len
= 0;
400 close(buf
->fd
); buf
->fd
= -1;
402 write_line(job
, buf
, buf
->len
, marker
, " [missing final newline]\n");
406 static void reap_children(void)
408 struct job
*job
, **link
;
413 kid
= waitpid(0, &st
, WNOHANG
);
415 for (link
= &job_run
; (job
= *link
); link
= &job
->next
)
416 if (job
->kid
== kid
) goto found
;
417 moan("unexpected child process %d exited with status %06o", kid
, st
);
420 job
->exit
= st
; job
->st
= JST_DEAD
; job
->kid
= -1; nrun
--;
421 *link
= job
->next
; job
->next
= job_dead
; job_dead
= job
;
423 if (kid
< 0 && errno
!= ECHILD
)
424 lose("failed to collect child process exit status: %s", strerror(errno
));
427 static void check_signals(void)
433 sigprocmask(SIG_BLOCK
, &caught
, &old
);
434 pend
= pending
; sigemptyset(&pending
);
436 n
= read(sig_pipe
[0], buf
, sizeof(buf
));
437 if (!n
) lose("(internal) signal pipe closed!");
440 if (errno
!= EAGAIN
&& errno
!= EWOULDBLOCK
)
441 lose("failed to read signal pipe: %s", strerror(errno
));
442 sigprocmask(SIG_SETMASK
, &old
, 0);
444 if (sigismember(&pend
, SIGINT
)) sigloss
= SIGINT
;
445 else if (sigismember(&pend
, SIGHUP
)) sigloss
= SIGHUP
;
446 else if (sigismember(&pend
, SIGTERM
)) sigloss
= SIGTERM
;
447 if (sigismember(&pend
, SIGCHLD
)) reap_children();
450 #define SIGF_IGNOK 1u
451 static void set_signal_handler(const char *what
, int sig
, unsigned f
)
453 struct sigaction sa
, sa_old
;
455 sigaddset(&caught
, sig
);
458 if (sigaction(sig
, 0, &sa_old
)) goto fail
;
459 if (sa_old
.sa_handler
== SIG_IGN
) return;
462 sa
.sa_handler
= handle_signal
;
463 sigemptyset(&sa
.sa_mask
);
464 sa
.sa_flags
= SA_NOCLDSTOP
;
465 if (sigaction(sig
, &sa
, 0)) goto fail
;
470 lose("failed to set %s signal handler: %s", what
, strerror(errno
));
473 static NORETURN
void job_child(struct job
*job
)
476 !(flags
&AF_CHECKINST
) && verbose
>= 2 ? TEF_VERBOSE
: 0);
477 moan("failed to run `%s': %s", job
->av
.v
[0], strerror(errno
));
481 static void start_jobs(void)
483 struct dstr d
= DSTR_INIT
;
484 int p_out
[2], p_err
[2];
488 while (job_ready
&& nrun
< maxrun
) {
489 job
= job_ready
; job_ready
= job
->next
;
490 p_out
[0] = p_out
[1] = p_err
[0] = p_err
[1] = -1;
491 dstr_reset(&d
); dstr_putf(&d
, "%s/%s", tmpdir
, JOB_NAME(job
));
492 if (mkdir(d
.p
, 0700)) {
493 bad("failed to create working directory for job `%s': %s",
494 JOB_NAME(job
), strerror(errno
));
500 dstr_puts(&d
, "/log"); job
->log
= fopen(d
.p
, "w+");
502 lose("failed to open log file `%s': %s", d
.p
, strerror(errno
));
504 if (pipe(p_out
) || pipe(p_err
)) {
505 bad("failed to create pipes for job `%s': %s",
506 JOB_NAME(job
), strerror(errno
));
509 if (configure_fd("job stdout pipe", p_out
[0], 1, 1) ||
510 configure_fd("job stdout pipe", p_out
[1], 0, 1) ||
511 configure_fd("job stderr pipe", p_err
[0], 1, 1) ||
512 configure_fd("job stderr pipe", p_err
[1], 0, 1) ||
513 configure_fd("log file", fileno(job
->log
), 1, 1))
515 job
->out
.buf
= xmalloc(MAXLINE
); job
->out
.off
= job
->out
.len
= 0;
516 job
->out
.fd
= p_out
[0]; p_out
[0] = -1;
517 job
->err
.buf
= xmalloc(MAXLINE
); job
->err
.off
= job
->err
.len
= 0;
518 job
->err
.fd
= p_err
[0]; p_err
[0] = -1;
519 dstr_reset(&d
); argv_string(&d
, &job
->av
);
520 fprintf(job
->log
, "%-13s > starting %s\n", JOB_NAME(job
), d
.p
);
524 bad("failed to fork process for job `%s': %s",
525 JOB_NAME(job
), strerror(errno
));
529 if (dup2(nullfd
, 0) < 0 ||
530 dup2(p_out
[1], 1) < 0 ||
531 dup2(p_err
[1], 2) < 0)
532 lose("failed to juggle job `%s' file descriptors: %s",
533 JOB_NAME(job
), strerror(errno
));
536 close(p_out
[1]); close(p_err
[1]);
538 job
->st
= JST_RUN
; job
->next
= job_run
; job_run
= job
; nrun
++;
541 if (p_out
[0] >= 0) close(p_out
[0]);
542 if (p_out
[1] >= 0) close(p_out
[1]);
543 if (p_err
[0] >= 0) close(p_err
[0]);
544 if (p_err
[1] >= 0) close(p_err
[1]);
550 static void version(FILE *fp
)
551 { fprintf(fp
, "%s, runlisp version %s\n", progname
, PACKAGE_VERSION
); }
553 static void usage(FILE *fp
)
556 usage: %s [-afnqv] [-c CONF] [-o [SECT:]VAR=VAL]\n\
557 [-O FILE|DIR] [-j NJOBS] [LISP ...]\n",
561 static void help(FILE *fp
)
563 version(fp
); fputc('\n', fp
); usage(fp
);
566 -h, --help Show this help text and exit successfully.\n\
567 -V, --version Show version number and exit successfully.\n\
570 -n, --dry-run Don't run run anything (useful with `-v').\n\
571 -q, --quiet Don't print warning messages.\n\
572 -v, --verbose Print informational messages (repeatable).\n\
575 -c, --config-file=CONF Read configuration from CONF (repeatable).\n\
576 -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\
579 -O, --output=FILE|DIR Store image(s) in FILE or DIR.\n\
580 -a, --all-configured Dump all implementations configured.\n\
581 -f, --force Dump images even if they already exist.\n\
582 -i, --check-installed Check Lisp systems exist before invoking.\n\
583 -j, --jobs=NJOBS Run up to NJOBS jobs in parallel.\n",
587 int main(int argc
, char *argv
[])
589 struct config_section_iter si
;
590 struct config_section
*sect
;
591 struct config_var
*var
;
592 const char *out
= 0, *p
, *q
, *l
;
593 struct job
*job
, **tail
, **link
, *next
;
595 struct dstr d
= DSTR_INIT
;
596 int i
, fd
, nfd
, first
;
599 static const struct option opts
[] = {
600 { "help", 0, 0, 'h' },
601 { "version", 0, 0, 'V' },
602 { "output", OPTF_ARGREQ
, 0, 'O' },
603 { "all-configured", 0, 0, 'a' },
604 { "config-file", OPTF_ARGREQ
, 0, 'c' },
605 { "force", OPTF_NEGATE
, 0, 'f' },
606 { "check-installed", OPTF_NEGATE
, 0, 'i' },
607 { "jobs", OPTF_ARGREQ
, 0, 'j' },
608 { "dry-run", OPTF_NEGATE
, 0, 'n' },
609 { "set-option", OPTF_ARGREQ
, 0, 'o' },
610 { "quiet", 0, 0, 'q' },
611 { "verbose", 0, 0, 'v' },
615 set_progname(argv
[0]);
618 optprog
= (/*unconst*/ char *)progname
;
620 i
= mdwopt(argc
- 1, argv
+ 1, "hVO:ac:f+i+j:n+o:qv", opts
, 0, 0,
621 OPTF_NEGATION
| OPTF_NOPROGNAME
);
624 case 'h': help(stdout
); exit(0);
625 case 'V': version(stdout
); exit(0);
626 case 'O': out
= optarg
; break;
627 case 'a': flags
|= AF_ALL
; break;
628 case 'c': read_config_path(optarg
, 0); flags
|= AF_SETCONF
; break;
629 case 'f': flags
|= AF_FORCE
; break;
630 case 'f' | OPTF_NEGATED
: flags
&= ~AF_FORCE
; break;
631 case 'i': flags
|= AF_CHECKINST
; break;
632 case 'i' | OPTF_NEGATED
: flags
&= ~AF_CHECKINST
; break;
633 case 'j': maxrun
= parse_int("number of jobs", optarg
, 1, 65535); break;
634 case 'n': flags
|= AF_DRYRUN
; break;
635 case 'n' | OPTF_NEGATED
: flags
&= ~AF_DRYRUN
; break;
636 case 'o': if (set_config_var(optarg
)) flags
|= AF_BOGUS
; break;
637 case 'q': if (verbose
) verbose
--; break;
638 case 'v': verbose
++; break;
639 default: flags
|= AF_BOGUS
; break;
644 if ((flags
&AF_ALL
) ? optind
< argc
: optind
>= argc
) flags
|= AF_BOGUS
;
645 if (flags
&AF_BOGUS
) { usage(stderr
); exit(2); }
647 if (!(flags
&AF_SETCONF
)) load_default_config();
650 config_set_var(&config
, builtin
, 0,
651 "@IMAGE", "${@CONFIG:image-dir}/${image-file}");
652 else if (stat(out
, &st
) || !S_ISDIR(st
.st_mode
))
653 config_set_var(&config
, builtin
, CF_LITERAL
, "@IMAGE", out
);
655 config_set_var(&config
, builtin
, CF_LITERAL
, "@%OUTDIR", out
);
656 config_set_var(&config
, builtin
, 0,
657 "@IMAGE", "${@BUILTIN:@%OUTDIR}/${image-file}");
662 lose("failed to create signal pipe: %s", strerror(errno
));
663 configure_fd("signal pipe (read end)", sig_pipe
[0], 1, 1);
664 configure_fd("signal pipe (write end)", sig_pipe
[1], 1, 1);
665 sigemptyset(&caught
); sigemptyset(&pending
);
666 set_signal_handler("SIGTERM", SIGTERM
, SIGF_IGNOK
);
667 set_signal_handler("SIGINT", SIGINT
, SIGF_IGNOK
);
668 set_signal_handler("SIGHUP", SIGHUP
, SIGF_IGNOK
);
669 set_signal_handler("SIGCHLD", SIGCHLD
, 0);
672 config_set_var(&config
, builtin
, CF_LITERAL
, "@%TMPDIR", tmpdir
);
673 config_set_var(&config
, builtin
, 0,
674 "@TMPDIR", "${@BUILTIN:@%TMPDIR}/${@NAME}");
676 if (verbose
>= 5) dump_config();
680 for (i
= optind
; i
< argc
; i
++)
681 add_job(&tail
, 0, argv
[i
], strlen(argv
[i
]));
683 var
= config_find_var(&config
, toplevel
, 0, "dump");
685 for (config_start_section_iter(&config
, &si
);
686 (sect
= config_next_section(&si
)); )
687 add_job(&tail
, JF_QUIET
,
688 CONFIG_SECTION_NAME(sect
),
689 CONFIG_SECTION_NAMELEN(sect
));
691 p
= var
->val
; l
= p
+ var
->n
;
693 while (p
< l
&& ISSPACE(*p
)) p
++;
696 while (p
< l
&& !ISSPACE(*p
) && *p
!= ',') p
++;
697 add_job(&tail
, 0, q
, p
- q
);
707 for (job
= job_ready
; job
; job
= job
->next
) {
708 if (first
) first
= 0;
709 else dstr_puts(&d
, ", ");
710 dstr_putf(&d
, "`%s'", JOB_NAME(job
));
712 if (first
) dstr_puts(&d
, "(none)");
714 moan("dumping Lisps: %s", d
.p
);
717 if (flags
&AF_DRYRUN
) {
718 for (job
= job_ready
; job
; job
= job
->next
) {
719 if (try_exec(&job
->av
,
721 (verbose
>= 2 && !(flags
&AF_CHECKINST
) ?
724 else if (verbose
>= 2)
725 printf("%-13s > (not dumping `%s': dry run)\n",
726 JOB_NAME(job
), JOB_NAME(job
));
732 fd
= open("/dev/null", O_RDWR
);
733 if (fd
< 0) lose("failed to open `/dev/null': %s", strerror(errno
));
734 if (fd
> 2) { nullfd
= fd
; break; }
736 configure_fd("null fd", nullfd
, 0, 1);
740 if (!job_run
&& !job_dead
) break;
742 #define SET_FD(dir, fd) do { \
745 FD_SET(_fd, &fd_##dir); \
746 if (_fd >= nfd) nfd = _fd + 1; \
749 FD_ZERO(&fd_in
); nfd
= 0;
750 SET_FD(in
, sig_pipe
[0]);
751 for (job
= job_run
; job
; job
= job
->next
) {
752 if (job
->out
.fd
>= 0) SET_FD(in
, job
->out
.fd
);
753 if (job
->err
.fd
>= 0) SET_FD(in
, job
->err
.fd
);
755 for (job
= job_dead
; job
; job
= job
->next
) {
756 if (job
->out
.fd
>= 0) SET_FD(in
, job
->out
.fd
);
757 if (job
->err
.fd
>= 0) SET_FD(in
, job
->err
.fd
);
762 if (select(nfd
, &fd_in
, 0, 0, 0) < 0) {
763 if (errno
== EINTR
) continue;
764 else lose("select failed: %s", strerror(errno
));
767 if (FD_ISSET(sig_pipe
[0], &fd_in
)) {
770 for (job
= job_ready
; job
; job
= next
)
771 { next
= job
->next
; release_job(job
); }
772 for (job
= job_run
; job
; job
= next
)
773 { next
= job
->next
; release_job(job
); }
774 for (job
= job_dead
; job
; job
= next
)
775 { next
= job
->next
; release_job(job
); }
780 for (job
= job_run
; job
; job
= job
->next
) {
781 if (job
->out
.fd
>= 0 && FD_ISSET(job
->out
.fd
, &fd_in
))
782 prefix_lines(job
, &job
->out
, '|');
783 if (job
->err
.fd
>= 0 && FD_ISSET(job
->err
.fd
, &fd_in
))
784 prefix_lines(job
, &job
->err
, '*');
786 for (link
= &job_dead
, job
= *link
; job
; job
= next
) {
788 if (job
->out
.fd
>= 0 && FD_ISSET(job
->out
.fd
, &fd_in
))
789 prefix_lines(job
, &job
->out
, '|');
790 if (job
->err
.fd
>= 0 && FD_ISSET(job
->err
.fd
, &fd_in
))
791 prefix_lines(job
, &job
->err
, '*');
792 if (job
->out
.fd
>= 0 || job
->err
.fd
>= 0) link
= &job
->next
;
793 else { *link
= next
; finish_job(job
); }
798 if (sigloss
) { cleanup(); signal(sigloss
, SIG_DFL
); raise(sigloss
); }
803 /*----- That's all, folks -------------------------------------------------*/