chiark / gitweb /
@@@ work in progress
[runlisp] / common.c
1 /* -*-c-*-
2  *
3  * Common functionality of a less principled nature
4  *
5  * (c) 2020 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
11  *
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.
16  *
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
20  * for more details.
21  *
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/>.
24  */
25
26 /*----- Header files ------------------------------------------------------*/
27
28 #include "config.h"
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include <dirent.h>
37 #include <pwd.h>
38 #include <unistd.h>
39
40 #include <sys/stat.h>
41
42 #include "common.h"
43 #include "lib.h"
44
45 /*----- Public variables --------------------------------------------------*/
46
47 struct config config = CONFIG_INIT;
48 struct config_section *toplevel, *builtin, *common, *env;
49 unsigned verbose = 1;
50
51 /*----- Internal utilities ------------------------------------------------*/
52
53 static void escapify(struct dstr *d, const char *p)
54 {
55   size_t n;
56
57   for (;;) {
58     n = strcspn(p, "\"'\\");
59     if (n) { dstr_putm(d, p, n); p += n; }
60     if (!*p) break;
61     dstr_putc(d, '\\'); dstr_putc(d, *p++);
62   }
63   dstr_putz(d);
64 }
65
66 static void homedir(struct dstr *d)
67 {
68   static const char *home = 0;
69   const char *p;
70   struct passwd *pw;
71
72   if (!home) {
73     p = my_getenv("HOME", 0);
74     if (p) home = p;
75     else {
76       pw = getpwuid(getuid());
77       if (!pw) lose("can't find user in password database");
78       home = xstrdup(pw->pw_dir);
79     }
80   }
81   dstr_puts(d, home);
82 }
83
84 static void user_config_dir(struct dstr *d)
85 {
86   const char *p;
87
88   p = my_getenv("XDG_CONFIG_HOME", 0);
89   if (p) dstr_puts(d, p);
90   else { homedir(d); dstr_puts(d, "/.config"); }
91 }
92
93 /*----- Miscellany --------------------------------------------------------*/
94
95 const char *my_getenv(const char *name, const char *dflt)
96 {
97   struct config_var *var;
98
99   var = config_find_var(&config, env, 0, name);
100   return (var ? var->val : dflt);
101 }
102
103 long parse_int(const char *what, const char *p, long min, long max)
104 {
105   long n;
106   int oerr = errno;
107   char *q;
108
109   errno = 0;
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)",
115          what, n, min, max);
116   errno = oerr;
117   return (n);
118 }
119
120 void argv_string(struct dstr *d, const struct argv *av)
121 {
122   size_t i;
123
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, '\'');
127   }
128   dstr_putz(d);
129 }
130
131 /*----- File utilities ----------------------------------------------------*/
132
133 int file_exists_p(const char *path, unsigned f)
134 {
135   struct stat st;
136
137   if (stat(path, &st)) {
138     if (f&FEF_VERBOSE) moan("file `%s' not found", path);
139     return (0);
140   } else if (!(S_ISREG(st.st_mode))) {
141     if (f&FEF_VERBOSE) moan("`%s' is not a regular file", path);
142     return (0);
143   } else if ((f&FEF_EXEC) && access(path, X_OK)) {
144     if (f&FEF_VERBOSE) moan("file `%s' is not executable", path);
145     return (0);
146   } else {
147     if (f&FEF_VERBOSE) moan("found file `%s'", path);
148     return (1);
149   }
150 }
151
152 int found_in_path_p(const char *prog, unsigned f)
153 {
154   struct dstr p = DSTR_INIT, d = DSTR_INIT;
155   const char *path;
156   char *q;
157   size_t n, avail, proglen;
158   int i, rc;
159
160   if (strchr(prog, '/'))
161     return (file_exists_p(prog, f));
162   path = my_getenv("PATH", 0);
163   if (path)
164     dstr_puts(&p, path);
165   else {
166     dstr_puts(&p, ".:");
167     i = 0;
168   again:
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; }
172   }
173
174   q = p.p; proglen = strlen(prog);
175   for (;;) {
176     n = strcspn(q, ":");
177     dstr_reset(&d);
178     if (n) dstr_putm(&d, q, n);
179     else dstr_putc(&d, '.');
180     dstr_putc(&d, '/');
181     dstr_putm(&d, prog, proglen);
182     dstr_putz(&d);
183     if (file_exists_p(d.p, verbose >= 4 ? f : f&~FEF_VERBOSE)) {
184       if (verbose == 2) moan("found program `%s'", d.p);
185       rc = 1; goto end;
186     }
187     q += n; if (!*q) break; else q++;
188   }
189
190   rc = 0;
191 end:
192   dstr_release(&p); dstr_release(&d);
193   return (rc);
194 }
195
196 int try_exec(struct argv *av, unsigned f)
197 {
198   struct dstr d = DSTR_INIT;
199   int rc;
200
201   assert(av->n); argv_appendz(av);
202   if (verbose >= 2) { argv_string(&d, av); moan("trying %s...", d.p); }
203   if (f&TEF_DRYRUN) {
204     if (found_in_path_p(av->v[0], f&TEF_VERBOSE ? FEF_VERBOSE : 0))
205       { rc = 0; goto end; }
206   } else {
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));
210       _exit(2);
211     }
212   }
213
214   if (verbose >= 2) moan("`%s' not found", av->v[0]);
215   rc = -1;
216 end:
217   dstr_release(&d);
218   return (rc);
219 }
220
221 /*----- Configuration -----------------------------------------------------*/
222
223 void read_config_file(const char *what, const char *file, unsigned f)
224 {
225   if (!config_read_file(&config, file, f)) {
226     if (verbose >= 2)
227       moan("read %s configuration file `%s'", what, file);
228   } else {
229     if (verbose >= 3)
230       moan("ignoring missing %s configuration file `%s'", what, file);
231   }
232 }
233
234 static int order_strings(const void *xx, const void *yy)
235   { const char *const *x = xx, *const *y = yy; return (strcmp(*x, *y)); }
236
237 void read_config_dir(const char *what, const char *path, unsigned f)
238 {
239   struct argv av = ARGV_INIT;
240   struct dstr dd = DSTR_INIT;
241   struct stat st;
242   DIR *dir;
243   struct dirent *d;
244   size_t i, n, len;
245
246   dir = opendir(path);
247   if (!dir) {
248     if (!(f&CF_NOENTOK) || errno != ENOENT)
249       lose("failed to read %s configuration directory `%s': %s",
250            what, path, strerror(errno));
251     if (verbose >= 3)
252       moan("ignoring missing %s configuration directory `%s'", what, path);
253     return;
254   }
255
256   dstr_puts(&dd, path); dstr_putc(&dd, '/'); n = dd.len;
257   for (;;) {
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);
262     if (stat(dd.p, &st))
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));
267   }
268
269   qsort(av.v, av.n, sizeof(*av.v), order_strings);
270
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);
274   }
275
276   for (i = 0; i < av.n; i++) free((/*unconst*/ char *)av.v[i]);
277   argv_release(&av); dstr_release(&dd); closedir(dir);
278   return;
279 }
280
281 void read_config_path(const char *path, unsigned f)
282 {
283   struct stat st;
284
285   if (!stat(path, &st) && S_ISDIR(st.st_mode))
286     read_config_dir("command-line specified ", path, f);
287   else
288     read_config_file("command-line specified", path, f);
289 }
290
291 int set_config_var(const char *assign)
292 {
293   struct config_section *sect;
294   const char *p, *q;
295
296   p = strchr(assign, '=');
297   if (!p) { moan("missing `=' in option assignment"); return (-1); }
298   q = strchr(assign, ':');
299   if (!q || q > p)
300     { sect = toplevel; q = assign; }
301   else {
302     sect = config_find_section_n(&config, CF_CREAT, assign, q - assign);
303     q++;
304   }
305   config_set_var_n(&config, sect, CF_LITERAL | CF_OVERRIDE,
306                    q, p - q, p + 1, strlen(p + 1));
307   return (0);
308 }
309
310 void init_config(void)
311 {
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);
321
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));
326
327 #ifdef ECL_OPTIONS_GNU
328   config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "--");
329 #else
330   config_set_var(&config, builtin, CF_LITERAL, "@ECLOPT", "-");
331 #endif
332 }
333
334 void load_default_config(void)
335 {
336   const char *p;
337   struct dstr d = DSTR_INIT;
338
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);
344   if (p)
345     read_config_file("user", p, CF_NOENTOK);
346   else {
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);
351   }
352   dstr_release(&d);
353 }
354
355 void dump_config(void)
356 {
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;
362
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);
370     }
371   dstr_release(&d);
372 }
373
374 /*----- That's all, folks -------------------------------------------------*/