3 * Common functionality of a less principled nature
5 * (c) 2020 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
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.
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
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/>.
26 /*----- Header files ------------------------------------------------------*/
45 /*----- Public variables --------------------------------------------------*/
47 struct config config
= CONFIG_INIT
; /* main configuration */
48 struct config_section
*toplevel
, *builtin
, *common
, *env
; /* well-known
50 unsigned verbose
= 1; /* verbosity level */
52 /*----- Miscellany --------------------------------------------------------*/
54 /* Look up the environment variable NAME.
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.
61 const char *my_getenv(const char *name
, const char *dflt
)
63 struct config_var
*var
;
65 var
= config_find_var(&config
, env
, 0, name
);
66 return (var ? var
->val
: dflt
);
69 /* Parse and return an integer from the string P.
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
75 long parse_int(const char *what
, const char *p
, long min
, long max
)
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)",
92 /* Append a word P to string D, quoting and/or escaping it in shell style.
94 * It tries to pick a `good' way to protect metacharacters, but the precise
95 * details aren't guaranteed to remain stable.
97 static void putword(struct dstr
*d
, const char *p
)
99 unsigned bare
= 0, sq
= 2, dq
= 2;
100 const char *q
, *e
, *f
;
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.
112 case '\\': case '"': case '`': case '$': case '!': bare
++; dq
++; break;
113 case '\'': bare
++; sq
+= 3; break;
114 case '^': case '|': case ';': case '&': case '(': case ')':
116 case '*': case '?': case '[':
120 if (ISSPACE(*q
)) bare
++;
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.
129 if (bare
< dq
&& bare
< sq
)
130 { q
= "\\\"`$!'^|;&()<>*?[# \b\f\n\r\t\v"; e
= "\\"; f
= ""; }
132 { q
= "\\\"`$!"; e
= "\\"; dstr_putc(d
, '"'); f
= "\""; }
134 { q
= "'"; e
= "'\\'"; dstr_putc(d
, '\''); f
= "'"; }
136 /* Work through the input string inserting escapes as we go. */
139 if (n
) { dstr_putm(d
, p
, n
); p
+= n
; }
141 dstr_puts(d
, e
); dstr_putc(d
, *p
++);
146 /* Format string-vector AV as a sequence of possibly-quoted words.
148 * Append the resulting list to D.
150 void argv_string(struct dstr
*d
, const struct argv
*av
)
154 for (i
= 0; i
< av
->n
; i
++) {
155 if (i
) dstr_putc(d
, ' ');
156 putword(d
, av
->v
[i
]);
161 /*----- Internal utilities ------------------------------------------------*/
163 /* Append the user's home directory to D. */
164 static void homedir(struct dstr
*d
)
166 static const char *home
= 0;
172 p
= my_getenv("HOME", 0);
175 pw
= getpwuid(getuid());
176 if (!pw
) lose("can't find user in password database");
177 home
= xstrdup(pw
->pw_dir
);
183 /* Append the user's XDG configuration directory to D. */
184 static void user_config_dir(struct dstr
*d
)
188 p
= my_getenv("XDG_CONFIG_HOME", 0);
189 if (p
) dstr_puts(d
, p
);
190 else { homedir(d
); dstr_puts(d
, "/.config"); }
193 /*----- File utilities ----------------------------------------------------*/
195 /* Return whether PATH names an existing file.
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.
202 int file_exists_p(const char *path
, unsigned f
)
206 if (stat(path
, &st
)) {
207 if (f
&FEF_VERBOSE
) moan("file `%s' not found", path
);
209 } else if (!(S_ISREG(st
.st_mode
))) {
210 if (f
&FEF_VERBOSE
) moan("`%s' is not a regular file", path
);
212 } else if ((f
&FEF_EXEC
) && access(path
, X_OK
)) {
213 if (f
&FEF_VERBOSE
) moan("file `%s' is not executable", path
);
216 if (f
&FEF_VERBOSE
) moan("found file `%s'", path
);
221 /* Return whether PROG can be found in the `PATH'.
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'.
228 int found_in_path_p(const char *prog
, unsigned f
)
230 struct dstr p
= DSTR_INIT
, d
= DSTR_INIT
;
233 size_t n
, avail
, proglen
;
236 if (strchr(prog
, '/'))
237 return (file_exists_p(prog
, f
| FEF_EXEC
));
238 path
= my_getenv("PATH", 0);
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
; }
250 q
= p
.p
; proglen
= strlen(prog
);
254 if (n
) dstr_putm(&d
, q
, n
);
255 else dstr_putc(&d
, '.');
257 dstr_putm(&d
, prog
, proglen
);
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
);
263 q
+= n
; if (!*q
) break; else q
++;
268 dstr_release(&p
); dstr_release(&d
);
272 /* Try to run a program as indicated by the argument list AV.
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
281 int try_exec(struct argv
*av
, unsigned f
)
283 struct dstr d
= DSTR_INIT
;
286 assert(av
->n
); argv_appendz(av
);
287 if (verbose
>= 2) { argv_string(&d
, av
); moan("trying %s...", d
.p
); }
289 if (found_in_path_p(av
->v
[0], f
&TEF_VERBOSE ? FEF_VERBOSE
: 0))
290 { rc
= 0; goto end
; }
292 execvp(av
->v
[0], av
->v
);
293 if (errno
!= ENOENT
) {
294 moan("failed to exec `%s': %s", av
->v
[0], strerror(errno
));
299 if (verbose
>= 2) moan("`%s' not found", av
->v
[0]);
306 /*----- Configuration -----------------------------------------------------*/
308 /* Initialize the configuration machinery.
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).
315 void init_config(void)
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_read_env(&config
, env
);
327 config_set_var(&config
, builtin
, CF_LITERAL
,
328 "@%data-dir", DATADIR
);
329 config_set_var(&config
, builtin
, 0,
330 "@data-dir", "${@ENV:RUNLISP_DATADIR?"
331 "${@CONFIG:data-dir?"
332 "${@BUILTIN:@%data-dir}}}");
333 config_set_var(&config
, builtin
, CF_LITERAL
,
334 "@%image-dir", IMAGEDIR
);
335 config_set_var(&config
, builtin
, 0,
336 "@image-dir", "${@ENV:RUNLISP_IMAGEDIR?"
337 "${@CONFIG:image-dir?"
338 "${@BUILTIN:@%image-dir}}}");
340 #ifdef ECL_OPTIONS_GNU
341 config_set_var(&config
, builtin
, CF_LITERAL
, "@%ecl-opt", "--");
343 config_set_var(&config
, builtin
, CF_LITERAL
, "@%ecl-opt", "-");
345 config_set_var(&config
, builtin
, 0,
346 "@ecl-opt", "${@CONFIG:ecl-opt?${@BUILTIN:@%ecl-opt}}");
349 /* Read a named configuration FILE.
351 * WHAT is an adjective describing the configuration file, to be used in
352 * diagnostics; FILE is the actual filename to read; and F holds `CF_...'
353 * flags for `config_read_file', which actually does most of the work.
355 void read_config_file(const char *what
, const char *file
, unsigned f
)
357 if (!config_read_file(&config
, file
, f
)) {
359 moan("read %s configuration file `%s'", what
, file
);
362 moan("ignoring missing %s configuration file `%s'", what
, file
);
366 /* Order strings lexicographically.
368 * This function is intended to be passed an argument to qsort(3).
370 static int order_strings(const void *xx
, const void *yy
)
371 { const char *const *x
= xx
, *const *y
= yy
; return (strcmp(*x
, *y
)); }
373 /* Read all of the configuration files in directory PATH.
375 * WHAT is an adjective describing the configuration directory, to be used in
376 * diagnostics; FILE is the actual filename to read; and F holds `CF_...'
377 * flags for `config_read_file', which actually reads the files.
379 * All of the files named `*.conf' in the directory are read, in ascending
380 * lexicographical order by name. If `CF_NOENTOK' is set in F, then ignore
381 * an error explaining that the directory doesn't exist. (This only ignores
382 * `ENOENT': any other problem is still a fatal error.)
384 void read_config_dir(const char *what
, const char *path
, unsigned f
)
386 struct argv av
= ARGV_INIT
;
387 struct dstr dd
= DSTR_INIT
;
395 if (!(f
&CF_NOENTOK
) || errno
!= ENOENT
)
396 lose("failed to read %s configuration directory `%s': %s",
397 what
, path
, strerror(errno
));
399 moan("ignoring missing %s configuration directory `%s'", what
, path
);
403 dstr_puts(&dd
, path
); dstr_putc(&dd
, '/'); n
= dd
.len
;
405 d
= readdir(dir
); if (!d
) break;
406 len
= strlen(d
->d_name
);
407 if (len
< 5 || STRCMP(d
->d_name
+ len
- 5, !=, ".conf")) continue;
408 dd
.len
= n
; dstr_putm(&dd
, d
->d_name
, len
); dstr_putz(&dd
);
410 lose("failed to read file metadata for `%s': %s",
411 dd
.p
, strerror(errno
));
412 if (!S_ISREG(st
.st_mode
)) continue;
413 argv_append(&av
, xstrdup(d
->d_name
));
416 qsort(av
.v
, av
.n
, sizeof(*av
.v
), order_strings
);
418 for (i
= 0; i
< av
.n
; i
++) {
419 dd
.len
= n
; dstr_puts(&dd
, av
.v
[i
]);
420 read_config_file(what
, dd
.p
, f
&~CF_NOENTOK
);
423 for (i
= 0; i
< av
.n
; i
++) free(av
.v
[i
]);
424 argv_release(&av
); dstr_release(&dd
); closedir(dir
);
428 /* Read configuration from a file or directory PATH.
430 * If PATH exists and names a directory then process all of the files within,
431 * as for `read_config_dir'; otherwise try to read it as a file, as for
432 * `read_config_file'. The flags F are passed to the respective function.
434 void read_config_path(const char *path
, unsigned f
)
438 if (!stat(path
, &st
) && S_ISDIR(st
.st_mode
))
439 read_config_dir("command-line specified ", path
, f
);
441 read_config_file("command-line specified", path
, f
);
444 /* Apply a configuration variable setting in command-line syntax.
446 * ASSIGN should be a string in the form `[SECT:]VAR=VALUE'. Set VAR to
447 * VALUE in section SECT (defaults to `@CONFIG'). The variable is set with
448 * `CF_OVERRIDE' set to prevent the setting from being overwritten by a
449 * configuration file.
451 int set_config_var(const char *assign
)
453 struct config_section
*sect
;
456 p
= strchr(assign
, '=');
457 if (!p
) { moan("missing `=' in option assignment"); return (-1); }
458 q
= strchr(assign
, ':');
460 { sect
= toplevel
; q
= assign
; }
461 else if (q
== assign
)
462 lose("expected section or variable name in option assignment");
464 sect
= config_find_section_n(&config
, CF_CREAT
, assign
, q
- assign
);
467 if (p
== q
) lose("expected variable name in option assignment");
468 config_set_var_n(&config
, sect
, CF_LITERAL
| CF_OVERRIDE
,
469 q
, p
- q
, p
+ 1, strlen(p
+ 1));
473 /* Load the default configuration files.
475 * This will read `ETCDIR/runlisp.d/*.conf', `ETCDIR/runlisp.conf',
476 * `~/.runlisp.conf', and `~/.config/runlisp.conf'.
478 void load_default_config(void)
481 struct dstr d
= DSTR_INIT
;
483 p
= my_getenv("RUNLISP_SYSCONFIG_DIR", ETCDIR
"/runlisp.d");
484 read_config_dir("system", p
, CF_NOENTOK
);
485 p
= my_getenv("RUNLISP_SYSCONFIG", ETCDIR
"/runlisp.conf");
486 read_config_file("system", p
, 0);
488 p
= my_getenv("RUNLISP_USERCONFIG", 0);
490 read_config_file("user", p
, CF_NOENTOK
);
492 dstr_reset(&d
); homedir(&d
); dstr_puts(&d
, "/.runlisp.conf");
493 read_config_file("user", d
.p
, CF_NOENTOK
);
494 dstr_reset(&d
); user_config_dir(&d
); dstr_puts(&d
, "/runlisp.conf");
495 read_config_file("user", d
.p
, CF_NOENTOK
);
500 /* Dump the configuration to standard error. */
501 void dump_config(void)
503 struct config_section_iter si
;
504 struct config_section
*sect
;
505 struct config_var_iter vi
;
506 struct config_var
*var
;
508 for (config_start_section_iter(&config
, &si
);
509 (sect
= config_next_section(&si
)); )
510 for (config_start_var_iter(&config
, sect
, &vi
);
511 (var
= config_next_var(&vi
)); )
512 moan("config %s:%s = %s",
513 CONFIG_SECTION_NAME(sect
), CONFIG_VAR_NAME(var
), var
->val
);
516 /*----- That's all, folks -------------------------------------------------*/