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;
48 struct config_section *toplevel, *builtin, *common, *env;
51 /*----- Internal utilities ------------------------------------------------*/
53 static void escapify(struct dstr *d, const char *p)
58 n = strcspn(p, "\"'\\");
59 if (n) { dstr_putm(d, p, n); p += n; }
61 dstr_putc(d, '\\'); dstr_putc(d, *p++);
66 static void homedir(struct dstr *d)
68 static const char *home = 0;
73 p = my_getenv("HOME", 0);
76 pw = getpwuid(getuid());
77 if (!pw) lose("can't find user in password database");
78 home = xstrdup(pw->pw_dir);
84 static void user_config_dir(struct dstr *d)
88 p = my_getenv("XDG_CONFIG_HOME", 0);
89 if (p) dstr_puts(d, p);
90 else { homedir(d); dstr_puts(d, "/.config"); }
93 /*----- Miscellany --------------------------------------------------------*/
95 const char *my_getenv(const char *name, const char *dflt)
97 struct config_var *var;
99 var = config_find_var(&config, env, 0, name);
100 return (var ? var->val : dflt);
103 long parse_int(const char *what, const char *p, long min, long max)
110 n = strtol(p, &q, 0);
111 while (ISSPACE(*q)) q++;
112 if (errno || *q) lose("invalid %s `%s'", what, p);
113 if (n < min || n > max)
114 lose("%s %ld out of range (must be between %ld and %ld)",
120 void argv_string(struct dstr *d, const struct argv *av)
124 for (i = 0; i < av->n; i++) {
125 if (i) { dstr_putc(d, ','); dstr_putc(d, ' '); }
126 dstr_putc(d, '`'); escapify(d, av->v[i]); dstr_putc(d, '\'');
131 /*----- File utilities ----------------------------------------------------*/
133 int file_exists_p(const char *path, unsigned f)
137 if (stat(path, &st)) {
138 if (f&FEF_VERBOSE) moan("file `%s' not found", path);
140 } else if (!(S_ISREG(st.st_mode))) {
141 if (f&FEF_VERBOSE) moan("`%s' is not a regular file", path);
143 } else if ((f&FEF_EXEC) && access(path, X_OK)) {
144 if (f&FEF_VERBOSE) moan("file `%s' is not executable", path);
147 if (f&FEF_VERBOSE) moan("found file `%s'", path);
152 int found_in_path_p(const char *prog, unsigned f)
154 struct dstr p = DSTR_INIT, d = DSTR_INIT;
157 size_t n, avail, proglen;
160 if (strchr(prog, '/'))
161 return (file_exists_p(prog, f));
162 path = my_getenv("PATH", 0);
169 avail = p.sz - p.len;
170 n = confstr(_CS_PATH, p.p + p.len, avail);
171 if (avail > n) { i++; assert(i < 2); dstr_ensure(&p, n); goto again; }
174 q = p.p; proglen = strlen(prog);
178 if (n) dstr_putm(&d, q, n);
179 else dstr_putc(&d, '.');
181 dstr_putm(&d, prog, proglen);
183 if (file_exists_p(d.p, verbose >= 4 ? f : f&~FEF_VERBOSE)) {
184 if (verbose == 2) moan("found program `%s'", d.p);
187 q += n; if (!*q) break; else q++;
192 dstr_release(&p); dstr_release(&d);
196 int try_exec(struct argv *av, unsigned f)
198 struct dstr d = DSTR_INIT;
201 assert(av->n); argv_appendz(av);
202 if (verbose >= 2) { argv_string(&d, av); moan("trying %s...", d.p); }
204 if (found_in_path_p(av->v[0], f&TEF_VERBOSE ? FEF_VERBOSE : 0))
205 { rc = 0; goto end; }
207 execvp(av->v[0], (/*unconst*/ char **)av->v);
208 if (errno != ENOENT) {
209 moan("failed to exec `%s': %s", av->v[0], strerror(errno));
214 if (verbose >= 2) moan("`%s' not found", av->v[0]);
221 /*----- Configuration -----------------------------------------------------*/
223 void read_config_file(const char *what, const char *file, unsigned f)
225 if (!config_read_file(&config, file, f)) {
227 moan("read %s configuration file `%s'", what, file);
230 moan("ignoring missing %s configuration file `%s'", what, file);
234 static int order_strings(const void *xx, const void *yy)
235 { const char *const *x = xx, *const *y = yy; return (strcmp(*x, *y)); }
237 void read_config_dir(const char *what, const char *path, unsigned f)
239 struct argv av = ARGV_INIT;
240 struct dstr dd = DSTR_INIT;
248 if (!(f&CF_NOENTOK) || errno != ENOENT)
249 lose("failed to read %s configuration directory `%s': %s",
250 what, path, strerror(errno));
252 moan("ignoring missing %s configuration directory `%s'", what, path);
256 dstr_puts(&dd, path); dstr_putc(&dd, '/'); n = dd.len;
258 d = readdir(dir); if (!d) break;
259 len = strlen(d->d_name);
260 if (len < 5 || STRCMP(d->d_name + len - 5, !=, ".conf")) continue;
261 dd.len = n; dstr_putm(&dd, d->d_name, len); dstr_putz(&dd);
263 lose("failed to read file metadata for `%s': %s",
264 dd.p, strerror(errno));
265 if (!S_ISREG(st.st_mode)) continue;
266 argv_append(&av, xstrdup(d->d_name));
269 qsort(av.v, av.n, sizeof(*av.v), order_strings);
271 for (i = 0; i < av.n; i++) {
272 dd.len = n; dstr_puts(&dd, av.v[i]);
273 read_config_file(what, dd.p, f&~CF_NOENTOK);
276 for (i = 0; i < av.n; i++) free((/*unconst*/ char *)av.v[i]);
277 argv_release(&av); dstr_release(&dd); closedir(dir);
281 void read_config_path(const char *path, unsigned f)
285 if (!stat(path, &st) && S_ISDIR(st.st_mode))
286 read_config_dir("command-line specified ", path, f);
288 read_config_file("command-line specified", path, f);
291 int set_config_var(const char *assign)
293 struct config_section *sect;
296 p = strchr(assign, '=');
297 if (!p) { moan("missing `=' in option assignment"); return (-1); }
298 q = strchr(assign, ':');
300 { sect = toplevel; q = assign; }
302 sect = config_find_section_n(&config, CF_CREAT, assign, q - assign);
305 config_set_var_n(&config, sect, CF_LITERAL | CF_OVERRIDE,
306 q, p - q, p + 1, strlen(p + 1));
310 void init_config(void)
312 toplevel = config_find_section(&config, CF_CREAT, "@CONFIG");
313 builtin = config_find_section(&config, CF_CREAT, "@BUILTIN");
314 common = config_find_section(&config, CF_CREAT, "@COMMON");
315 env = config_find_section(&config, CF_CREAT, "@ENV");
316 config_set_fallback(&config, common);
317 config_set_parent(builtin, 0);
318 config_set_parent(common, builtin);
319 config_set_parent(toplevel, 0);
320 config_read_env(&config, env);
322 config_set_var(&config, toplevel, CF_LITERAL, "data-dir",
323 my_getenv("RUNLISP_DATADIR", DATADIR));
324 config_set_var(&config, toplevel, CF_LITERAL, "image-dir",
325 my_getenv("RUNLISP_IMAGEDIR", IMAGEDIR));
327 #ifdef ECL_OPTIONS_GNU
328 config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "--");
330 config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "-");
334 void load_default_config(void)
337 struct dstr d = DSTR_INIT;
339 p = my_getenv("RUNLISP_SYSCONFIG", ETCDIR "/runlisp.conf");
340 read_config_file("system", p, 0);
341 p = my_getenv("RUNLISP_SYSCONFIG_DIR", ETCDIR "/runlisp.d");
342 read_config_dir("system", p, CF_NOENTOK);
343 p = my_getenv("RUNLISP_USERCONFIG", 0);
345 read_config_file("user", p, CF_NOENTOK);
347 dstr_reset(&d); homedir(&d); dstr_puts(&d, "/.runlisp.conf");
348 read_config_file("user", d.p, CF_NOENTOK);
349 dstr_reset(&d); user_config_dir(&d); dstr_puts(&d, "/runlisp.conf");
350 read_config_file("user", d.p, CF_NOENTOK);
355 void dump_config(void)
357 struct config_section_iter si;
358 struct config_section *sect;
359 struct config_var_iter vi;
360 struct config_var *var;
361 struct dstr d = DSTR_INIT;
363 for (config_start_section_iter(&config, &si);
364 (sect = config_next_section(&si)); )
365 for (config_start_var_iter(sect, &vi);
366 (var = config_next_var(&vi)); ) {
367 dstr_reset(&d); escapify(&d, var->val);
368 moan("config %s:%s = `%s'",
369 CONFIG_SECTION_NAME(sect), CONFIG_VAR_NAME(var), d.p);
374 /*----- That's all, folks -------------------------------------------------*/