chiark / gitweb /
New version.
[runlisp] / runlisp.c
1 /* -*-c-*-
2  *
3  * Invoke a Lisp script
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 <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <unistd.h>
39 #include <sys/stat.h>
40
41 #include <pwd.h>
42
43 /*----- Common Lisp runes -------------------------------------------------*/
44
45 /* A common preamble rune to do the necessary things.
46  *
47  * We need to ensure that `asdf' (and therefore `uiop') is loaded.  And we
48  * should arrange for `:runlisp-script' to find its way into the `*features*'
49  * list so that scripts can notice that they're being invoked from the
50  * command line rather than loaded into a resident session, and actually do
51  * something useful.
52  */
53 #define COMMON_PRELUDE_RUNE                                             \
54         "(progn "                                                       \
55           "(setf *load-verbose* nil *compile-verbose* nil) "            \
56           "(require \"asdf\") "                                         \
57           "(funcall (intern \"REGISTER-IMMUTABLE-SYSTEM\" "             \
58                            "(find-package \"ASDF\")) "                  \
59                    "\"asdf\") "                                         \
60           "(set-dispatch-macro-character "                              \
61            "#\\# #\\! "                                                 \
62            "(lambda (#1=#:stream #2=#:char #3=#:arg) "                  \
63              "(declare (ignore #2# #3#)) "                              \
64              "(values (read-line #1#)))) "                              \
65           "(pushnew :runlisp-script *features*))"
66
67 /* Get `uiop' to re-check the command-line arguments following an image
68  * restore.
69  */
70 #define IMAGE_RESTORE_RUNE                                              \
71         "(uiop:call-image-restore-hook)"
72
73 /* Some Lisps leave crud in the `COMMON-LISP-USER' package.  Clear it out. */
74 #define CLEAR_CL_USER_RUNE                                              \
75         "(let ((#4=#:pkg (find-package \"COMMON-LISP-USER\"))) "        \
76           "(with-package-iterator (#5=#:next #4# :internal) "           \
77             "(loop (multiple-value-bind (#6=#:anyp #7=#:sym #8=#:how) " \
78                       "(#5#) "                                          \
79                     "(declare (ignore #8#)) "                           \
80                     "(unless #6# (return)) "                            \
81                     "(unintern #7# #4#)))))"
82
83 /*----- Handy macros ------------------------------------------------------*/
84
85 #define N(v) (sizeof(v)/sizeof((v)[0]))
86
87 #if defined(__GNUC__)
88 #  define GCC_VERSION_P(maj, min)                                       \
89         (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
90 #else
91 #  define GCC_VERSION_P(maj, min) 0
92 #endif
93
94 #ifdef __clang__
95 #  define CLANG_VERSION_P(maj, min)                                     \
96         (__clang_major__ > (maj) || (__clang_major__ == (maj) &&        \
97                                      __clang_minor__ >= (min)))
98 #else
99 #  define CLANG_VERSION_P(maj, min) 0
100 #endif
101
102 #if GCC_VERSION_P(2, 5) || CLANG_VERSION_P(3, 3)
103 #  define NORETURN __attribute__((__noreturn__))
104 #  define PRINTF_LIKE(fix, aix) __attribute__((__format__(printf, fix, aix)))
105 #endif
106
107 #if GCC_VERSION_P(4, 0) || CLANG_VERSION_P(3, 3)
108 #  define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
109 #endif
110
111 #define CTYPE_HACK(func, ch) (func((unsigned char)(ch)))
112 #define ISSPACE(ch) CTYPE_HACK(isspace, ch)
113
114 #define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0)
115 #define STRCMP(x, op, y) (strcmp((x), (y)) op 0)
116 #define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0)
117
118 #define END ((const char *)0)
119
120 /*----- The Lisp implementation table -------------------------------------*/
121
122 /* The systems, in decreasing order of (not quite my personal) preference.
123  * This list is used to initialize various tables and constants.
124  */
125 #define LISP_SYSTEMS(_)                                                 \
126         _(sbcl)                                                         \
127         _(ccl)                                                          \
128         _(clisp)                                                        \
129         _(ecl)                                                          \
130         _(cmucl)                                                        \
131         _(abcl)
132
133 enum {
134 #define DEFSYS(sys) sys##_INDEX,
135   LISP_SYSTEMS(DEFSYS)
136 #undef DEFSYS
137   NSYS
138 };
139
140 enum {
141 #define DEFFLAG(sys) sys##_FLAG = 1 << sys##_INDEX,
142   LISP_SYSTEMS(DEFFLAG)
143 #undef DEFFLAG
144   ALL_SYSTEMS = 0
145 #define SETFLAG(sys) | sys##_FLAG
146   LISP_SYSTEMS(SETFLAG)
147 #undef SETFLAG
148 };
149
150 struct argstate;
151 struct argv;
152
153 #define DECLENTRY(sys) \
154 static void run_##sys(struct argstate *, const char *);
155   LISP_SYSTEMS(DECLENTRY)
156 #undef DECLENTRY
157
158 static const struct systab {
159   const char *name;
160   unsigned f;
161   void (*run)(struct argstate *, const char *);
162 } systab[] = {
163 #define SYSENTRY(sys) { #sys, sys##_FLAG, run_##sys },
164   LISP_SYSTEMS(SYSENTRY)
165 #undef SYSENTRY
166 };
167
168 /*----- Diagnostic utilities ----------------------------------------------*/
169
170 static const char *progname = "runlisp";
171
172 static void set_progname(const char *prog)
173 {
174   const char *p;
175
176   p = strrchr(prog, '/');
177   progname = p ? p + 1 : progname;
178 }
179
180 static void vmoan(const char *msg, va_list ap)
181 {
182   fprintf(stderr, "%s: ", progname);
183   vfprintf(stderr, msg, ap);
184   fputc('\n', stderr);
185 }
186
187 static PRINTF_LIKE(1, 2) void moan(const char *msg, ...)
188   { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); }
189
190 static NORETURN PRINTF_LIKE(1, 2) void lose(const char *msg, ...)
191   { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); exit(127); }
192
193 /*----- Memory allocation -------------------------------------------------*/
194
195 static void *xmalloc(size_t n)
196 {
197   void *p;
198
199   if (!n) return (0);
200   p = malloc(n); if (!p) lose("failed to allocate memory");
201   return (p);
202 }
203
204 static void *xrealloc(void *p, size_t n)
205 {
206   if (!n) { free(p); return (0); }
207   else if (!p) return (xmalloc(n));
208   p = realloc(p, n); if (!p) lose("failed to allocate memory");
209   return (p);
210 }
211
212 static char *xstrdup(const char *p)
213 {
214   size_t n = strlen(p) + 1;
215   char *q = xmalloc(n);
216
217   memcpy(q, p, n);
218   return (q);
219 }
220
221 /*----- Dynamic strings ---------------------------------------------------*/
222
223 struct dstr {
224   char *p;
225   size_t len, sz;
226 };
227 #define DSTR_INIT { 0, 0, 0 }
228
229 /*
230 static void dstr_init(struct dstr *d) { d->p = 0; d->len = d->sz = 0; }
231 */
232
233 static void dstr_reset(struct dstr *d) { d->len = 0; }
234
235 static void dstr_ensure(struct dstr *d, size_t n)
236 {
237   size_t need = d->len + n, newsz;
238
239   if (need <= d->sz) return;
240   newsz = d->sz ? 2*d->sz : 16;
241   while (newsz < need) newsz *= 2;
242   d->p = xrealloc(d->p, newsz); d->sz = newsz;
243 }
244
245 static void dstr_release(struct dstr *d) { free(d->p); }
246
247 static void dstr_putm(struct dstr *d, const void *p, size_t n)
248   { dstr_ensure(d, n); memcpy(d->p + d->len, p, n); d->len += n; }
249
250 static void dstr_puts(struct dstr *d, const char *p)
251 {
252   size_t n = strlen(p);
253
254   dstr_ensure(d, n + 1);
255   memcpy(d->p + d->len, p, n + 1);
256   d->len += n;
257 }
258
259 static void dstr_putc(struct dstr *d, int ch)
260   { dstr_ensure(d, 1); d->p[d->len++] = ch; }
261
262 static void dstr_putz(struct dstr *d)
263   { dstr_ensure(d, 1); d->p[d->len] = 0; }
264
265 static int dstr_readline(struct dstr *d, FILE *fp)
266 {
267   size_t n;
268   int any = 0;
269
270   for (;;) {
271     dstr_ensure(d, 2);
272     if (!fgets(d->p + d->len, d->sz - d->len, fp)) break;
273     n = strlen(d->p + d->len); assert(n > 0); any = 1;
274     d->len += n;
275     if (d->p[d->len - 1] == '\n') { d->p[--d->len] = 0; break; }
276   }
277
278   if (!any) return (-1);
279   else return (0);
280 }
281 /*----- Dynamic vectors of strings ----------------------------------------*/
282
283 struct argv {
284   const char **v;
285   size_t o, n, sz;
286 };
287 #define ARGV_INIT { 0, 0, 0, 0 }
288
289 /*
290 static void argv_init(struct argv *av)
291   { av->v = 0; av->o = av->n = av->sz = 0; }
292 */
293
294 /*
295 static void argv_reset(struct argv *av) { av->o = av->n = 0; }
296 */
297
298 static void argv_ensure(struct argv *av, size_t n)
299 {
300   size_t need = av->n + av->o + n, newsz;
301
302   if (need <= av->sz) return;
303   newsz = av->sz ? 2*av->sz : 8;
304   while (newsz < need) newsz *= 2;
305   av->v = xrealloc(av->v, newsz*sizeof(const char *)); av->sz = newsz;
306 }
307
308 static void argv_ensure_offset(struct argv *av, size_t n)
309 {
310   size_t newoff;
311
312   /* Stupid version.  We won't, in practice, be prepending lots of stuff, so
313    * avoid the extra bookkeeping involved in trying to make a double-ended
314    * extendable array asymptotically efficient.
315    */
316   if (av->o >= n) return;
317   newoff = 16;
318   while (newoff < n) newoff *= 2;
319   argv_ensure(av, newoff - av->o);
320   memmove(av->v + newoff, av->v + av->o, av->n*sizeof(const char *));
321   av->o = newoff;
322 }
323
324 static void argv_release(struct argv *av) { free(av->v); }
325
326 static void argv_append(struct argv *av, const char *p)
327   { argv_ensure(av, 1); av->v[av->n++ + av->o] = p; }
328
329 static void argv_appendz(struct argv *av)
330   { argv_ensure(av, 1); av->v[av->n + av->o] = 0; }
331
332 static void argv_appendn(struct argv *av, const char *const *v, size_t n)
333 {
334   argv_ensure(av, n);
335   memcpy(av->v + av->n + av->o, v, n*sizeof(const char *));
336   av->n += n;
337 }
338
339 /*
340 static void argv_appendav(struct argv *av, const struct argv *bv)
341   { argv_appendn(av, bv->v + bv->o, bv->n); }
342 */
343
344 /*
345 static void argv_appendv(struct argv *av, va_list ap)
346 {
347   const char *p;
348
349   for (;;)
350     { p = va_arg(ap, const char *); if (!p) break; argv_append(av, p); }
351 }
352 */
353
354 /*
355 static EXECL_LIKE(0) void argv_appendl(struct argv *av, ...)
356   { va_list ap; va_start(ap, av); argv_appendv(av, ap); va_end(ap); }
357 */
358
359 static void argv_prepend(struct argv *av, const char *p)
360   { argv_ensure_offset(av, 1); av->v[--av->o] = p; av->n++; }
361
362 /*
363 static void argv_prependn(struct argv *av, const char *const *v, size_t n)
364 {
365   argv_ensure_offset(av, 1);
366   av->o -= n; av->n += n;
367   memcpy(av->v + av->o, v, n*sizeof(const char *));
368 }
369 */
370
371 /*
372 static void argv_prependav(struct argv *av, const struct argv *bv)
373   { argv_prependn(av, bv->v + bv->o, bv->n); }
374 */
375
376 static void argv_prependv(struct argv *av, va_list ap)
377 {
378   const char *p, **v;
379   size_t n = 0;
380
381   for (;;) {
382     p = va_arg(ap, const char *); if (!p) break;
383     argv_prepend(av, p); n++;
384   }
385   v = av->v + av->o;
386   while (n >= 2) {
387     p = v[0]; v[0] = v[n - 1]; v[n - 1] = p;
388     v++; n -= 2;
389   }
390 }
391
392 static EXECL_LIKE(0) void argv_prependl(struct argv *av, ...)
393   { va_list ap; va_start(ap, av); argv_prependv(av, ap); va_end(ap); }
394
395 /*----- Lisp system table (redux) -----------------------------------------*/
396
397 static const struct systab *find_system(const char *name)
398 {
399   const struct systab *sys;
400   size_t i;
401
402   for (i = 0; i < NSYS; i++) {
403     sys = &systab[i];
404     if (STRCMP(name, ==, sys->name)) return (sys);
405   }
406   lose("unknown Lisp system `%s'", name);
407 }
408
409 static void lisp_quote_string(struct dstr *d, const char *p)
410 {
411   size_t n;
412
413   for (;;) {
414     n = strcspn(p, "\"\\");
415     if (n) { dstr_putm(d, p, n); p += n; }
416     if (!*p) break;
417     dstr_putc(d, '\\'); dstr_putc(d, *p++);
418   }
419   dstr_putz(d);
420 }
421
422 static const char *expand_rune(struct dstr *d, const char *rune, ...)
423 {
424   const struct argv *av;
425   va_list ap;
426   size_t i, n;
427
428   va_start(ap, rune);
429   for (;;) {
430     n = strcspn(rune, "%");
431     if (n) { dstr_putm(d, rune, n); rune += n; }
432     if (!*rune) break;
433     switch (*++rune) {
434       case '%': dstr_putc(d, '%'); break;
435       case 'e': lisp_quote_string(d, va_arg(ap, const char *)); break;
436       case 'E':
437         av = va_arg(ap, const struct argv *);
438         for (i = 0; i < av->n; i++) {
439           if (i) dstr_putc(d, ' ');
440           dstr_putc(d, '"');
441           lisp_quote_string(d, av->v[i]);
442           dstr_putc(d, '"');
443         }
444         break;
445       default: lose("*** BUG unknown expansion `%%%c'", *rune);
446     }
447     rune++;
448   }
449   dstr_putz(d);
450   return (d->p);
451 }
452
453 /*----- Argument processing -----------------------------------------------*/
454
455 struct syslist {
456   const struct systab *sys[NSYS];
457   size_t n;
458   unsigned f;
459 };
460 #define SYSLIST_INIT { { 0 }, 0, 0 }
461
462 struct argstate {
463   unsigned f;
464 #define F_BOGUS 1u
465 #define F_NOEMBED 2u
466 #define F_NOACT 4u
467 #define F_NODUMP 8u
468 #define F_AUX 16u
469   int verbose;
470   char *imagedir;
471   struct syslist allow, pref;
472   struct argv av;
473 };
474 #define ARGSTATE_INIT { 0, 1, 0, SYSLIST_INIT, SYSLIST_INIT, ARGV_INIT }
475
476 /*----- Running programs --------------------------------------------------*/
477
478 #define FEF_EXEC 1u
479 static int file_exists_p(const struct argstate *arg, const char *path,
480                          unsigned f)
481 {
482   struct stat st;
483
484   if (stat(path, &st)) {
485     if (arg && arg->verbose > 2) moan("file `%s' not found", path);
486     return (0);
487   } else if (!(S_ISREG(st.st_mode))) {
488     if (arg && arg->verbose > 2) moan("`%s' is not a regular file", path);
489     return (0);
490   } else if ((f&FEF_EXEC) && access(path, X_OK)) {
491     if (arg && arg->verbose > 2) moan("file `%s' is not executable", path);
492     return (0);
493   } else {
494     if (arg && arg->verbose > 2) moan("found file `%s'", path);
495     return (1);
496   }
497 }
498
499 static int found_in_path_p(const struct argstate *arg, const char *prog)
500 {
501   struct dstr p = DSTR_INIT, d = DSTR_INIT;
502   const char *path;
503   char *q;
504   size_t n, avail, proglen;
505   int i;
506
507   if (strchr(prog, '/')) return (file_exists_p(arg, prog, 0));
508   path = getenv("PATH");
509   if (path)
510     dstr_puts(&p, path);
511   else {
512     dstr_puts(&p, ".:");
513     i = 0;
514   again:
515     avail = p.sz - p.len;
516     n = confstr(_CS_PATH, p.p + p.len, avail);
517     if (avail > n) { i++; assert(i < 2); dstr_ensure(&p, n); goto again; }
518   }
519
520   q = p.p; proglen = strlen(prog);
521   for (;;) {
522     n = strcspn(q, ":");
523     dstr_reset(&d);
524     if (q[n]) dstr_putm(&d, q, n);
525     else dstr_putc(&d, '.');
526     dstr_putc(&d, '/');
527     dstr_putm(&d, prog, proglen);
528     dstr_putz(&d);
529     if (file_exists_p(arg, d.p, FEF_EXEC)) {
530       if (arg->verbose == 2) moan("found program `%s'", d.p);
531       return (1);
532     }
533     q += n; if (!*q) break; else q++;
534   }
535   return (0);
536 }
537
538 static void try_exec(const struct argstate *arg, struct argv *av)
539 {
540   struct dstr d = DSTR_INIT;
541   size_t i;
542
543   assert(av->n); argv_appendz(av);
544   if (arg->verbose > 1) {
545     for (i = 0; i < av->n; i++) {
546       if (i) { dstr_putc(&d, ','); dstr_putc(&d, ' '); }
547       dstr_putc(&d, '"');
548       lisp_quote_string(&d, av->v[av->o + i]);
549       dstr_putc(&d, '"');
550     }
551     dstr_putz(&d);
552     moan("trying %s...", d.p);
553   }
554   if (arg->f&F_NOACT)
555     { if (found_in_path_p(arg, av->v[av->o])) exit(0); }
556   else {
557     execvp(av->v[av->o], (/*unconst*/ char **)av->v + av->o);
558     if (errno != ENOENT)
559       lose("failed to exec `%s': %s", av->v[av->o], strerror(errno));
560   }
561   if (arg->verbose > 1) moan("`%s' not found", av->v[av->o]);
562   dstr_release(&d);
563 }
564
565 static const char *getenv_or_default(const char *var, const char *dflt)
566   { const char *p = getenv(var); return (p ? p : dflt); }
567
568 /*----- Invoking Lisp systems ---------------------------------------------*/
569
570 /* Steel Bank Common Lisp. */
571
572 static void run_sbcl(struct argstate *arg, const char *script)
573 {
574   struct dstr d = DSTR_INIT;
575
576   argv_prependl(&arg->av, "--script", script, END);
577
578   dstr_puts(&d, arg->imagedir);
579   dstr_putc(&d, '/');
580   dstr_puts(&d, "sbcl+asdf.core");
581   if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
582     argv_prependl(&arg->av,
583                   "--core", d.p,
584                   "--eval", IMAGE_RESTORE_RUNE,
585                   END);
586   else
587     argv_prependl(&arg->av, "--eval", COMMON_PRELUDE_RUNE, END);
588
589   argv_prependl(&arg->av, getenv_or_default("SBCL", "sbcl"),
590                 "--noinform",
591                 END);
592   try_exec(arg, &arg->av);
593   dstr_release(&d);
594 }
595
596 /* Clozure Common Lisp. */
597
598 #define CCL_QUIT_RUNE                                                   \
599         "(ccl:quit)"
600
601 static void run_ccl(struct argstate *arg, const char *script)
602 {
603   struct dstr d = DSTR_INIT;
604
605   argv_prependl(&arg->av, "-b", "-n", "-Q",
606                 "-l", script,
607                 "-e", CCL_QUIT_RUNE,
608                 "--",
609                 END);
610
611   dstr_puts(&d, arg->imagedir);
612   dstr_putc(&d, '/');
613   dstr_puts(&d, "ccl+asdf.image");
614   if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
615     argv_prependl(&arg->av, "-I", d.p, "-e", IMAGE_RESTORE_RUNE, END);
616   else
617     argv_prependl(&arg->av, "-e", COMMON_PRELUDE_RUNE, END);
618
619   argv_prepend(&arg->av, getenv_or_default("CCL", "ccl"));
620   try_exec(arg, &arg->av);
621   dstr_release(&d);
622 }
623
624 /* GNU CLisp.
625  *
626  * CLisp causes much sadness.  Superficially, it's the most sensible of all
627  * of the systems supported here: you just run `clisp SCRIPT -- ARGS ...' and
628  * it works.
629  *
630  * The problems come when you want to do some preparatory work (e.g., load
631  * `asdf') and then run the script.  There's a `-x' option to evaluate some
632  * Lisp code, but it has three major deficiencies.
633  *
634  *   * It insists on printing the values of the forms it evaluates.  It
635  *     prints a blank line even if the form goes out of its way to produce no
636  *     values at all.  So the whole thing has to be a single top-level form
637  *     which quits the Lisp rather than returning.
638  *
639  *   * For some idiotic reason, you can have /either/ `-x' forms /or/ a
640  *     script, but not both.  So we have to include the `load' here
641  *     explicitly.  I suppose that was inevitable because we have to inhibit
642  *     printing of the result forms, but it's still a separate source of
643  *     annoyance.
644  *
645  *   * The icing on the cake: the `-x' forms are collectively concatenated --
646  *     without spaces! -- and used to build a string stream, which is then
647  *     assigned over the top of `*standard-input*', making the original stdin
648  *     somewhat fiddly to track down.
649  *
650  * There's an `-i' option which will load a file without any of this
651  * stupidity, but nothing analogous for immediate expressions.
652  */
653
654 #define CLISP_COMMON_STARTUP_RUNES                                      \
655         "(setf *standard-input* (ext:make-stream :input)) "             \
656         "(load \"%e\" :verbose nil :print nil) "                        \
657         "(ext:quit)"
658
659 #define CLISP_STARTUP_RUNE                                              \
660         "(progn "                                                       \
661            COMMON_PRELUDE_RUNE " "                                      \
662            CLISP_COMMON_STARTUP_RUNES ")"
663
664 #define CLISP_STARTUP_IMAGE_RUNE                                        \
665         "(progn "                                                       \
666            IMAGE_RESTORE_RUNE " "                                       \
667            CLISP_COMMON_STARTUP_RUNES ")"
668
669 static void run_clisp(struct argstate *arg, const char *script)
670 {
671   struct dstr d = DSTR_INIT, dd = DSTR_INIT;
672
673   dstr_puts(&d, arg->imagedir);
674   dstr_putc(&d, '/');
675   dstr_puts(&d, "clisp+asdf.mem");
676   if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
677     argv_prependl(&arg->av, "-M", d.p, "-q",
678                   "-x", expand_rune(&dd, CLISP_STARTUP_IMAGE_RUNE, script),
679                   "--",
680                   END);
681   else
682     argv_prependl(&arg->av, "-norc", "-q",
683                   "-x", expand_rune(&dd, CLISP_STARTUP_RUNE, script),
684                   "--",
685                   END);
686
687   argv_prepend(&arg->av, getenv_or_default("CLISP", "clisp"));
688   try_exec(arg, &arg->av);
689   dstr_release(&d);
690   dstr_release(&dd);
691
692 #undef f
693 }
694
695 /* Embeddable Common Lisp. *
696  *
697  * ECL is changing its command-line option syntax in version 16.  I have no
698  * idea why they think the result can ever be worth the pain of a transition.
699  */
700
701 #if ECL_OPTIONS_GNU
702 #  define ECLOPT "--"
703 #else
704 #  define ECLOPT "-"
705 #endif
706
707 #define ECL_STARTUP_RUNE                                                \
708         "(progn "                                                       \
709            COMMON_PRELUDE_RUNE " "                                      \
710            CLEAR_CL_USER_RUNE ")"
711
712 static void run_ecl(struct argstate *arg, const char *script)
713 {
714   struct dstr d = DSTR_INIT;
715
716   dstr_puts(&d, arg->imagedir);
717   dstr_putc(&d, '/');
718   dstr_puts(&d, "ecl+asdf");
719   if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, FEF_EXEC)) {
720     argv_prependl(&arg->av, "-s", script, "--", END);
721     argv_prependl(&arg->av, d.p, END);
722   } else {
723     argv_prependl(&arg->av, ECLOPT "shell", script, "--", END);
724     argv_prependl(&arg->av, getenv_or_default("ECL", "ecl"), ECLOPT "norc",
725                   ECLOPT "eval", ECL_STARTUP_RUNE,
726                   END);
727   }
728   try_exec(arg, &arg->av);
729 }
730
731 /* Carnegie--Mellon University Common Lisp. */
732
733 #define CMUCL_STARTUP_RUNE                                              \
734         "(progn "                                                       \
735           "(setf ext:*require-verbose* nil) "                           \
736           COMMON_PRELUDE_RUNE ")"
737 #define CMUCL_QUIT_RUNE                                                 \
738         "(ext:quit)"
739
740 static void run_cmucl(struct argstate *arg, const char *script)
741 {
742   struct dstr d = DSTR_INIT;
743
744   argv_prependl(&arg->av,
745                 "-load", script,
746                 "-eval", CMUCL_QUIT_RUNE,
747                 "--",
748                 END);
749
750   dstr_puts(&d, arg->imagedir);
751   dstr_putc(&d, '/');
752   dstr_puts(&d, "cmucl+asdf.core");
753   if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
754     argv_prependl(&arg->av, "-core", d.p, "-eval", IMAGE_RESTORE_RUNE, END);
755   else
756     argv_prependl(&arg->av, "-batch", "-noinit", "-nositeinit", "-quiet",
757                   "-eval", CMUCL_STARTUP_RUNE,
758                   END);
759
760   argv_prepend(&arg->av, getenv_or_default("CMUCL", "cmucl"));
761   try_exec(arg, &arg->av);
762   dstr_release(&d);
763 }
764
765 /* Armed Bear Common Lisp. *
766  *
767  * CLisp made a worthy effort, but ABCL still manages to take the price.
768  *
769  *   * ABCL manages to avoid touching the `stderr' stream at all, ever.  Its
770  *     startup machinery finds `stdout' (as `java.lang.System.out'), wraps it
771  *     up in a Lisp stream, and uses the result as `*standard-output*' and
772  *     `*error-output*' (and a goodly number of other things too).  So we
773  *     must manufacture a working `stderr' the hard way.
774  *
775  *   * There doesn't appear to be any easy way to prevent toplevel errors
776  *     from invoking the interactive debugger.  For extra fun, the debugger
777  *     reads from `stdin' by default, so an input file which somehow manages
778  *     to break the script can then take over its brain by providing Lisp
779  *     forms for the debugger to evaluate.
780  */
781
782 #define ABCL_STARTUP_RUNE                                               \
783         "(let ((#9=#:script \"%e\")) "                                  \
784            COMMON_PRELUDE_RUNE " "                                      \
785            CLEAR_CL_USER_RUNE " "                                       \
786                                                                         \
787            /* Replace the broken `*error-output*' stream with a working \
788             * copy of `stderr'.                                         \
789             */                                                          \
790           "(setf *error-output* "                                       \
791                   "(java:jnew \"org.armedbear.lisp.Stream\" "           \
792                              "'sys::system-stream "                     \
793                              "(java:jfield \"java.lang.System\" \"err\") " \
794                              "'character "                              \
795                              "java:+true+)) "                           \
796                                                                         \
797            /* Trap errors signalled by the script and arrange for them  \
798             * to actually kill the process rather than ending up in the \
799             * interactive debugger.                                     \
800             */                                                          \
801           "(handler-case (load #9# :verbose nil :print nil) "           \
802             "(error (error) "                                           \
803               "(format *error-output* \"~A (unhandled error): ~A~%%\" " \
804                       "#9# error) "                                     \
805             "(ext:quit :status 255))))"
806
807 static void run_abcl(struct argstate *arg, const char *script)
808 {
809   struct dstr d = DSTR_INIT;
810
811   argv_prependl(&arg->av, getenv_or_default("ABCL", "abcl"),
812                 "--batch", "--noinform", "--noinit", "--nosystem",
813                 "--eval", expand_rune(&d, ABCL_STARTUP_RUNE, script),
814                 "--",
815                 END);
816   try_exec(arg, &arg->av);
817   dstr_release(&d);
818 }
819
820 /*----- Main code ---------------------------------------------------------*/
821
822 static void version(FILE *fp)
823   { fprintf(fp, "%s, version %s\n", progname, PACKAGE_VERSION); }
824
825 static void usage(FILE *fp)
826 {
827   fprintf(fp, "usage: %s [-CDEnqv] [-I IMAGEDIR] "
828               "[-L SYS,SYS,...] [-P SYS,SYS,...]\n"
829               "\t[--] SCRIPT [ARGUMENTS ...] |\n"
830               "\t[-e EXPR] [-p EXPR] [-l FILE] [--] [ARGUMENTS ...]\n",
831           progname);
832 }
833
834 static void help(FILE *fp)
835 {
836   version(fp); fputc('\n', fp); usage(fp);
837   fputs("\n\
838 Options:\n\
839   --help                Show this help text and exit successfully.\n\
840   --version             Show the version number and exit successfully.\n\
841   -C                    Clear the list of preferred Lisp systems.\n\
842   -D                    Run system Lisp images, rather than custom images.\n\
843   -E                    Don't read embedded options from the script.\n\
844   -I IMAGEDIR           Look for custom images in IMAGEDIR rather than\n\
845                           `" IMAGEDIR "'.\n\
846   -L SYS,SYS,...        Only use the listed Lisp systems.the script.\n\
847   -P SYS,SYS,...        Prefer the listed Lisp systems.\n\
848   -e EXPR               Evaluate EXPR (can be repeated).\n\
849   -l FILE               Load FILE (can be repeated).\n\
850   -n                    Don't actually run the script (useful with `-v')\n\
851   -p EXPR               Print (`prin1') EXPR (can be repeated).\n\
852   -q                    Don't print warning messages.\n\
853   -v                    Print informational messages (repeat for even more).\n",
854         fp);
855 }
856
857 /* Parse a comma-separated list of system names SPEC, and add the named
858  * systems to LIST.
859  */
860 static void parse_syslist(const char *spec, const struct argstate *arg,
861                           struct syslist *list, const char *what)
862 {
863   char *copy = xstrdup(spec), *p = copy, *q;
864   const struct systab *sys;
865   size_t n;
866
867   for (;;) {
868     n = strcspn(p, ",");
869     if (p[n]) q = p + n + 1;
870     else q = 0;
871     p[n] = 0; sys = find_system(p);
872     if (list->f&sys->f) {
873       if (arg->verbose > 0)
874         moan("ignoring duplicate system `%s' in %s list", p, what);
875     } else {
876       list->sys[list->n++] = sys;
877       list->f |= sys->f;
878     }
879     if (!q) break;
880     p = q;
881   }
882   free(copy);
883 }
884
885 static void push_eval_op(struct argstate *arg, char op, const char *val)
886 {
887   char *p;
888   size_t n;
889
890   if (arg->f&F_AUX) {
891     moan("must use `-e', `-p', or `-l' on command line");
892     arg->f |= F_BOGUS;
893     return;
894   }
895
896   n = strlen(val) + 1;
897   p = xmalloc(n + 1);
898   p[0] = op; memcpy(p + 1, val, n);
899   argv_append(&arg->av, p);
900 }
901
902 /* Parse a vector ARGS of command-line arguments.  Update ARG with the
903  * results.  NARG is the number of arguments, and *I_INOUT is the current
904  * index into the vector, to be updated on exit to identify the first
905  * non-option argument (or the end of the vector).
906  */
907 static void parse_arguments(struct argstate *arg, const char *const *args,
908                             size_t nargs, size_t *i_inout)
909 {
910   const char *o, *a;
911   char opt;
912
913   for (;;) {
914     if (*i_inout >= nargs) break;
915     o = args[*i_inout];
916     if (STRCMP(o, ==, "--help")) { help(stdout); exit(0); }
917     else if (STRCMP(o, ==, "--version")) { version(stdout); exit(0); }
918     if (!*o || *o != '-' || !o[1]) break;
919     (*i_inout)++;
920     if (STRCMP(o, ==, "--")) break;
921     o++;
922     while (o && *o) {
923       opt = *o++;
924       switch (opt) {
925
926 #define GETARG do {                                                     \
927   if (*o)                                                               \
928     { a = o; o = 0; }                                                   \
929   else {                                                                \
930     if (*i_inout >= nargs) goto noarg;                                  \
931     a = args[(*i_inout)++];                                             \
932   }                                                                     \
933 } while (0)
934
935         case 'C': arg->pref.n = 0; arg->pref.f = 0; break;
936         case 'D': arg->f |= F_NODUMP; break;
937         case 'E': arg->f |= F_NOEMBED; break;
938         case 'e': GETARG; push_eval_op(arg, '!', a); break;
939         case 'p': GETARG; push_eval_op(arg, '?', a); break;
940         case 'l': GETARG; push_eval_op(arg, '<', a); break;
941         case 'n': arg->f |= F_NOACT; break;
942         case 'q': if (arg->verbose) arg->verbose--; break;
943         case 'v': arg->verbose++; break;
944
945         case 'I':
946           free(arg->imagedir);
947           GETARG; arg->imagedir = xstrdup(a);
948           break;
949
950         case 'L':
951           GETARG;
952           parse_syslist(a, arg, &arg->allow, "allowed");
953           break;
954
955         case 'P':
956           GETARG;
957           parse_syslist(a, arg, &arg->pref, "preferred");
958           break;
959
960         default:
961           moan("unknown option `%c'", opt);
962           arg->f |= F_BOGUS;
963           break;
964
965 #undef GETARG
966
967       }
968     }
969   }
970   goto end;
971
972 noarg:
973   moan("missing argument for `-%c'", opt);
974   arg->f |= F_BOGUS;
975 end:
976   return;
977 }
978
979 /* Parse a string P into words (destructively), and process them as
980  * command-line options, updating ARG.  Non-option arguments are not
981  * permitted.  If `SOSF_EMACS' is set in FLAGS, then ignore `-*- ... -*-'
982  * editor turds.  If `SOSF_ENDOK' is set, then accept `--' and ignore
983  * whatever comes after; otherwise, reject all positional arguments.
984  */
985 #define SOSF_EMACS 1u
986 #define SOSF_ENDOK 2u
987 static void scan_options_from_string(char *p, struct argstate *arg,
988                                      unsigned flags,
989                                      const char *what, const char *file)
990 {
991   struct argv av = ARGV_INIT;
992   char *q;
993   size_t i;
994   int st = 0;
995   unsigned f = 0;
996 #define f_escape 1u
997
998   for (;;) {
999     while (ISSPACE(*p)) p++;
1000     if (!*p) break;
1001     if ((flags&SOSF_EMACS) && p[0] == '-' && p[1] == '*' && p[2] == '-') {
1002       p = strstr(p + 3, "-*-");
1003       if (!p) lose("unfinished local-variables list in %s `%s'", what, file);
1004       p += 3; continue;
1005     }
1006     if ((flags&SOSF_ENDOK) &&
1007         p[0] == '-' && p[1] == '-' && (!p[2] || ISSPACE(p[2])))
1008       break;
1009     argv_append(&av, p); q = p;
1010     for (;;) {
1011       if (!*p) break;
1012       else if (f&f_escape) { *q++ = *p; f &= ~f_escape; }
1013       else if (st && *p == st) st = 0;
1014       else if (st != '\'' && *p == '\\') f |= f_escape;
1015       else if (!st && (*p == '"' || *p == '\'')) st = *p;
1016       else if (!st && ISSPACE(*p)) break;
1017       else *q++ = *p;
1018       p++;
1019     }
1020     if (*p) p++;
1021     *q = 0;
1022     if (f&f_escape) lose("unfinished escape in %s `%s'", what, file);
1023     if (st) lose("unfinished `%c' string in %s `%s'", st, what, file);
1024   }
1025
1026   i = 0; parse_arguments(arg, av.v, av.n, &i);
1027   if (i < av.n)
1028     lose("positional argument `%s' in %s `%s'", av.v[i], what, file);
1029   argv_release(&av);
1030
1031 #undef f_escape
1032 }
1033
1034 /* Read SCRIPT, and check for a `@RUNLISP:' marker in the second line.  If
1035  * there is one, parse options from it, and update ARG.
1036  */
1037 static void check_for_embedded_args(const char *script, struct argstate *arg)
1038 {
1039   struct dstr d = DSTR_INIT;
1040   char *p;
1041   FILE *fp = 0;
1042
1043   fp = fopen(script, "r");
1044   if (!fp) lose("can't read script `%s': %s", script, strerror(errno));
1045
1046   if (dstr_readline(&d, fp)) goto end;
1047   dstr_reset(&d); if (dstr_readline(&d, fp)) goto end;
1048
1049   p = strstr(d.p, "@RUNLISP:");
1050   if (p)
1051     scan_options_from_string(p + 9, arg, SOSF_EMACS | SOSF_ENDOK,
1052                              "embedded options in script", script);
1053
1054 end:
1055   if (fp) {
1056     if (ferror(fp))
1057       lose("error reading script `%s': %s", script, strerror(errno));
1058     fclose(fp);
1059   }
1060   dstr_release(&d);
1061 }
1062
1063 /* Read the file PATH (if it exists) and update ARG with the arguments parsed
1064  * from it.  Ignore blank lines and (Unix- or Lisp-style) comments.
1065  */
1066 static void read_config_file(const char *path, struct argstate *arg)
1067 {
1068   FILE *fp = 0;
1069   struct dstr d = DSTR_INIT;
1070   char *p;
1071
1072   fp = fopen(path, "r");
1073   if (!fp) {
1074     if (errno == ENOENT) {
1075       if (arg->verbose > 2)
1076         moan("ignoring nonexistent configuration file `%s'", path);
1077       goto end;
1078     }
1079     lose("failed to open configuration file `%s': %s",
1080          path, strerror(errno));
1081   }
1082   if (arg->verbose > 1)
1083     moan("reading configuration file `%s'", path);
1084   for (;;) {
1085     dstr_reset(&d);
1086     if (dstr_readline(&d, fp)) break;
1087     p = d.p;
1088     while (ISSPACE(*p)) p++;
1089     if (!*p || *p == ';' || *p == '#') continue;
1090     scan_options_from_string(p, arg, 0, "configuration file `%s'", path);
1091   }
1092   if (arg->f&F_BOGUS)
1093     lose("invalid options in configuration file `%s'", path);
1094
1095 end:
1096   if (fp) {
1097     if (ferror(fp))
1098       lose("error reading configuration file `%s': %s",
1099            path, strerror(errno));
1100     fclose(fp);
1101   }
1102   dstr_release(&d);
1103 }
1104
1105 int main(int argc, char *argv[])
1106 {
1107   struct dstr d = DSTR_INIT;
1108   const char *script, *p;
1109   const char *home;
1110   struct passwd *pw;
1111   char *t;
1112   size_t i, n;
1113   struct argstate arg = ARGSTATE_INIT;
1114
1115   /* Scan the command line.  This gets low priority, since it's probably
1116    * from the script shebang.
1117    */
1118   set_progname(argv[0]); i = 1;
1119   parse_arguments(&arg, (const char *const *)argv, argc, &i);
1120   arg.f |= F_AUX;
1121   if ((i >= argc && !arg.av.n) || (arg.f&F_BOGUS))
1122     { usage(stderr); exit(255); }
1123
1124   /* Prepare the argument vector.  Keep track of the number of arguments
1125    * here: we'll need to refer to this later.
1126    */
1127   if (!arg.av.n) {
1128     script = argv[i++];
1129     if (!(arg.f&F_NOEMBED)) check_for_embedded_args(script, &arg);
1130     if (arg.f&F_BOGUS)
1131       lose("invalid options in `%s' embedded option list", script);
1132   } else {
1133     script = getenv("RUNLISP_EVAL");
1134     if (!script) script = DATADIR "/eval.lisp";
1135     argv_append(&arg.av, "--");
1136   }
1137   argv_appendn(&arg.av, (const char *const *)argv + i, argc - i);
1138   n = arg.av.n;
1139
1140   /* Find the user's home directory.  (Believe them if they set something
1141    * strange.)
1142    */
1143   home = getenv("HOME");
1144   if (!home) {
1145     pw = getpwuid(getuid());
1146     if (!pw) lose("can't find user in password database");
1147     home = pw->pw_dir;
1148   }
1149
1150   /* Check user configuration file `~/.runlisprc'. */
1151   dstr_reset(&d);
1152   dstr_puts(&d, home); dstr_putc(&d, '/'); dstr_puts(&d, ".runlisprc");
1153   read_config_file(d.p, &arg);
1154
1155   /* Check user configuration file `~/.config/runlisprc'. */
1156   dstr_reset(&d);
1157   p = getenv("XDG_CONFIG_HOME");
1158   if (p)
1159     dstr_puts(&d, p);
1160   else
1161     { dstr_puts(&d, home); dstr_putc(&d, '/'); dstr_puts(&d, ".config"); }
1162   dstr_putc(&d, '/'); dstr_puts(&d, "runlisprc");
1163   read_config_file(d.p, &arg);
1164
1165   /* Finally, check the environment variables. */
1166   p = getenv("RUNLISP_OPTIONS");
1167   if (p) {
1168     t = xstrdup(p);
1169     scan_options_from_string(t, &arg, 0,
1170                              "environment variable", "RUNLISP_OPTIONS");
1171     free(t);
1172   }
1173   if (arg.f&F_BOGUS)
1174     lose("invalid options in environment variable `RUNLISP_OPTIONS'");
1175   if (!arg.imagedir) {
1176     arg.imagedir = getenv("RUNLISP_IMAGEDIR");
1177     if (!arg.imagedir) arg.imagedir = IMAGEDIR;
1178   }
1179
1180   /* If no systems are listed as acceptable, try them all. */
1181   if (!arg.allow.n) {
1182     if (arg.verbose > 1)
1183       moan("no explicitly allowed implementations: allowing all");
1184     for (i = 0; i < NSYS; i++) arg.allow.sys[i] = &systab[i];
1185     arg.allow.n = NSYS; arg.allow.f = (1u << NSYS) - 1;
1186   }
1187
1188   /* Print what we're going to do. */
1189   if (arg.verbose > 2) {
1190     dstr_reset(&d); p = "";
1191     for (i = 0; i < arg.allow.n; i++)
1192       { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.allow.sys[i]->name); }
1193     moan("permitted Lisps: %s", d.p);
1194
1195     dstr_reset(&d); p = "";
1196     for (i = 0; i < arg.pref.n; i++)
1197       { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.pref.sys[i]->name); }
1198     moan("preferred Lisps: %s", d.p);
1199
1200     dstr_reset(&d); p = "";
1201     for (i = 0; i < arg.pref.n; i++)
1202       if (arg.pref.sys[i]->f&arg.allow.f)
1203         { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.pref.sys[i]->name); }
1204     for (i = 0; i < arg.allow.n; i++)
1205       if (!(arg.allow.sys[i]->f&arg.pref.f))
1206         { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.allow.sys[i]->name); }
1207     moan("overall preference order: %s", d.p);
1208   }
1209
1210   /* Inform `uiop' of the script name.
1211    *
1212    * As an aside, this is a terrible interface.  It's too easy to forget to
1213    * set it.  (To illustrate this, `cl-launch -x' indeed forgets to set it.)
1214    * If you're lucky, the script just thinks that its argument is `nil', in
1215    * which case maybe it can use `*load-pathname*' as a fallback.  If you're
1216    * unlucky, your script was invoked (possibly indirectly) by another
1217    * script, and now you've accidentally inherited the calling script's name.
1218    *
1219    * It would have been far better simply to repeat the script name as the
1220    * first user argument, if nothing else had come readily to mind.
1221    */
1222   if (setenv("__CL_ARGV0", script, 1))
1223     lose("failed to set script-name environment variable");
1224
1225   /* Work through the list of preferred Lisp systems, trying the ones which
1226    * are allowed.
1227    */
1228   for (i = 0; i < arg.pref.n; i++)
1229     if (arg.pref.sys[i]->f&arg.allow.f) {
1230       arg.av.o += arg.av.n - n; arg.av.n = n;
1231       arg.pref.sys[i]->run(&arg, script);
1232     }
1233
1234   /* That didn't work.  Try the remaining allowed systems, in the given
1235    * order.
1236    */
1237   for (i = 0; i < arg.allow.n; i++)
1238     if (!(arg.allow.sys[i]->f&arg.pref.f)) {
1239       arg.av.o += arg.av.n - n; arg.av.n = n;
1240       arg.allow.sys[i]->run(&arg, script);
1241     }
1242
1243   /* No joy.  Give up. */
1244   argv_release(&arg.av);
1245   lose("no supported Lisp systems found");
1246 }
1247
1248 /*----- That's all, folks -------------------------------------------------*/