@@@ 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 -------------------------------------------------*/