chiark / gitweb /
@@@ work in progress
[runlisp] / dump-runlisp-image.c
1 /* -*-c-*-
2  *
3  * Dump custom Lisp images for faster script execution
4  *
5  * (c) 2020 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
11  *
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.
16  *
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
20  * for more details.
21  *
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/>.
24  */
25
26 /*----- Header files ------------------------------------------------------*/
27
28 #include "config.h"
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38
39 #include <dirent.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42
43 #include <sys/select.h>
44 #include <sys/stat.h>
45 #include <sys/time.h>
46 #include <sys/uio.h>
47 #include <sys/wait.h>
48
49 #include "common.h"
50 #include "lib.h"
51 #include "mdwopt.h"
52
53 /*----- Static data -------------------------------------------------------*/
54
55 #define MAXLINE 16384u
56 struct linebuf {
57   int fd;
58   char *buf;
59   unsigned off, len;
60 };
61
62 enum {
63   JST_READY,
64   JST_RUN,
65   JST_DEAD,
66   JST_NSTATE
67 };
68
69 struct job {
70   struct treap_node _node;
71   struct job *next;
72   struct argv av;
73   unsigned st;
74   FILE *log;
75   pid_t kid;
76   int exit;
77   struct linebuf out, err;
78 };
79 #define JOB_NAME(job) TREAP_NODE_KEY(job)
80 #define JOB_NAMELEN(job) TREAP_NODE_KEYLEN(job)
81
82 static struct treap jobs = TREAP_INIT;
83 static struct job *job_ready, *job_run, *job_dead;
84 static unsigned nrun, maxrun = 1;
85 static int rc = 0;
86 static int nullfd;
87
88 static int sig_pipe[2] = { -1, -1 };
89 static sigset_t caught, pending;
90 static int sigloss = -1;
91
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
99
100 /*----- Main code ---------------------------------------------------------*/
101
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; }
104
105 static const char *tmpdir;
106
107 static void set_tmpdir(void)
108 {
109   struct dstr d = DSTR_INIT;
110   size_t n;
111   unsigned i;
112
113   dstr_putf(&d, "%s/runlisp.%d.", my_getenv("TMPDIR", "/tmp"), getpid());
114   i = 0; n = d.len;
115   for (;;) {
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",
124            d.p);
125     }
126   }
127   tmpdir = xstrndup(d.p, d.len); dstr_release(&d);
128 }
129
130 static void recursive_delete_(struct dstr *dd)
131 {
132   size_t n = dd->len;
133   DIR *dir;
134   struct dirent *d;
135
136   dd->p[n] = 0;
137   dir = opendir(dd->p);
138   if (!dir)
139     lose("failed to open directory `%s' for cleanup: %s",
140          dd->p, strerror(errno));
141
142   dd->p[n++] = '/';
143   for (;;) {
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])))
147       continue;
148     dd->len = n; dstr_puts(dd, d->d_name);
149     if (!unlink(dd->p));
150     else if (errno == EISDIR) recursive_delete_(dd);
151     else lose("failed to delete file `%s': %s", dd->p, strerror(errno));
152   }
153   closedir(dir);
154   dd->p[--n] = 0;
155   if (rmdir(dd->p))
156     lose("failed to delete directory `%s': %s", dd->p, strerror(errno));
157 }
158
159 static void recursive_delete(const char *path)
160 {
161   struct dstr d = DSTR_INIT;
162   dstr_puts(&d, path); recursive_delete_(&d); dstr_release(&d);
163 }
164
165 static void cleanup(void)
166   { if (tmpdir) { recursive_delete(tmpdir); tmpdir = 0; } }
167
168 static int configure_fd(const char *what, int fd, int nonblock, int cloexec)
169 {
170   int fl, nfl;
171
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;
177   }
178
179   if (cloexec != -1) {
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;
184   }
185
186   return (0);
187
188 fail:
189   bad("failed to configure %s descriptor: %s", what, strerror(errno));
190   return (-1);
191 }
192
193 static void handle_signal(int sig)
194 {
195   sigset_t old;
196   char x = '!';
197
198   sigprocmask(SIG_BLOCK, &caught, &old);
199   sigaddset(&pending, sig);
200   sigprocmask(SIG_SETMASK, &old, 0);
201
202   DISCARD(write(sig_pipe[1], &x, 1));
203 }
204
205 #define JF_QUIET 1u
206 static void add_job(struct job ***tail_inout, unsigned f,
207                     const char *name, size_t len)
208 {
209   struct job *job;
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;
215   unsigned fef;
216
217   job = treap_probe(&jobs, name, len, &path);
218   if (job) {
219     if (verbose >= 2) {
220       moan("ignoring duplicate Lisp `%s'", JOB_NAME(job));
221       return;
222     }
223   }
224
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");
229   if (!dump_var) {
230     if (!(f&JF_QUIET))
231       lose("don't know how to dump images for Lisp implementation `%s'",
232            name);
233     goto end;
234   }
235   cmd_var = config_find_var(&config, sect, 0, "command");
236   if (!cmd_var)
237     lose("no `command' defined for Lisp implementation `%s'", name);
238
239   config_subst_split_var(&config, sect, dump_var, &av);
240   if (!av.n) lose("empty command for Lisp implementation `%s'", name);
241
242   if (flags&AF_CHECKINST) {
243     dstr_reset(&d);
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);
249       goto end;
250     }
251   }
252
253   if (!(flags&AF_FORCE)) {
254     dstr_reset(&d);
255     config_subst_string(&config, sect, "<internal>", "${@IMAGE}", &d);
256     if (!access(d.p, F_OK)) {
257       if (verbose >= 2)
258         moan("image `%s' already exists: skipping `%s'", d.p, name);
259       goto end;
260     }
261   }
262
263   job = xmalloc(sizeof(*job));
264   job->st = JST_READY;
265   job->kid = -1;
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;
271 end:
272   dstr_release(&d); argv_release(&av);
273 }
274
275 static void release_job(struct job *job)
276 {
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);
281   free(job);
282 }
283
284 static void finish_job(struct job *job)
285 {
286   char buf[16483];
287   size_t n;
288   int ok = 0;
289
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; }
294     else
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)],
303 #else
304         "unknown signal",
305 #endif
306 #ifdef WCOREDUMP
307         WCOREDUMP(job->exit) ? "; core dumped" :
308 #endif
309         "");
310   else
311     fprintf(job->log, "exited with incomprehensible status %06o\n",
312             job->exit);
313
314   if (!ok && verbose < 2) {
315     rewind(job->log);
316     for (;;) {
317       n = fread(buf, 1, sizeof(buf), job->log);
318       if (n) fwrite(buf, 1, n, stdout);
319       if (n < sizeof(buf)) break;
320     }
321   }
322
323   release_job(job);
324 }
325
326 static int find_newline(struct linebuf *buf, size_t *linesz_out)
327 {
328   char *nl;
329
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); }
333   } else {
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));
337     if (nl)
338       { *linesz_out = (nl - buf->buf) + (MAXLINE - buf->off); return (0); }
339   }
340   return (-1);
341 }
342
343 static void write_line(struct job *job, struct linebuf *buf,
344                        size_t n, char marker, const char *tail)
345 {
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);
349   else {
350     fwrite(buf->buf + buf->off, 1, MAXLINE - buf->off, job->log);
351     fwrite(buf->buf, 1, n - (MAXLINE - buf->off), job->log);
352   }
353   fputs(tail, job->log);
354 }
355
356 static void prefix_lines(struct job *job, struct linebuf *buf, char marker)
357 {
358   struct iovec iov[2]; int niov;
359   ssize_t n;
360   size_t linesz;
361
362   assert(buf->len < MAXLINE);
363   if (!buf->off) {
364     iov[0].iov_base = buf->buf + buf->len;
365     iov[0].iov_len = MAXLINE - buf->len;
366     niov = 1;
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;
370     niov = 1;
371   } else {
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;
376     niov = 1;
377   }
378
379   n = readv(buf->fd, iov, niov);
380   if (n < 0) {
381     if (errno == EAGAIN || errno == EWOULDBLOCK) return;
382     lose("failed to read job `%s' output stream: %s",
383          JOB_NAME(job), strerror(errno));
384   }
385   buf->len += n;
386
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;
391   }
392   if (!buf->len)
393     buf->off = 0;
394   else if (buf->len == MAXLINE) {
395     write_line(job, buf, MAXLINE, marker, " [...]\n");
396     buf->off = buf->len = 0;
397   }
398
399   if (!n) {
400     close(buf->fd); buf->fd = -1;
401     if (buf->len)
402       write_line(job, buf, buf->len, marker, " [missing final newline]\n");
403   }
404 }
405
406 static void reap_children(void)
407 {
408   struct job *job, **link;
409   pid_t kid;
410   int st;
411
412   for (;;) {
413     kid = waitpid(0, &st, WNOHANG);
414     if (kid <= 0) break;
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);
418     continue;
419   found:
420     job->exit = st; job->st = JST_DEAD; job->kid = -1; nrun--;
421     *link = job->next; job->next = job_dead; job_dead = job;
422   }
423   if (kid < 0 && errno != ECHILD)
424     lose("failed to collect child process exit status: %s", strerror(errno));
425 }
426
427 static void check_signals(void)
428 {
429   sigset_t old, pend;
430   char buf[32];
431   ssize_t n;
432
433   sigprocmask(SIG_BLOCK, &caught, &old);
434   pend = pending; sigemptyset(&pending);
435   for (;;) {
436     n = read(sig_pipe[0], buf, sizeof(buf));
437     if (!n) lose("(internal) signal pipe closed!");
438     if (n < 0) break;
439   }
440   if (errno != EAGAIN && errno != EWOULDBLOCK)
441     lose("failed to read signal pipe: %s", strerror(errno));
442   sigprocmask(SIG_SETMASK, &old, 0);
443
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();
448 }
449
450 #define SIGF_IGNOK 1u
451 static void set_signal_handler(const char *what, int sig, unsigned f)
452 {
453   struct sigaction sa, sa_old;
454
455   sigaddset(&caught, sig);
456
457   if (f&SIGF_IGNOK) {
458     if (sigaction(sig, 0, &sa_old)) goto fail;
459     if (sa_old.sa_handler == SIG_IGN) return;
460   }
461
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;
466
467   return;
468
469 fail:
470   lose("failed to set %s signal handler: %s", what, strerror(errno));
471 }
472
473 static NORETURN void job_child(struct job *job)
474 {
475   try_exec(&job->av,
476            !(flags&AF_CHECKINST) && verbose >= 2 ? TEF_VERBOSE : 0);
477   moan("failed to run `%s': %s", job->av.v[0], strerror(errno));
478   _exit(2);
479 }
480
481 static void start_jobs(void)
482 {
483   struct dstr d = DSTR_INIT;
484   int p_out[2], p_err[2];
485   struct job *job;
486   pid_t kid;
487
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));
495       goto fail;
496     }
497     if (verbose >= 2)
498       job->log = stdout;
499     else {
500       dstr_puts(&d, "/log"); job->log = fopen(d.p, "w+");
501       if (!job->log)
502         lose("failed to open log file `%s': %s", d.p, strerror(errno));
503     }
504     if (pipe(p_out) || pipe(p_err)) {
505       bad("failed to create pipes for job `%s': %s",
506           JOB_NAME(job), strerror(errno));
507       goto fail;
508     }
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))
514       goto fail;
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);
521     fflush(stdout);
522     kid = fork();
523     if (kid < 0) {
524       bad("failed to fork process for job `%s': %s",
525           JOB_NAME(job), strerror(errno));
526       goto fail;
527     }
528     if (!kid) {
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));
534       job_child(job);
535     }
536     close(p_out[1]); close(p_err[1]);
537     job->kid = kid;
538     job->st = JST_RUN; job->next = job_run; job_run = job; nrun++;
539     continue;
540   fail:
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]);
545     release_job(job);
546   }
547   dstr_release(&d);
548 }
549
550 static void version(FILE *fp)
551   { fprintf(fp, "%s, runlisp version %s\n", progname, PACKAGE_VERSION); }
552
553 static void usage(FILE *fp)
554 {
555   fprintf(fp, "\
556 usage: %s [-afnqv] [-c CONF] [-o [SECT:]VAR=VAL]\n\
557         [-O FILE|DIR] [-j NJOBS] [LISP ...]\n",
558           progname);
559 }
560
561 static void help(FILE *fp)
562 {
563   version(fp); fputc('\n', fp); usage(fp);
564   fputs("\n\
565 Help options:\n\
566   -h, --help                    Show this help text and exit successfully.\n\
567   -V, --version                 Show version number and exit successfully.\n\
568 \n\
569 Diagnostics:\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\
573 \n\
574 Configuration:\n\
575   -c, --config-file=CONF        Read configuration from CONF (repeatable).\n\
576   -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\
577 \n\
578 Image dumping:\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",
584         fp);
585 }
586
587 int main(int argc, char *argv[])
588 {
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;
594   struct stat st;
595   struct dstr d = DSTR_INIT;
596   int i, fd, nfd, first;
597   fd_set fd_in;
598
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' },
612     { 0,                        0,              0,      0 }
613   };
614
615   set_progname(argv[0]);
616   init_config();
617
618   optprog = (/*unconst*/ char *)progname;
619   for (;;) {
620     i = mdwopt(argc - 1, argv + 1, "hVO:ac:f+i+j:n+o:qv", opts, 0, 0,
621                OPTF_NEGATION | OPTF_NOPROGNAME);
622     if (i < 0) break;
623     switch (i) {
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;
640     }
641   }
642
643   optind++;
644   if ((flags&AF_ALL) ? optind < argc : optind >= argc) flags |= AF_BOGUS;
645   if (flags&AF_BOGUS) { usage(stderr); exit(2); }
646
647   if (!(flags&AF_SETCONF)) load_default_config();
648
649   if (!out)
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);
654   else {
655     config_set_var(&config, builtin, CF_LITERAL, "@%OUTDIR", out);
656     config_set_var(&config, builtin, 0,
657                    "@IMAGE", "${@BUILTIN:@%OUTDIR}/${image-file}");
658   }
659
660   atexit(cleanup);
661   if (pipe(sig_pipe))
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);
670
671   set_tmpdir();
672   config_set_var(&config, builtin, CF_LITERAL, "@%TMPDIR", tmpdir);
673   config_set_var(&config, builtin, 0,
674                  "@TMPDIR", "${@BUILTIN:@%TMPDIR}/${@NAME}");
675
676   if (verbose >= 5) dump_config();
677
678   tail = &job_ready;
679   if (!(flags&AF_ALL))
680     for (i = optind; i < argc; i++)
681       add_job(&tail, 0, argv[i], strlen(argv[i]));
682   else {
683     var = config_find_var(&config, toplevel, 0, "dump");
684     if (!var)
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));
690     else {
691       p = var->val; l = p + var->n;
692       for (;;) {
693         while (p < l && ISSPACE(*p)) p++;
694         if (p >= l) break;
695         q = p;
696         while (p < l && !ISSPACE(*p) && *p != ',') p++;
697         add_job(&tail, 0, q, p - q);
698         if (p < l) p++;
699       }
700     }
701   }
702   *tail = 0;
703
704   if (verbose >= 3) {
705     dstr_reset(&d);
706     first = 1;
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));
711     }
712     if (first) dstr_puts(&d, "(none)");
713     dstr_putz(&d);
714     moan("dumping Lisps: %s", d.p);
715   }
716
717   if (flags&AF_DRYRUN) {
718     for (job = job_ready; job; job = job->next) {
719       if (try_exec(&job->av,
720                    TEF_DRYRUN |
721                      (verbose >= 2 && !(flags&AF_CHECKINST) ?
722                        TEF_VERBOSE : 0)))
723         rc = 2;
724       else if (verbose >= 2)
725         printf("%-13s > (not dumping `%s': dry run)\n",
726                 JOB_NAME(job), JOB_NAME(job));
727     }
728     return (rc);
729   }
730
731   for (;;) {
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; }
735   }
736   configure_fd("null fd", nullfd, 0, 1);
737
738   for (;;) {
739     start_jobs();
740     if (!job_run && !job_dead) break;
741
742 #define SET_FD(dir, fd) do {                                            \
743   int _fd = (fd);                                                       \
744                                                                         \
745   FD_SET(_fd, &fd_##dir);                                               \
746   if (_fd >= nfd) nfd = _fd + 1;                                        \
747 } while (0)
748
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);
754     }
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);
758     }
759
760 #undef SET_FD
761
762     if (select(nfd, &fd_in, 0, 0, 0) < 0) {
763       if (errno == EINTR) continue;
764       else lose("select failed: %s", strerror(errno));
765     }
766
767     if (FD_ISSET(sig_pipe[0], &fd_in)) {
768       check_signals();
769       if (sigloss >= 0) {
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); }
776         break;
777       }
778     }
779
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, '*');
785     }
786     for (link = &job_dead, job = *link; job; job = next) {
787       next = 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); }
794     }
795   }
796
797   check_signals();
798   if (sigloss) { cleanup(); signal(sigloss, SIG_DFL); raise(sigloss); }
799
800   return (rc);
801 }
802
803 /*----- That's all, folks -------------------------------------------------*/