Commit | Line | Data |
---|---|---|
7b8ff279 MW |
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 -------------------------------------------------*/ |