chiark / gitweb /
@@@ more wip
[runlisp] / common.c
1 /* -*-c-*-
2  *
3  * Common functionality of a less principled nature
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 <stdlib.h>
34 #include <string.h>
35
36 #include <dirent.h>
37 #include <pwd.h>
38 #include <unistd.h>
39
40 #include <sys/stat.h>
41
42 #include "common.h"
43 #include "lib.h"
44
45 /*----- Public variables --------------------------------------------------*/
46
47 struct config config = CONFIG_INIT;     /* main configuration */
48 struct config_section *toplevel, *builtin, *common, *env; /* well-known
49                                                            * sections */
50 unsigned verbose = 1;                   /* verbosity level */
51
52 /*----- Miscellany --------------------------------------------------------*/
53
54 /* Look up the environment variable NAME.
55  *
56  * If it's found, return the value; otherwise return DFLT.  This function
57  * looks up the environment variable in the `@ENV' configuration section, so
58  * (a) it's likely more efficient than getenv(3), and (b) the `init_config'
59  * function must have been called earlier.
60  */
61 const char *my_getenv(const char *name, const char *dflt)
62 {
63   struct config_var *var;
64
65   var = config_find_var(&config, env, 0, name);
66   return (var ? var->val : dflt);
67 }
68
69 /* Parse and return an integer from the string P.
70  *
71  * Report an error if the string doesn't look like an integer, or if it's not
72  * between MIN and MAX (inclusive).  Qualify error messages using the
73  * adjective WHAT.
74  */
75 long parse_int(const char *what, const char *p, long min, long max)
76 {
77   long n;
78   int oerr = errno;
79   char *q;
80
81   errno = 0;
82   n = strtol(p, &q, 0);
83   while (ISSPACE(*q)) q++;
84   if (errno || *q) lose("invalid %s `%s'", what, p);
85   if (n < min || n > max)
86     lose("%s %ld out of range (must be between %ld and %ld)",
87          what, n, min, max);
88   errno = oerr;
89   return (n);
90 }
91
92 /* Append a word P to string D, quoting and/or escaping it in shell style.
93  *
94  * It tries to pick a `good' way to protect metacharacters, but the precise
95  * details aren't guaranteed to remain stable.
96  */
97 static void putword(struct dstr *d, const char *p)
98 {
99   unsigned bare = 0, sq = 2, dq = 2;
100   const char *q, *e, *f;
101   size_t n;
102
103   /* Pass one: count up how many extra escaping and/or quoting characters
104    * we'd need for each quoting strategy: `bare' is no quoting, just adding
105    * toothpicks before naughty characters; `dq' is double quotes, with fewer
106    * toothpicks; and `sq' is single quotes, with the somewhat awful rune
107    * `'\''' rune replacing embedded single quotes.  The quoting strategies
108    * start off with a two-character penalty for the surrounding quotes.
109    */
110   for (q = p; *q; q++)
111     switch (*q) {
112       case '\\': case '"': case '`': case '$': case '!': bare++; dq++; break;
113       case '\'': bare++; sq += 3; break;
114       case '^': case '|': case ';': case '&': case '(': case ')':
115       case '<': case '>':
116       case '*': case '?': case '[':
117       case '#':
118         bare++; break;
119       default:
120         if (ISSPACE(*q)) bare++;
121         break;
122     }
123
124   /* Prepare for the output loop: `q' will be a string of naughty characters
125    * which need escaping somehow; `e' is a sequence to insert before each
126    * naughty character, and `f' is a final string to add to the end.  We'll
127    * put the initial quote on ourselves, if necessary.
128    */
129   if (bare < dq && bare < sq)
130     { q = "\\\"`$!'^|;&()<>*?[# \b\f\n\r\t\v"; e = "\\"; f = ""; }
131   else if (dq < sq)
132     { q = "\\\"`$!"; e = "\\"; dstr_putc(d, '"'); f = "\""; }
133   else
134     { q = "'"; e = "'\\'"; dstr_putc(d, '\''); f = "'"; }
135
136   /* Work through the input string inserting escapes as we go. */
137   for (;;) {
138     n = strcspn(p, q);
139     if (n) { dstr_putm(d, p, n); p += n; }
140     if (!*p) break;
141     dstr_puts(d, e); dstr_putc(d, *p++);
142   }
143   dstr_puts(d, f);
144 }
145
146 /* Format string-vector AV as a sequence of possibly-quoted words.
147  *
148  * Append the resulting list to D.
149  */
150 void argv_string(struct dstr *d, const struct argv *av)
151 {
152   size_t i;
153
154   for (i = 0; i < av->n; i++) {
155     if (i) dstr_putc(d, ' ');
156     putword(d, av->v[i]);
157   }
158   dstr_putz(d);
159 }
160
161 /*----- Internal utilities ------------------------------------------------*/
162
163 /* Append the user's home directory to D. */
164 static void homedir(struct dstr *d)
165 {
166   static const char *home = 0;
167   const char *p;
168   struct passwd *pw;
169
170   if (!home) {
171
172     p = my_getenv("HOME", 0);
173     if (p) home = p;
174     else {
175       pw = getpwuid(getuid());
176       if (!pw) lose("can't find user in password database");
177       home = xstrdup(pw->pw_dir);
178     }
179   }
180   dstr_puts(d, home);
181 }
182
183 /* Append the user's XDG configuration directory to D. */
184 static void user_config_dir(struct dstr *d)
185 {
186   const char *p;
187
188   p = my_getenv("XDG_CONFIG_HOME", 0);
189   if (p) dstr_puts(d, p);
190   else { homedir(d); dstr_puts(d, "/.config"); }
191 }
192
193 /*----- File utilities ----------------------------------------------------*/
194
195 /* Return whether PATH names an existing file.
196  *
197  * This will return zero if PATH names something which isn't a regular file.
198  * If `FEF_EXEC' is set in F, then additionally ensure that it's executable
199  * by the (real) calling uid.  If `FEF_VERBOSE' is set in F, then report on
200  * the outcome of the check to standard error.
201  */
202 int file_exists_p(const char *path, unsigned f)
203 {
204   struct stat st;
205
206   if (stat(path, &st)) {
207     if (f&FEF_VERBOSE) moan("file `%s' not found", path);
208     return (0);
209   } else if (!(S_ISREG(st.st_mode))) {
210     if (f&FEF_VERBOSE) moan("`%s' is not a regular file", path);
211     return (0);
212   } else if ((f&FEF_EXEC) && access(path, X_OK)) {
213     if (f&FEF_VERBOSE) moan("file `%s' is not executable", path);
214     return (0);
215   } else {
216     if (f&FEF_VERBOSE) moan("found file `%s'", path);
217     return (1);
218   }
219 }
220
221 /* Return whether PROG can be found in the `PATH'.
222  *
223  * If PROG is a pathname (absolute or relative -- i.e., if it contains a
224  * `/'), then just check that it names an executable program.  Otherwise
225  * check to see whether `DIR/PROG' exists and is executable for any DIR in
226  * the `PATH'.  The flags F are as for `file_exists_p'.
227  */
228 int found_in_path_p(const char *prog, unsigned f)
229 {
230   struct dstr p = DSTR_INIT, d = DSTR_INIT;
231   const char *path;
232   char *q;
233   size_t n, avail, proglen;
234   int i, rc;
235
236   if (strchr(prog, '/'))
237     return (file_exists_p(prog, f | FEF_EXEC));
238   path = my_getenv("PATH", 0);
239   if (path)
240     dstr_puts(&p, path);
241   else {
242     dstr_puts(&p, ".:");
243     i = 0;
244   again:
245     avail = p.sz - p.len;
246     n = confstr(_CS_PATH, p.p + p.len, avail);
247     if (avail > n) { i++; assert(i < 2); dstr_ensure(&p, n); goto again; }
248   }
249
250   q = p.p; proglen = strlen(prog);
251   for (;;) {
252     n = strcspn(q, ":");
253     dstr_reset(&d);
254     if (n) dstr_putm(&d, q, n);
255     else dstr_putc(&d, '.');
256     dstr_putc(&d, '/');
257     dstr_putm(&d, prog, proglen);
258     dstr_putz(&d);
259     if (file_exists_p(d.p, (verbose >= 4 ? f : f&~FEF_VERBOSE) | FEF_EXEC)) {
260       if (verbose == 2) moan("found program `%s'", d.p);
261       rc = 1; goto end;
262     }
263     q += n; if (!*q) break; else q++;
264   }
265
266   rc = 0;
267 end:
268   dstr_release(&p); dstr_release(&d);
269   return (rc);
270 }
271
272 /* Try to run a program as indicated by the argument list AV.
273  *
274  * This is essentially execvp(3).  If `TEF_VERBOSE' is set in F then trace
275  * what's going on to standard error.  If `TEF_DRYRUN' is set in F then don't
276  * actually try to run the program: just check whether it exists and is
277  * vaguely plausible.  Return -1 if there was a problem, or 0 if it was
278  * successful but didn't actually run the program because of the flags
279  * settings.
280  */
281 int try_exec(struct argv *av, unsigned f)
282 {
283   struct dstr d = DSTR_INIT;
284   int rc;
285
286   assert(av->n); argv_appendz(av);
287   if (verbose >= 2) { argv_string(&d, av); moan("trying %s...", d.p); }
288   if (f&TEF_DRYRUN) {
289     if (found_in_path_p(av->v[0], f&TEF_VERBOSE ? FEF_VERBOSE : 0))
290       { rc = 0; goto end; }
291   } else {
292     execvp(av->v[0], av->v);
293     if (errno != ENOENT) {
294       moan("failed to exec `%s': %s", av->v[0], strerror(errno));
295       _exit(2);
296     }
297   }
298
299   if (verbose >= 2) moan("`%s' not found", av->v[0]);
300   rc = -1;
301 end:
302   dstr_release(&d);
303   return (rc);
304 }
305
306 /*----- Configuration -----------------------------------------------------*/
307
308 /* Initialize the configuration machinery.
309  *
310  * This establishes the standard configuration sections `@CONFIG',
311  * `@BUILTIN', `@COMMON', and `@ENV', setting the corresponding global
312  * variables, and populates `@BUILTIN' (from compile-time configuration) and
313  * `@ENV' (from the environment variables).
314  */
315 void init_config(void)
316 {
317   toplevel = config_find_section(&config, CF_CREAT, "@CONFIG");
318   builtin = config_find_section(&config, CF_CREAT, "@BUILTIN");
319   common = config_find_section(&config, CF_CREAT, "@COMMON");
320   env = config_find_section(&config, CF_CREAT, "@ENV");
321   config_set_fallback(&config, common);
322   config_set_parent(builtin, 0);
323   config_set_parent(common, builtin);
324   config_set_parent(env, 0);
325   config_set_parent(toplevel, 0);
326   config_read_env(&config, env);
327
328   config_set_var(&config, builtin, CF_LITERAL,
329                  "@%data-dir", DATADIR);
330   config_set_var(&config, builtin, 0,
331                  "@data-dir", "${@ENV:RUNLISP_DATADIR?"
332                                "${@CONFIG:data-dir?"
333                                  "${@BUILTIN:@%data-dir}}}");
334   config_set_var(&config, builtin, CF_LITERAL,
335                  "@%image-dir", IMAGEDIR);
336   config_set_var(&config, builtin, 0,
337                  "@image-dir", "${@ENV:RUNLISP_IMAGEDIR?"
338                                 "${@CONFIG:image-dir?"
339                                   "${@BUILTIN:@%image-dir}}}");
340
341 #ifdef ECL_OPTIONS_GNU
342   config_set_var(&config, builtin, CF_LITERAL, "@%ecl-opt", "--");
343 #else
344   config_set_var(&config, builtin, CF_LITERAL, "@%ecl-opt", "-");
345 #endif
346   config_set_var(&config, builtin, 0,
347                  "@ecl-opt", "${@CONFIG:ecl-opt?${@BUILTIN:@%ecl-opt}}");
348 }
349
350 /* Read a named configuration FILE.
351  *
352  * WHAT is an adjective describing the configuration file, to be used in
353  * diagnostics; FILE is the actual filename to read; and F holds `CF_...'
354  * flags for `config_read_file', which actually does most of the work.
355  */
356 void read_config_file(const char *what, const char *file, unsigned f)
357 {
358   if (!config_read_file(&config, file, f)) {
359     if (verbose >= 2)
360       moan("read %s configuration file `%s'", what, file);
361   } else {
362     if (verbose >= 3)
363       moan("ignoring missing %s configuration file `%s'", what, file);
364   }
365 }
366
367 /* Order strings lexicographically.
368  *
369  * This function is intended to be passed an argument to qsort(3).
370  */
371 static int order_strings(const void *xx, const void *yy)
372   { const char *const *x = xx, *const *y = yy; return (strcmp(*x, *y)); }
373
374 /* Read all of the configuration files in directory PATH.
375  *
376  * WHAT is an adjective describing the configuration directory, to be used in
377  * diagnostics; FILE is the actual filename to read; and F holds `CF_...'
378  * flags for `config_read_file', which actually reads the files.
379  *
380  * All of the files named `*.conf' in the directory are read, in ascending
381  * lexicographical order by name.  If `CF_NOENTOK' is set in F, then ignore
382  * an error explaining that the directory doesn't exist.  (This only ignores
383  * `ENOENT': any other problem is still a fatal error.)
384  */
385 void read_config_dir(const char *what, const char *path, unsigned f)
386 {
387   struct argv av = ARGV_INIT;
388   struct dstr dd = DSTR_INIT;
389   struct stat st;
390   DIR *dir;
391   struct dirent *d;
392   size_t i, n, len;
393
394   dir = opendir(path);
395   if (!dir) {
396     if (!(f&CF_NOENTOK) || errno != ENOENT)
397       lose("failed to read %s configuration directory `%s': %s",
398            what, path, strerror(errno));
399     if (verbose >= 3)
400       moan("ignoring missing %s configuration directory `%s'", what, path);
401     return;
402   }
403
404   dstr_puts(&dd, path); dstr_putc(&dd, '/'); n = dd.len;
405   for (;;) {
406     d = readdir(dir); if (!d) break;
407     len = strlen(d->d_name);
408     if (len < 5 || STRCMP(d->d_name + len - 5, !=, ".conf")) continue;
409     dd.len = n; dstr_putm(&dd, d->d_name, len); dstr_putz(&dd);
410     if (stat(dd.p, &st))
411       lose("failed to read file metadata for `%s': %s",
412            dd.p, strerror(errno));
413     if (!S_ISREG(st.st_mode)) continue;
414     argv_append(&av, xstrdup(d->d_name));
415   }
416
417   qsort(av.v, av.n, sizeof(*av.v), order_strings);
418
419   for (i = 0; i < av.n; i++) {
420     dd.len = n; dstr_puts(&dd, av.v[i]);
421     read_config_file(what, dd.p, f&~CF_NOENTOK);
422   }
423
424   for (i = 0; i < av.n; i++) free(av.v[i]);
425   argv_release(&av); dstr_release(&dd); closedir(dir);
426   return;
427 }
428
429 /* Read configuration from a file or directory PATH.
430  *
431  * If PATH exists and names a directory then process all of the files within,
432  * as for `read_config_dir'; otherwise try to read it as a file, as for
433  * `read_config_file'. The flags F are passed to the respective function.
434  */
435 void read_config_path(const char *path, unsigned f)
436 {
437   struct stat st;
438
439   if (!stat(path, &st) && S_ISDIR(st.st_mode))
440     read_config_dir("command-line specified ", path, f);
441   else
442     read_config_file("command-line specified", path, f);
443 }
444
445 /* Apply a configuration variable setting in command-line syntax.
446  *
447  * ASSIGN should be a string in the form `[SECT:]VAR=VALUE'.  Set VAR to
448  * VALUE in section SECT (defaults to `@CONFIG').  The variable is set with
449  * `CF_OVERRIDE' set to prevent the setting from being overwritten by a
450  * configuration file.
451  */
452 int set_config_var(const char *assign)
453 {
454   struct config_section *sect;
455   const char *p, *q;
456
457   p = strchr(assign, '=');
458   if (!p) { moan("missing `=' in option assignment"); return (-1); }
459   q = strchr(assign, ':');
460   if (!q || q > p)
461     { sect = toplevel; q = assign; }
462   else if (q == assign)
463     lose("expected section or variable name in option assignment");
464   else {
465     sect = config_find_section_n(&config, CF_CREAT, assign, q - assign);
466     q++;
467   }
468   if (p == q) lose("expected variable name in option assignment");
469   config_set_var_n(&config, sect, CF_LITERAL | CF_OVERRIDE,
470                    q, p - q, p + 1, strlen(p + 1));
471   return (0);
472 }
473
474 /* Load the default configuration files.
475  *
476  * This will read `ETCDIR/runlisp.d/*.conf', `ETCDIR/runlisp.conf',
477  * `~/.runlisp.conf', and `~/.config/runlisp.conf'.
478  */
479 void load_default_config(void)
480 {
481   const char *p;
482   struct dstr d = DSTR_INIT;
483
484   p = my_getenv("RUNLISP_SYSCONFIG_DIR", ETCDIR "/runlisp.d");
485   read_config_dir("system", p, CF_NOENTOK);
486   p = my_getenv("RUNLISP_SYSCONFIG", ETCDIR "/runlisp.conf");
487   read_config_file("system", p, 0);
488
489   p = my_getenv("RUNLISP_USERCONFIG", 0);
490   if (p)
491     read_config_file("user", p, CF_NOENTOK);
492   else {
493     dstr_reset(&d); homedir(&d); dstr_puts(&d, "/.runlisp.conf");
494     read_config_file("user", d.p, CF_NOENTOK);
495     dstr_reset(&d); user_config_dir(&d); dstr_puts(&d, "/runlisp.conf");
496     read_config_file("user", d.p, CF_NOENTOK);
497   }
498   dstr_release(&d);
499 }
500
501 /* Dump the configuration to standard error. */
502 void dump_config(void)
503 {
504   struct config_section_iter si;
505   struct config_section *sect;
506   struct config_var_iter vi;
507   struct config_var *var;
508
509   for (config_start_section_iter(&config, &si);
510        (sect = config_next_section(&si)); )
511     for (config_start_var_iter(&config, sect, &vi);
512          (var = config_next_var(&vi)); )
513       moan("config %s:%s = %s",
514            CONFIG_SECTION_NAME(sect), CONFIG_VAR_NAME(var), var->val);
515 }
516
517 /*----- That's all, folks -------------------------------------------------*/