X-Git-Url: https://git.distorted.org.uk/~mdw/runlisp/blobdiff_plain/e29834b853038e8c90dcfe8377f02431cad42fc5..7b8ff279e7304e41b243459d78c3b6703bb8c3f5:/common.c diff --git a/common.c b/common.c new file mode 100644 index 0000000..b0d5857 --- /dev/null +++ b/common.c @@ -0,0 +1,374 @@ +/* -*-c-*- + * + * Common functionality of a less principled nature + * + * (c) 2020 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Runlisp, a tool for invoking Common Lisp scripts. + * + * Runlisp is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * Runlisp is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with Runlisp. If not, see . + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "common.h" +#include "lib.h" + +/*----- Public variables --------------------------------------------------*/ + +struct config config = CONFIG_INIT; +struct config_section *toplevel, *builtin, *common, *env; +unsigned verbose = 1; + +/*----- Internal utilities ------------------------------------------------*/ + +static void escapify(struct dstr *d, const char *p) +{ + size_t n; + + for (;;) { + n = strcspn(p, "\"'\\"); + if (n) { dstr_putm(d, p, n); p += n; } + if (!*p) break; + dstr_putc(d, '\\'); dstr_putc(d, *p++); + } + dstr_putz(d); +} + +static void homedir(struct dstr *d) +{ + static const char *home = 0; + const char *p; + struct passwd *pw; + + if (!home) { + p = my_getenv("HOME", 0); + if (p) home = p; + else { + pw = getpwuid(getuid()); + if (!pw) lose("can't find user in password database"); + home = xstrdup(pw->pw_dir); + } + } + dstr_puts(d, home); +} + +static void user_config_dir(struct dstr *d) +{ + const char *p; + + p = my_getenv("XDG_CONFIG_HOME", 0); + if (p) dstr_puts(d, p); + 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 ----------------------------------------------------*/ + +int file_exists_p(const char *path, unsigned f) +{ + struct stat st; + + if (stat(path, &st)) { + if (f&FEF_VERBOSE) moan("file `%s' not found", path); + return (0); + } else if (!(S_ISREG(st.st_mode))) { + if (f&FEF_VERBOSE) moan("`%s' is not a regular file", path); + return (0); + } else if ((f&FEF_EXEC) && access(path, X_OK)) { + if (f&FEF_VERBOSE) moan("file `%s' is not executable", path); + return (0); + } else { + if (f&FEF_VERBOSE) moan("found file `%s'", path); + return (1); + } +} + +int found_in_path_p(const char *prog, unsigned f) +{ + struct dstr p = DSTR_INIT, d = DSTR_INIT; + const char *path; + char *q; + size_t n, avail, proglen; + int i, rc; + + if (strchr(prog, '/')) + return (file_exists_p(prog, f)); + path = my_getenv("PATH", 0); + if (path) + dstr_puts(&p, path); + else { + dstr_puts(&p, ".:"); + i = 0; + again: + avail = p.sz - p.len; + n = confstr(_CS_PATH, p.p + p.len, avail); + if (avail > n) { i++; assert(i < 2); dstr_ensure(&p, n); goto again; } + } + + q = p.p; proglen = strlen(prog); + for (;;) { + n = strcspn(q, ":"); + dstr_reset(&d); + if (n) dstr_putm(&d, q, n); + else dstr_putc(&d, '.'); + dstr_putc(&d, '/'); + dstr_putm(&d, prog, proglen); + dstr_putz(&d); + if (file_exists_p(d.p, verbose >= 4 ? f : f&~FEF_VERBOSE)) { + if (verbose == 2) moan("found program `%s'", d.p); + rc = 1; goto end; + } + q += n; if (!*q) break; else q++; + } + + rc = 0; +end: + dstr_release(&p); dstr_release(&d); + return (rc); +} + +int try_exec(struct argv *av, unsigned f) +{ + struct dstr d = DSTR_INIT; + int rc; + + assert(av->n); argv_appendz(av); + if (verbose >= 2) { argv_string(&d, av); moan("trying %s...", d.p); } + if (f&TEF_DRYRUN) { + 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); + if (errno != ENOENT) { + moan("failed to exec `%s': %s", av->v[0], strerror(errno)); + _exit(2); + } + } + + if (verbose >= 2) moan("`%s' not found", av->v[0]); + rc = -1; +end: + dstr_release(&d); + return (rc); +} + +/*----- Configuration -----------------------------------------------------*/ + +void read_config_file(const char *what, const char *file, unsigned f) +{ + if (!config_read_file(&config, file, f)) { + if (verbose >= 2) + moan("read %s configuration file `%s'", what, file); + } else { + if (verbose >= 3) + moan("ignoring missing %s configuration file `%s'", what, file); + } +} + +static int order_strings(const void *xx, const void *yy) + { const char *const *x = xx, *const *y = yy; return (strcmp(*x, *y)); } + +void read_config_dir(const char *what, const char *path, unsigned f) +{ + struct argv av = ARGV_INIT; + struct dstr dd = DSTR_INIT; + struct stat st; + DIR *dir; + struct dirent *d; + size_t i, n, len; + + dir = opendir(path); + if (!dir) { + if (!(f&CF_NOENTOK) || errno != ENOENT) + lose("failed to read %s configuration directory `%s': %s", + what, path, strerror(errno)); + if (verbose >= 3) + moan("ignoring missing %s configuration directory `%s'", what, path); + return; + } + + dstr_puts(&dd, path); dstr_putc(&dd, '/'); n = dd.len; + for (;;) { + d = readdir(dir); if (!d) break; + len = strlen(d->d_name); + if (len < 5 || STRCMP(d->d_name + len - 5, !=, ".conf")) continue; + dd.len = n; dstr_putm(&dd, d->d_name, len); dstr_putz(&dd); + if (stat(dd.p, &st)) + lose("failed to read file metadata for `%s': %s", + dd.p, strerror(errno)); + if (!S_ISREG(st.st_mode)) continue; + argv_append(&av, xstrdup(d->d_name)); + } + + qsort(av.v, av.n, sizeof(*av.v), order_strings); + + for (i = 0; i < av.n; i++) { + dd.len = n; dstr_puts(&dd, av.v[i]); + read_config_file(what, dd.p, f&~CF_NOENTOK); + } + + for (i = 0; i < av.n; i++) free((/*unconst*/ char *)av.v[i]); + argv_release(&av); dstr_release(&dd); closedir(dir); + return; +} + +void read_config_path(const char *path, unsigned f) +{ + struct stat st; + + if (!stat(path, &st) && S_ISDIR(st.st_mode)) + read_config_dir("command-line specified ", path, f); + else + read_config_file("command-line specified", path, f); +} + +int set_config_var(const char *assign) +{ + struct config_section *sect; + const char *p, *q; + + p = strchr(assign, '='); + if (!p) { moan("missing `=' in option assignment"); return (-1); } + q = strchr(assign, ':'); + if (!q || q > p) + { sect = toplevel; q = assign; } + else { + sect = config_find_section_n(&config, CF_CREAT, assign, q - assign); + q++; + } + 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 +} + +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_USERCONFIG", 0); + if (p) + read_config_file("user", p, CF_NOENTOK); + else { + dstr_reset(&d); homedir(&d); dstr_puts(&d, "/.runlisp.conf"); + read_config_file("user", d.p, CF_NOENTOK); + dstr_reset(&d); user_config_dir(&d); dstr_puts(&d, "/runlisp.conf"); + read_config_file("user", d.p, CF_NOENTOK); + } + dstr_release(&d); +} + +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); +} + +/*----- That's all, folks -------------------------------------------------*/