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 -------------------------------------------------*/