X-Git-Url: https://git.distorted.org.uk/~mdw/runlisp/blobdiff_plain/7b8ff279e7304e41b243459d78c3b6703bb8c3f5..8996f767e047eefa8af4d01b1434b54f4c169b79:/common.c diff --git a/common.c b/common.c index b0d5857..1c0b0ce 100644 --- a/common.c +++ b/common.c @@ -44,25 +44,123 @@ /*----- Public variables --------------------------------------------------*/ -struct config config = CONFIG_INIT; -struct config_section *toplevel, *builtin, *common, *env; -unsigned verbose = 1; +struct config config = CONFIG_INIT; /* main configuration */ +struct config_section *toplevel, *builtin, *common, *env; /* well-known + * sections */ +unsigned verbose = 1; /* verbosity level */ -/*----- Internal utilities ------------------------------------------------*/ +/*----- Miscellany --------------------------------------------------------*/ + +/* Look up the environment variable NAME. + * + * If it's found, return the value; otherwise return DFLT. This function + * looks up the environment variable in the `@ENV' configuration section, so + * (a) it's likely more efficient than getenv(3), and (b) the `init_config' + * function must have been called earlier. + */ +const char *my_getenv(const char *name, const char *dflt) +{ + struct config_var *var; + + var = config_find_var(&config, env, 0, name); + return (var ? var->val : dflt); +} + +/* Parse and return an integer from the string P. + * + * Report an error if the string doesn't look like an integer, or if it's not + * between MIN and MAX (inclusive). Qualify error messages using the + * adjective WHAT. + */ +long parse_int(const char *what, const char *p, long min, long max) +{ + long n; + int oerr = errno; + char *q; -static void escapify(struct dstr *d, const char *p) + errno = 0; + n = strtol(p, &q, 0); + while (ISSPACE(*q)) q++; + if (errno || *q) lose("invalid %s `%s'", what, p); + if (n < min || n > max) + lose("%s %ld out of range (must be between %ld and %ld)", + what, n, min, max); + errno = oerr; + return (n); +} + +/* Append a word P to string D, quoting and/or escaping it in shell style. + * + * It tries to pick a `good' way to protect metacharacters, but the precise + * details aren't guaranteed to remain stable. + */ +static void putword(struct dstr *d, const char *p) { + unsigned bare = 0, sq = 2, dq = 2; + const char *q, *e, *f; size_t n; + /* Pass one: count up how many extra escaping and/or quoting characters + * we'd need for each quoting strategy: `bare' is no quoting, just adding + * toothpicks before naughty characters; `dq' is double quotes, with fewer + * toothpicks; and `sq' is single quotes, with the somewhat awful rune + * `'\''' rune replacing embedded single quotes. The quoting strategies + * start off with a two-character penalty for the surrounding quotes. + */ + for (q = p; *q; q++) + switch (*q) { + case '\\': case '"': case '`': case '$': case '!': bare++; dq++; break; + case '\'': bare++; sq += 3; break; + case '^': case '|': case ';': case '&': case '(': case ')': + case '<': case '>': + case '*': case '?': case '[': + case '#': + bare++; break; + default: + if (ISSPACE(*q)) bare++; + break; + } + + /* Prepare for the output loop: `q' will be a string of naughty characters + * which need escaping somehow; `e' is a sequence to insert before each + * naughty character, and `f' is a final string to add to the end. We'll + * put the initial quote on ourselves, if necessary. + */ + if (bare < dq && bare < sq) + { q = "\\\"`$!'^|;&()<>*?[# \b\f\n\r\t\v"; e = "\\"; f = ""; } + else if (dq < sq) + { q = "\\\"`$!"; e = "\\"; dstr_putc(d, '"'); f = "\""; } + else + { q = "'"; e = "'\\'"; dstr_putc(d, '\''); f = "'"; } + + /* Work through the input string inserting escapes as we go. */ for (;;) { - n = strcspn(p, "\"'\\"); + n = strcspn(p, q); if (n) { dstr_putm(d, p, n); p += n; } if (!*p) break; - dstr_putc(d, '\\'); dstr_putc(d, *p++); + dstr_puts(d, e); dstr_putc(d, *p++); + } + dstr_puts(d, f); +} + +/* Format string-vector AV as a sequence of possibly-quoted words. + * + * Append the resulting list to D. + */ +void argv_string(struct dstr *d, const struct argv *av) +{ + size_t i; + + for (i = 0; i < av->n; i++) { + if (i) dstr_putc(d, ' '); + putword(d, av->v[i]); } dstr_putz(d); } +/*----- Internal utilities ------------------------------------------------*/ + +/* Append the user's home directory to D. */ static void homedir(struct dstr *d) { static const char *home = 0; @@ -70,6 +168,7 @@ static void homedir(struct dstr *d) struct passwd *pw; if (!home) { + p = my_getenv("HOME", 0); if (p) home = p; else { @@ -81,6 +180,7 @@ static void homedir(struct dstr *d) dstr_puts(d, home); } +/* Append the user's XDG configuration directory to D. */ static void user_config_dir(struct dstr *d) { const char *p; @@ -90,46 +190,15 @@ static void user_config_dir(struct dstr *d) else { homedir(d); dstr_puts(d, "/.config"); } } -/*----- Miscellany --------------------------------------------------------*/ - -const char *my_getenv(const char *name, const char *dflt) -{ - struct config_var *var; - - var = config_find_var(&config, env, 0, name); - return (var ? var->val : dflt); -} - -long parse_int(const char *what, const char *p, long min, long max) -{ - long n; - int oerr = errno; - char *q; - - errno = 0; - n = strtol(p, &q, 0); - while (ISSPACE(*q)) q++; - if (errno || *q) lose("invalid %s `%s'", what, p); - if (n < min || n > max) - lose("%s %ld out of range (must be between %ld and %ld)", - what, n, min, max); - errno = oerr; - return (n); -} - -void argv_string(struct dstr *d, const struct argv *av) -{ - size_t i; - - for (i = 0; i < av->n; i++) { - if (i) { dstr_putc(d, ','); dstr_putc(d, ' '); } - dstr_putc(d, '`'); escapify(d, av->v[i]); dstr_putc(d, '\''); - } - dstr_putz(d); -} - /*----- File utilities ----------------------------------------------------*/ +/* Return whether PATH names an existing file. + * + * This will return zero if PATH names something which isn't a regular file. + * If `FEF_EXEC' is set in F, then additionally ensure that it's executable + * by the (real) calling uid. If `FEF_VERBOSE' is set in F, then report on + * the outcome of the check to standard error. + */ int file_exists_p(const char *path, unsigned f) { struct stat st; @@ -149,6 +218,13 @@ int file_exists_p(const char *path, unsigned f) } } +/* Return whether PROG can be found in the `PATH'. + * + * If PROG is a pathname (absolute or relative -- i.e., if it contains a + * `/'), then just check that it names an executable program. Otherwise + * check to see whether `DIR/PROG' exists and is executable for any DIR in + * the `PATH'. The flags F are as for `file_exists_p'. + */ int found_in_path_p(const char *prog, unsigned f) { struct dstr p = DSTR_INIT, d = DSTR_INIT; @@ -158,7 +234,7 @@ int found_in_path_p(const char *prog, unsigned f) int i, rc; if (strchr(prog, '/')) - return (file_exists_p(prog, f)); + return (file_exists_p(prog, f | FEF_EXEC)); path = my_getenv("PATH", 0); if (path) dstr_puts(&p, path); @@ -180,7 +256,7 @@ int found_in_path_p(const char *prog, unsigned f) dstr_putc(&d, '/'); dstr_putm(&d, prog, proglen); dstr_putz(&d); - if (file_exists_p(d.p, verbose >= 4 ? f : f&~FEF_VERBOSE)) { + if (file_exists_p(d.p, (verbose >= 4 ? f : f&~FEF_VERBOSE) | FEF_EXEC)) { if (verbose == 2) moan("found program `%s'", d.p); rc = 1; goto end; } @@ -193,6 +269,15 @@ end: return (rc); } +/* Try to run a program as indicated by the argument list AV. + * + * This is essentially execvp(3). If `TEF_VERBOSE' is set in F then trace + * what's going on to standard error. If `TEF_DRYRUN' is set in F then don't + * actually try to run the program: just check whether it exists and is + * vaguely plausible. Return -1 if there was a problem, or 0 if it was + * successful but didn't actually run the program because of the flags + * settings. + */ int try_exec(struct argv *av, unsigned f) { struct dstr d = DSTR_INIT; @@ -204,7 +289,7 @@ int try_exec(struct argv *av, unsigned f) if (found_in_path_p(av->v[0], f&TEF_VERBOSE ? FEF_VERBOSE : 0)) { rc = 0; goto end; } } else { - execvp(av->v[0], (/*unconst*/ char **)av->v); + execvp(av->v[0], av->v); if (errno != ENOENT) { moan("failed to exec `%s': %s", av->v[0], strerror(errno)); _exit(2); @@ -220,6 +305,54 @@ end: /*----- Configuration -----------------------------------------------------*/ +/* Initialize the configuration machinery. + * + * This establishes the standard configuration sections `@CONFIG', + * `@BUILTIN', `@COMMON', and `@ENV', setting the corresponding global + * variables, and populates `@BUILTIN' (from compile-time configuration) and + * `@ENV' (from the environment variables). + */ +void init_config(void) +{ + toplevel = config_find_section(&config, CF_CREAT, "@CONFIG"); + builtin = config_find_section(&config, CF_CREAT, "@BUILTIN"); + common = config_find_section(&config, CF_CREAT, "@COMMON"); + env = config_find_section(&config, CF_CREAT, "@ENV"); + config_set_fallback(&config, common); + config_set_parent(builtin, 0); + config_set_parent(common, builtin); + config_set_parent(env, 0); + config_set_parent(toplevel, 0); + config_read_env(&config, env); + + config_set_var(&config, builtin, CF_LITERAL, + "@%data-dir", DATADIR); + config_set_var(&config, builtin, 0, + "@data-dir", "${@ENV:RUNLISP_DATADIR?" + "${@CONFIG:data-dir?" + "${@BUILTIN:@%data-dir}}}"); + config_set_var(&config, builtin, CF_LITERAL, + "@%image-dir", IMAGEDIR); + config_set_var(&config, builtin, 0, + "@image-dir", "${@ENV:RUNLISP_IMAGEDIR?" + "${@CONFIG:image-dir?" + "${@BUILTIN:@%image-dir}}}"); + +#ifdef ECL_OPTIONS_GNU + config_set_var(&config, builtin, CF_LITERAL, "@%ecl-opt", "--"); +#else + config_set_var(&config, builtin, CF_LITERAL, "@%ecl-opt", "-"); +#endif + config_set_var(&config, builtin, 0, + "@ecl-opt", "${@CONFIG:ecl-opt?${@BUILTIN:@%ecl-opt}}"); +} + +/* Read a named configuration FILE. + * + * WHAT is an adjective describing the configuration file, to be used in + * diagnostics; FILE is the actual filename to read; and F holds `CF_...' + * flags for `config_read_file', which actually does most of the work. + */ void read_config_file(const char *what, const char *file, unsigned f) { if (!config_read_file(&config, file, f)) { @@ -231,9 +364,24 @@ void read_config_file(const char *what, const char *file, unsigned f) } } +/* Order strings lexicographically. + * + * This function is intended to be passed an argument to qsort(3). + */ static int order_strings(const void *xx, const void *yy) { const char *const *x = xx, *const *y = yy; return (strcmp(*x, *y)); } +/* Read all of the configuration files in directory PATH. + * + * WHAT is an adjective describing the configuration directory, to be used in + * diagnostics; FILE is the actual filename to read; and F holds `CF_...' + * flags for `config_read_file', which actually reads the files. + * + * All of the files named `*.conf' in the directory are read, in ascending + * lexicographical order by name. If `CF_NOENTOK' is set in F, then ignore + * an error explaining that the directory doesn't exist. (This only ignores + * `ENOENT': any other problem is still a fatal error.) + */ void read_config_dir(const char *what, const char *path, unsigned f) { struct argv av = ARGV_INIT; @@ -273,11 +421,17 @@ void read_config_dir(const char *what, const char *path, unsigned f) read_config_file(what, dd.p, f&~CF_NOENTOK); } - for (i = 0; i < av.n; i++) free((/*unconst*/ char *)av.v[i]); + for (i = 0; i < av.n; i++) free(av.v[i]); argv_release(&av); dstr_release(&dd); closedir(dir); return; } +/* Read configuration from a file or directory PATH. + * + * If PATH exists and names a directory then process all of the files within, + * as for `read_config_dir'; otherwise try to read it as a file, as for + * `read_config_file'. The flags F are passed to the respective function. + */ void read_config_path(const char *path, unsigned f) { struct stat st; @@ -288,6 +442,13 @@ void read_config_path(const char *path, unsigned f) read_config_file("command-line specified", path, f); } +/* Apply a configuration variable setting in command-line syntax. + * + * ASSIGN should be a string in the form `[SECT:]VAR=VALUE'. Set VAR to + * VALUE in section SECT (defaults to `@CONFIG'). The variable is set with + * `CF_OVERRIDE' set to prevent the setting from being overwritten by a + * configuration file. + */ int set_config_var(const char *assign) { struct config_section *sect; @@ -298,48 +459,33 @@ int set_config_var(const char *assign) q = strchr(assign, ':'); if (!q || q > p) { sect = toplevel; q = assign; } + else if (q == assign) + lose("expected section or variable name in option assignment"); else { sect = config_find_section_n(&config, CF_CREAT, assign, q - assign); q++; } + if (p == q) lose("expected variable name in option assignment"); config_set_var_n(&config, sect, CF_LITERAL | CF_OVERRIDE, q, p - q, p + 1, strlen(p + 1)); return (0); } -void init_config(void) -{ - toplevel = config_find_section(&config, CF_CREAT, "@CONFIG"); - builtin = config_find_section(&config, CF_CREAT, "@BUILTIN"); - common = config_find_section(&config, CF_CREAT, "@COMMON"); - env = config_find_section(&config, CF_CREAT, "@ENV"); - config_set_fallback(&config, common); - config_set_parent(builtin, 0); - config_set_parent(common, builtin); - config_set_parent(toplevel, 0); - config_read_env(&config, env); - - config_set_var(&config, toplevel, CF_LITERAL, "data-dir", - my_getenv("RUNLISP_DATADIR", DATADIR)); - config_set_var(&config, toplevel, CF_LITERAL, "image-dir", - my_getenv("RUNLISP_IMAGEDIR", IMAGEDIR)); - -#ifdef ECL_OPTIONS_GNU - config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "--"); -#else - config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "-"); -#endif -} - +/* Load the default configuration files. + * + * This will read `ETCDIR/runlisp.d/*.conf', `ETCDIR/runlisp.conf', + * `~/.runlisp.conf', and `~/.config/runlisp.conf'. + */ void load_default_config(void) { const char *p; struct dstr d = DSTR_INIT; - p = my_getenv("RUNLISP_SYSCONFIG", ETCDIR "/runlisp.conf"); - read_config_file("system", p, 0); p = my_getenv("RUNLISP_SYSCONFIG_DIR", ETCDIR "/runlisp.d"); read_config_dir("system", p, CF_NOENTOK); + p = my_getenv("RUNLISP_SYSCONFIG", ETCDIR "/runlisp.conf"); + read_config_file("system", p, 0); + p = my_getenv("RUNLISP_USERCONFIG", 0); if (p) read_config_file("user", p, CF_NOENTOK); @@ -352,23 +498,20 @@ void load_default_config(void) dstr_release(&d); } +/* Dump the configuration to standard error. */ void dump_config(void) { struct config_section_iter si; struct config_section *sect; struct config_var_iter vi; struct config_var *var; - struct dstr d = DSTR_INIT; for (config_start_section_iter(&config, &si); (sect = config_next_section(&si)); ) - for (config_start_var_iter(sect, &vi); - (var = config_next_var(&vi)); ) { - dstr_reset(&d); escapify(&d, var->val); - moan("config %s:%s = `%s'", - CONFIG_SECTION_NAME(sect), CONFIG_VAR_NAME(var), d.p); - } - dstr_release(&d); + for (config_start_var_iter(&config, sect, &vi); + (var = config_next_var(&vi)); ) + moan("config %s:%s = %s", + CONFIG_SECTION_NAME(sect), CONFIG_VAR_NAME(var), var->val); } /*----- That's all, folks -------------------------------------------------*/