1 /*
2 * Copyright (C) 1997-2004, Michael Jennings
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies of the Software, its documentation and marketing & publicity
13 * materials, and acknowledgment shall be given in the documentation, materials
14 * and software packages that this Software was used.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 /**
25 * @file conf.c
26 * Config File Parser Source File
27 *
28 * This file contains the functions which comprise the config file
29 * parser.
30 *
31 * @author Michael Jennings <mej@eterm.org>
32 */
33
34 static const char __attribute__((unused)) cvs_ident[] = "$Id: conf.c,v 1.26 2005/03/07 22:29:07 mej Exp $";
35
36 #ifdef HAVE_CONFIG_H
37 # include <config.h>
38 #endif
39
40 #include <libast_internal.h>
41
42 static spifconf_var_t *spifconf_new_var(void);
43 static void spifconf_free_var(spifconf_var_t *);
44 static spif_charptr_t spifconf_get_var(const spif_charptr_t);
45 static void spifconf_put_var(spif_charptr_t, spif_charptr_t);
46 static spif_charptr_t builtin_random(spif_charptr_t);
47 static spif_charptr_t builtin_exec(spif_charptr_t);
48 static spif_charptr_t builtin_get(spif_charptr_t);
49 static spif_charptr_t builtin_put(spif_charptr_t);
50 static spif_charptr_t builtin_dirscan(spif_charptr_t);
51 static spif_charptr_t builtin_version(spif_charptr_t);
52 static spif_charptr_t builtin_appname(spif_charptr_t);
53 static void *parse_null(spif_charptr_t, void *);
54
55 static ctx_t *context;
56 static ctx_state_t *ctx_state;
57 static spifconf_func_t *builtins;
58 static unsigned char ctx_cnt, ctx_idx, ctx_state_idx, ctx_state_cnt, fstate_cnt, builtin_cnt, builtin_idx;
59 static spifconf_var_t *spifconf_vars = NULL;
60
61 const char *true_vals[] = { "1", "on", "true", "yes" };
62 const char *false_vals[] = { "0", "off", "false", "no" };
63
64 fstate_t *fstate = NULL;
65 unsigned char fstate_idx = 0;
66
67 /***** The Config File Section *****/
68 /* This function must be called before any other spifconf_*() function.
69 Otherwise you will be bitten by dragons. That's life. */
70 void
spifconf_init_subsystem(void)71 spifconf_init_subsystem(void)
72 {
73 /* Initialize the context list and establish a catch-all "null" context */
74 ctx_cnt = 20;
75 ctx_idx = 0;
76 context = (ctx_t *) MALLOC(sizeof(ctx_t) * ctx_cnt);
77 MEMSET(context, 0, sizeof(ctx_t) * ctx_cnt);
78 context[0].name = SPIF_CAST(charptr) STRDUP("null");
79 context[0].handler = parse_null;
80
81 /* Initialize the context state stack and set the current context to "null" */
82 ctx_state_cnt = 20;
83 ctx_state_idx = 0;
84 ctx_state = (ctx_state_t *) MALLOC(sizeof(ctx_state_t) * ctx_state_cnt);
85 MEMSET(ctx_state, 0, sizeof(ctx_state_t) * ctx_state_cnt);
86
87 /* Initialize the file state stack */
88 fstate_cnt = 10;
89 fstate_idx = 0;
90 fstate = (fstate_t *) MALLOC(sizeof(fstate_t) * fstate_cnt);
91 MEMSET(fstate, 0, sizeof(fstate_t) * fstate_cnt);
92
93 /* Initialize the builtin function table */
94 builtin_cnt = 10;
95 builtin_idx = 0;
96 builtins = (spifconf_func_t *) MALLOC(sizeof(spifconf_func_t) * builtin_cnt);
97 MEMSET(builtins, 0, sizeof(spifconf_func_t) * builtin_cnt);
98
99 /* Register the omni-present builtin functions */
100 spifconf_register_builtin("appname", builtin_appname);
101 spifconf_register_builtin("version", builtin_version);
102 spifconf_register_builtin("exec", builtin_exec);
103 spifconf_register_builtin("random", builtin_random);
104 spifconf_register_builtin("get", builtin_get);
105 spifconf_register_builtin("put", builtin_put);
106 spifconf_register_builtin("dirscan", builtin_dirscan);
107 }
108
109 /* Register a new config file context */
110 unsigned char
spifconf_register_context(spif_charptr_t name,ctx_handler_t handler)111 spifconf_register_context(spif_charptr_t name, ctx_handler_t handler)
112 {
113 ASSERT_RVAL(!SPIF_PTR_ISNULL(name), SPIF_CAST_C(unsigned char) -1);
114 ASSERT_RVAL(!SPIF_PTR_ISNULL(handler), SPIF_CAST_C(unsigned char) -1);
115
116 if (strcasecmp(SPIF_CAST_C(char *) name, "null")) {
117 if (++ctx_idx == ctx_cnt) {
118 ctx_cnt *= 2;
119 context = (ctx_t *) REALLOC(context, sizeof(ctx_t) * ctx_cnt);
120 }
121 } else {
122 FREE(context[0].name);
123 }
124 context[ctx_idx].name = SPIF_CAST(charptr) STRDUP(name);
125 context[ctx_idx].handler = handler;
126 D_CONF(("Added context \"%s\" with ID %d and handler 0x%08x\n", context[ctx_idx].name, ctx_idx, context[ctx_idx].handler));
127 return (ctx_idx);
128 }
129
130 /* Register a new file state structure */
131 unsigned char
spifconf_register_fstate(FILE * fp,spif_charptr_t path,spif_charptr_t outfile,unsigned long line,unsigned char flags)132 spifconf_register_fstate(FILE * fp, spif_charptr_t path, spif_charptr_t outfile, unsigned long line, unsigned char flags)
133 {
134 ASSERT_RVAL(!SPIF_PTR_ISNULL(fp), SPIF_CAST_C(unsigned char) -1);
135 ASSERT_RVAL(!SPIF_PTR_ISNULL(path), SPIF_CAST_C(unsigned char) -1);
136
137 if (++fstate_idx == fstate_cnt) {
138 fstate_cnt *= 2;
139 fstate = (fstate_t *) REALLOC(fstate, sizeof(fstate_t) * fstate_cnt);
140 }
141 fstate[fstate_idx].fp = fp;
142 fstate[fstate_idx].path = path;
143 fstate[fstate_idx].outfile = outfile;
144 fstate[fstate_idx].line = line;
145 fstate[fstate_idx].flags = flags;
146 return (fstate_idx);
147 }
148
149 /* Register a new builtin function */
150 unsigned char
spifconf_register_builtin(char * name,spifconf_func_ptr_t ptr)151 spifconf_register_builtin(char *name, spifconf_func_ptr_t ptr)
152 {
153 ASSERT_RVAL(!SPIF_PTR_ISNULL(name), SPIF_CAST_C(unsigned char) -1);
154
155 builtins[builtin_idx].name = SPIF_CAST(charptr) STRDUP(name);
156 builtins[builtin_idx].ptr = ptr;
157 if (++builtin_idx == builtin_cnt) {
158 builtin_cnt *= 2;
159 builtins = (spifconf_func_t *) REALLOC(builtins, sizeof(spifconf_func_t) * builtin_cnt);
160 }
161 return (builtin_idx - 1);
162 }
163
164 /* Register a new config file context */
165 unsigned char
spifconf_register_context_state(unsigned char ctx_id)166 spifconf_register_context_state(unsigned char ctx_id)
167 {
168 if (++ctx_state_idx == ctx_state_cnt) {
169 ctx_state_cnt *= 2;
170 ctx_state = (ctx_state_t *) REALLOC(ctx_state, sizeof(ctx_state_t) * ctx_state_cnt);
171 }
172 ctx_state[ctx_state_idx].ctx_id = ctx_id;
173 ctx_state[ctx_state_idx].state = NULL;
174 return (ctx_state_idx);
175 }
176
177 void
spifconf_free_subsystem(void)178 spifconf_free_subsystem(void)
179 {
180 spifconf_var_t *v, *tmp;
181 unsigned long i;
182
183 for (v = spifconf_vars; v;) {
184 tmp = v;
185 v = v->next;
186 spifconf_free_var(tmp);
187 }
188 for (i = 0; i < builtin_idx; i++) {
189 FREE(builtins[i].name);
190 }
191 for (i = 0; i <= ctx_idx; i++) {
192 FREE(context[i].name);
193 }
194 FREE(ctx_state);
195 FREE(builtins);
196 FREE(fstate);
197 FREE(context);
198 }
199
200 static spifconf_var_t *
spifconf_new_var(void)201 spifconf_new_var(void)
202 {
203 spifconf_var_t *v;
204
205 v = (spifconf_var_t *) MALLOC(sizeof(spifconf_var_t));
206 MEMSET(v, 0, sizeof(spifconf_var_t));
207 return v;
208 }
209
210 static void
spifconf_free_var(spifconf_var_t * v)211 spifconf_free_var(spifconf_var_t *v)
212 {
213 ASSERT(!SPIF_PTR_ISNULL(v));
214 if (v->var) {
215 FREE(v->var);
216 }
217 if (v->value) {
218 FREE(v->value);
219 }
220 FREE(v);
221 }
222
223 static spif_charptr_t
spifconf_get_var(const spif_charptr_t var)224 spifconf_get_var(const spif_charptr_t var)
225 {
226 spifconf_var_t *v;
227
228 ASSERT_RVAL(!SPIF_PTR_ISNULL(var), SPIF_NULL_TYPE_C(spif_charptr_t));
229 D_CONF(("var == \"%s\"\n", var));
230 for (v = spifconf_vars; v; v = v->next) {
231 if (!strcmp(SPIF_CAST_C(char *) v->var, SPIF_CAST_C(char *) var)) {
232 D_CONF(("Found it at %10p: \"%s\" == \"%s\"\n", v, v->var, v->value));
233 return (v->value);
234 }
235 }
236 D_CONF(("Not found.\n"));
237 return NULL;
238 }
239
240 static void
spifconf_put_var(spif_charptr_t var,spif_charptr_t val)241 spifconf_put_var(spif_charptr_t var, spif_charptr_t val)
242 {
243 spifconf_var_t *v, *loc = NULL, *tmp;
244
245 ASSERT(var != NULL);
246 D_CONF(("var == \"%s\", val == \"%s\"\n", var, val));
247
248 for (v = spifconf_vars; v; loc = v, v = v->next) {
249 int n;
250
251 n = strcmp(SPIF_CAST_C(char *) var, SPIF_CAST_C(char *) v->var);
252 D_CONF(("Comparing at %10p: \"%s\" -> \"%s\", n == %d\n", v, v->var, v->value, n));
253 if (n == 0) {
254 FREE(v->value);
255 if (val) {
256 v->value = val;
257 D_CONF(("Variable already defined. Replacing its value with \"%s\"\n", v->value));
258 } else {
259 D_CONF(("Variable already defined. Deleting it.\n"));
260 if (loc) {
261 loc->next = v->next;
262 } else {
263 spifconf_vars = v->next;
264 }
265 spifconf_free_var(v);
266 }
267 return;
268 } else if (n < 0) {
269 break;
270 }
271 }
272 if (!val) {
273 D_CONF(("Empty value given for non-existant variable \"%s\". Aborting.\n", var));
274 return;
275 }
276 D_CONF(("Inserting new var/val pair between \"%s\" and \"%s\"\n",
277 ((loc) ? (loc->var) : (SPIF_CAST(charptr) "-beginning-")),
278 ((v) ? (v->var) : (SPIF_CAST(charptr) "-end-"))));
279 tmp = spifconf_new_var();
280 if (loc == NULL) {
281 tmp->next = spifconf_vars;
282 spifconf_vars = tmp;
283 } else {
284 tmp->next = loc->next;
285 loc->next = tmp;
286 }
287 tmp->var = var;
288 tmp->value = val;
289 }
290
291 static spif_charptr_t
builtin_random(spif_charptr_t param)292 builtin_random(spif_charptr_t param)
293 {
294 unsigned long n, index;
295 static unsigned int rseed = 0;
296
297 REQUIRE_RVAL(!SPIF_PTR_ISNULL(param), SPIF_NULL_TYPE_C(spif_charptr_t));
298 D_PARSE(("builtin_random(%s) called\n", NONULL(param)));
299
300 if (rseed == 0) {
301 rseed = (unsigned int) (getpid() * time(NULL) % ((unsigned int) -1));
302 srand(rseed);
303 }
304 n = spiftool_num_words(param);
305 index = (int) (n * ((float) rand()) / (RAND_MAX + 1.0)) + 1;
306 D_PARSE(("random index == %lu\n", index));
307
308 return (spiftool_get_word(index, param));
309 }
310
311 static spif_charptr_t
builtin_exec(spif_charptr_t param)312 builtin_exec(spif_charptr_t param)
313 {
314 spif_uint32_t fsize, maxlen;
315 spif_charptr_t Command, Output = NULL;
316 spif_char_t OutFile[256];
317 FILE *fp;
318 int fd;
319
320 REQUIRE_RVAL(!SPIF_PTR_ISNULL(param), SPIF_NULL_TYPE_C(spif_charptr_t));
321 D_PARSE(("builtin_exec(%s) called\n", NONULL(param)));
322
323 Command = (spif_charptr_t) MALLOC(CONFIG_BUFF);
324 strcpy(SPIF_CAST_C(char *) OutFile, "Eterm-exec-");
325 fd = spiftool_temp_file(OutFile, sizeof(OutFile));
326 if ((fd < 0) || fchmod(fd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
327 libast_print_error("Unable to create unique temporary file for \"%s\" -- %s\n", param, strerror(errno));
328 return ((spif_charptr_t) NULL);
329 }
330
331 maxlen = strlen(SPIF_CAST_C(char *) param) + strlen(SPIF_CAST_C(char *) OutFile) + 8;
332 if (maxlen > CONFIG_BUFF) {
333 libast_print_error("Parse error in file %s, line %lu: Cannot execute command, line too long\n",
334 file_peek_path(), file_peek_line());
335 return ((spif_charptr_t) NULL);
336 }
337 strcpy(SPIF_CAST_C(char *) Command, SPIF_CAST_C(char *) param);
338 strcat(SPIF_CAST_C(char *) Command, " >");
339 strcat(SPIF_CAST_C(char *) Command, SPIF_CAST_C(char *) OutFile);
340 system(SPIF_CAST_C(char *) Command);
341 if ((fp = fdopen(fd, "rb")) != NULL) {
342 fseek(fp, 0, SEEK_END);
343 fsize = ftell(fp);
344 rewind(fp);
345 if (fsize) {
346 Output = (spif_charptr_t) MALLOC(fsize + 1);
347 fread(Output, fsize, 1, fp);
348 Output[fsize] = 0;
349 fclose(fp);
350 remove(SPIF_CAST_C(char *) OutFile);
351 Output = spiftool_condense_whitespace(Output);
352 } else {
353 libast_print_warning("Command at line %lu of file %s returned no output.\n",
354 file_peek_line(), file_peek_path());
355 }
356 } else {
357 libast_print_warning("Output file %s could not be created. (line %lu of file %s)\n", NONULL(OutFile), file_peek_line(), file_peek_path());
358 }
359 FREE(Command);
360
361 return (Output);
362 }
363
364 static spif_charptr_t
builtin_get(spif_charptr_t param)365 builtin_get(spif_charptr_t param)
366 {
367 spif_charptr_t s, f, v;
368 unsigned short n;
369
370 if (!param || ((n = spiftool_num_words(param)) > 2)) {
371 libast_print_error("Parse error in file %s, line %lu: Invalid syntax for %get(). Syntax is: %get(variable)\n", file_peek_path(),
372 file_peek_line());
373 return NULL;
374 }
375
376 D_PARSE(("builtin_get(%s) called\n", param));
377 s = spiftool_get_word(1, param);
378 if (n == 2) {
379 f = spiftool_get_word(2, param);
380 } else {
381 f = NULL;
382 }
383 v = spifconf_get_var(s);
384 FREE(s);
385 if (v) {
386 if (f) {
387 FREE(f);
388 }
389 return (SPIF_CAST(charptr) STRDUP(v));
390 } else if (f) {
391 return f;
392 } else {
393 return NULL;
394 }
395 }
396
397 static spif_charptr_t
builtin_put(spif_charptr_t param)398 builtin_put(spif_charptr_t param)
399 {
400 spif_charptr_t var, val;
401
402 if (!param || (spiftool_num_words(param) != 2)) {
403 libast_print_error("Parse error in file %s, line %lu: Invalid syntax for %put(). Syntax is: %put(variable value)\n", file_peek_path(),
404 file_peek_line());
405 return NULL;
406 }
407
408 D_PARSE(("builtin_put(%s) called\n", param));
409 var = spiftool_get_word(1, param);
410 val = spiftool_get_word(2, param);
411 spifconf_put_var(var, val);
412 return NULL;
413 }
414
415 static spif_charptr_t
builtin_dirscan(spif_charptr_t param)416 builtin_dirscan(spif_charptr_t param)
417 {
418 int i;
419 unsigned long n;
420 DIR *dirp;
421 struct dirent *dp;
422 struct stat filestat;
423 spif_charptr_t dir, buff;
424
425 if (!param || (spiftool_num_words(param) != 1)) {
426 libast_print_error("Parse error in file %s, line %lu: Invalid syntax for %dirscan(). Syntax is: %dirscan(directory)\n",
427 file_peek_path(), file_peek_line());
428 return NULL;
429 }
430 D_PARSE(("builtin_dirscan(%s)\n", param));
431 dir = spiftool_get_word(1, param);
432 dirp = opendir(SPIF_CAST_C(char *) dir);
433 if (!dirp) {
434 return NULL;
435 }
436 buff = (spif_charptr_t) MALLOC(CONFIG_BUFF);
437 *buff = 0;
438 n = CONFIG_BUFF;
439
440 for (i = 0; (dp = readdir(dirp)) != NULL;) {
441 spif_char_t fullname[PATH_MAX];
442
443 snprintf(SPIF_CAST_C(char *) fullname, sizeof(fullname), "%s/%s", dir, dp->d_name);
444 if (stat(SPIF_CAST_C(char *) fullname, &filestat)) {
445 D_PARSE((" -> Couldn't stat() file %s -- %s\n", fullname, strerror(errno)));
446 } else {
447 if (S_ISREG(filestat.st_mode)) {
448 unsigned long len;
449
450 len = strlen(dp->d_name);
451 if (len < n) {
452 strcat(SPIF_CAST_C(char *) buff, dp->d_name);
453 strcat(SPIF_CAST_C(char *) buff, " ");
454 n -= len + 1;
455 }
456 }
457 }
458 if (n < 2) {
459 break;
460 }
461 }
462 closedir(dirp);
463 return buff;
464 }
465
466 static spif_charptr_t
builtin_version(spif_charptr_t param)467 builtin_version(spif_charptr_t param)
468 {
469 USE_VAR(param);
470 D_PARSE(("builtin_version(%s) called\n", NONULL(param)));
471
472 return (SPIF_CAST(charptr) STRDUP(libast_program_version));
473 }
474
475 static spif_charptr_t
builtin_appname(spif_charptr_t param)476 builtin_appname(spif_charptr_t param)
477 {
478 char buff[256];
479
480 USE_VAR(param);
481 D_PARSE(("builtin_appname(%s) called\n", NONULL(param)));
482
483 snprintf(buff, sizeof(buff), "%s-%s", libast_program_name, libast_program_version);
484 return (SPIF_CAST(charptr) STRDUP(buff));
485 }
486
487 /* spifconf_shell_expand() takes care of shell variable expansion, quote conventions,
488 calling of built-in functions, etc. -- mej */
489 spif_charptr_t
spifconf_shell_expand(spif_charptr_t s)490 spifconf_shell_expand(spif_charptr_t s)
491 {
492 register spif_charptr_t tmp;
493 register spif_charptr_t pbuff = s, tmp1;
494 register spif_uint32_t j, k, l = 0;
495 spif_char_t newbuff[CONFIG_BUFF];
496 spif_uint8_t in_single = 0, in_double = 0;
497 spif_uint32_t cnt1 = 0, cnt2 = 0;
498 const spif_uint32_t max = CONFIG_BUFF - 1;
499 spif_charptr_t Command, Output, EnvVar;
500
501 ASSERT_RVAL(s != NULL, SPIF_NULL_TYPE(charptr));
502
503 #if 0
504 newbuff = (spif_charptr_t) MALLOC(CONFIG_BUFF);
505 #endif
506
507 for (j = 0; *pbuff && j < max; pbuff++, j++) {
508 switch (*pbuff) {
509 case '~':
510 D_CONF(("Tilde detected.\n"));
511 EnvVar = SPIF_CAST(charptr) getenv("HOME");
512 if (!in_single && !in_double && EnvVar && *EnvVar) {
513 spiftool_safe_strncpy(newbuff + j, EnvVar, max - j);
514 cnt1 = strlen(SPIF_CAST_C(char *) EnvVar) - 1;
515 cnt2 = max - j - 1;
516 j += MIN(cnt1, cnt2);
517 } else {
518 newbuff[j] = *pbuff;
519 }
520 break;
521 case '\\':
522 D_CONF(("Escape sequence detected.\n"));
523 if (!in_single || (in_single && *(pbuff + 1) == '\'')) {
524 switch (tolower(*(++pbuff))) {
525 case 'n':
526 newbuff[j] = '\n';
527 break;
528 case 'r':
529 newbuff[j] = '\r';
530 break;
531 case 't':
532 newbuff[j] = '\t';
533 break;
534 case 'b':
535 newbuff[j] = '\b';
536 break;
537 case 'f':
538 newbuff[j] = '\f';
539 break;
540 case 'a':
541 newbuff[j] = '\a';
542 break;
543 case 'v':
544 newbuff[j] = '\v';
545 break;
546 case 'e':
547 newbuff[j] = '\033';
548 break;
549 default:
550 newbuff[j] = *pbuff;
551 break;
552 }
553 } else {
554 newbuff[j++] = *(pbuff++);
555 newbuff[j] = *pbuff;
556 }
557 break;
558 case '%':
559 D_CONF(("%% detected.\n"));
560 for (k = 0, pbuff++; builtins[k].name != NULL; k++) {
561 D_PARSE(("Checking for function %%%s, pbuff == \"%s\"\n", builtins[k].name, pbuff));
562 l = strlen(SPIF_CAST_C(char *) builtins[k].name);
563 if (!strncasecmp(SPIF_CAST_C(char *) builtins[k].name, SPIF_CAST_C(char *) pbuff, l)
564 && ((pbuff[l] == '(')
565 || (pbuff[l] == ' '
566 && pbuff[l + 1] == ')'))) {
567 break;
568 }
569 }
570 if (builtins[k].name == NULL) {
571 newbuff[j] = *pbuff;
572 } else {
573 D_CONF(("Call to built-in function %s detected.\n", builtins[k].name));
574 Command = SPIF_CAST(charptr) MALLOC(CONFIG_BUFF);
575 pbuff += l;
576 if (*pbuff != '(')
577 pbuff++;
578 for (tmp1 = Command, pbuff++, l = 1; l && *pbuff; pbuff++, tmp1++) {
579 switch (*pbuff) {
580 case '(':
581 l++;
582 *tmp1 = *pbuff;
583 break;
584 case ')':
585 l--;
586 default:
587 *tmp1 = *pbuff;
588 break;
589 }
590 }
591 *(--tmp1) = 0;
592 if (l) {
593 libast_print_error("parse error in file %s, line %lu: Mismatched parentheses\n", file_peek_path(), file_peek_line());
594 return SPIF_NULL_TYPE(charptr);
595 }
596 Command = spifconf_shell_expand(Command);
597 Output = SPIF_CAST(charptr) (builtins[k].ptr) (Command);
598 FREE(Command);
599 if (Output) {
600 if (*Output) {
601 spiftool_safe_strncpy(newbuff + j, Output, max - j);
602 l = strlen(SPIF_CAST_C(char *) Output) - 1;
603 cnt2 = max - j - 1;
604 j += MIN(l, cnt2);
605 } else {
606 j--;
607 }
608 FREE(Output);
609 } else {
610 j--;
611 }
612 pbuff--;
613 }
614 break;
615 case '`':
616 #if ALLOW_BACKQUOTE_EXEC
617 D_CONF(("Backquotes detected. Evaluating expression.\n"));
618 if (!in_single) {
619 Command = SPIF_CAST(charptr) MALLOC(CONFIG_BUFF);
620 l = 0;
621 for (pbuff++; *pbuff && *pbuff != '`' && l < max; pbuff++, l++) {
622 Command[l] = *pbuff;
623 }
624 ASSERT_RVAL(l < CONFIG_BUFF, NULL);
625 Command[l] = 0;
626 Command = spifconf_shell_expand(Command);
627 Output = builtin_exec(Command);
628 FREE(Command);
629 if (Output) {
630 if (*Output) {
631 spiftool_safe_strncpy(newbuff + j, Output, max - j);
632 l = strlen(SPIF_CAST_C(char *) Output) - 1;
633 cnt2 = max - j - 1;
634 j += MIN(l, cnt2);
635 } else {
636 j--;
637 }
638 FREE(Output);
639 } else {
640 j--;
641 }
642 } else {
643 newbuff[j] = *pbuff;
644 }
645 #else
646 libast_print_warning("Backquote execution support not compiled in, ignoring\n");
647 newbuff[j] = *pbuff;
648 #endif
649 break;
650 case '$':
651 D_CONF(("Environment variable detected. Evaluating.\n"));
652 if (!in_single) {
653 EnvVar = SPIF_CAST(charptr) MALLOC(128);
654 switch (*(++pbuff)) {
655 case '{':
656 for (pbuff++, k = 0; *pbuff != '}' && k < 127; k++, pbuff++)
657 EnvVar[k] = *pbuff;
658 break;
659 case '(':
660 for (pbuff++, k = 0; *pbuff != ')' && k < 127; k++, pbuff++)
661 EnvVar[k] = *pbuff;
662 break;
663 default:
664 for (k = 0; (isalnum(*pbuff) || *pbuff == '_') && k < 127; k++, pbuff++)
665 EnvVar[k] = *pbuff;
666 break;
667 }
668 EnvVar[k] = 0;
669 tmp = SPIF_CAST(charptr) getenv(SPIF_CAST_C(char *) EnvVar);
670 if (tmp && *tmp) {
671 spiftool_safe_strncpy(newbuff, tmp, max - j);
672 cnt1 = strlen(SPIF_CAST_C(char *) tmp) - 1;
673 cnt2 = max - j - 1;
674 j += MIN(cnt1, cnt2);
675 }
676 pbuff--;
677 } else {
678 newbuff[j] = *pbuff;
679 }
680 break;
681 case '\"':
682 D_CONF(("Double quotes detected.\n"));
683 if (!in_single) {
684 if (in_double) {
685 in_double = 0;
686 } else {
687 in_double = 1;
688 }
689 }
690 newbuff[j] = *pbuff;
691 break;
692
693 case '\'':
694 D_CONF(("Single quotes detected.\n"));
695 if (in_single) {
696 in_single = 0;
697 } else {
698 in_single = 1;
699 }
700 newbuff[j] = *pbuff;
701 break;
702
703 default:
704 newbuff[j] = *pbuff;
705 }
706 }
707 ASSERT_RVAL(j < CONFIG_BUFF, NULL);
708 newbuff[j] = 0;
709
710 D_PARSE(("spifconf_shell_expand(%s) returning \"%s\"\n", s, newbuff));
711 D_PARSE((" strlen(s) == %lu, strlen(newbuff) == %lu, j == %lu\n",
712 strlen(SPIF_CAST_C(char *) s),
713 strlen(SPIF_CAST_C(char *) newbuff), j));
714
715 strcpy(SPIF_CAST_C(char *) s, SPIF_CAST_C(char *) newbuff);
716 #if 0
717 FREE(newbuff);
718 #endif
719 return (s);
720 }
721
722 /* The config file reader. This looks for the config file by searching CONFIG_SEARCH_PATH.
723 If it can't find a config file, it displays a warning but continues. -- mej */
724 spif_charptr_t
spifconf_find_file(const spif_charptr_t file,const spif_charptr_t dir,const spif_charptr_t pathlist)725 spifconf_find_file(const spif_charptr_t file, const spif_charptr_t dir, const spif_charptr_t pathlist)
726 {
727 static spif_char_t name[PATH_MAX], full_path[PATH_MAX];
728 spif_charptr_t path, p;
729 spif_int32_t len, maxpathlen;
730 struct stat fst;
731
732 REQUIRE_RVAL(file != NULL, NULL);
733
734 getcwd(SPIF_CAST_C(char *) name, PATH_MAX);
735 D_CONF(("spifconf_find_file(\"%s\", \"%s\", \"%s\") called from directory \"%s\".\n",
736 file, NONULL(dir), NONULL(pathlist), name));
737
738 /* Make sure our supplied settings don't overflow. */
739 len = strlen(SPIF_CAST_C(char *) file) + ((dir) ? (strlen(SPIF_CAST_C(char *) dir)) : (0)) + 2;
740 if ((len > SPIF_CAST(int32) sizeof(name)) || (len <= 0)) {
741 D_CONF(("Too big. I lose. :(\n"));
742 return ((spif_charptr_t) NULL);
743 }
744
745 if (dir) {
746 strcpy(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) dir);
747 strcat(SPIF_CAST_C(char *) name, "/");
748 strcat(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) file);
749 } else {
750 strcpy(SPIF_CAST_C(char *) name, SPIF_CAST_C(char *) file);
751 }
752 len = strlen(SPIF_CAST_C(char *) name);
753 D_CONF(("Checking for file \"%s\"\n", name));
754 if ((!access(SPIF_CAST_C(char *) name, R_OK))
755 && (!stat(SPIF_CAST_C(char *) name, &fst))
756 && (!S_ISDIR(fst.st_mode))) {
757 D_CONF(("Found \"%s\"\n", name));
758 return ((spif_charptr_t) name);
759 }
760
761 /* maxpathlen is the longest possible path we can stuff into name[]. The - 2 saves room for
762 an additional / and the trailing null. */
763 if ((maxpathlen = sizeof(name) - len - 2) <= 0) {
764 D_CONF(("Too big. I lose. :(\n"));
765 return ((spif_charptr_t) NULL);
766 }
767
768 for (path = pathlist; path != NULL && *path != '\0'; path = p) {
769 short n;
770
771 /* Calculate the length of the next directory in the path */
772 if ((p = SPIF_CAST(charptr) strchr(SPIF_CAST_C(char *) path, ':')) != NULL) {
773 n = p++ - path;
774 } else {
775 n = strlen(SPIF_CAST_C(char *) path);
776 }
777
778 /* Don't try if it's too long */
779 if (n > 0 && n <= maxpathlen) {
780 /* Compose the /path/file combo */
781 memcpy(full_path, path, n);
782 if (full_path[n - 1] != '/') {
783 full_path[n++] = '/';
784 }
785 full_path[n] = '\0';
786 strcat(SPIF_CAST_C(char *) full_path, SPIF_CAST_C(char *) name);
787
788 D_CONF(("Checking for file \"%s\"\n", full_path));
789 if ((!access(SPIF_CAST_C(char *) full_path, R_OK))
790 && (!stat(SPIF_CAST_C(char *) full_path, &fst))
791 && (!S_ISDIR(fst.st_mode))) {
792 D_CONF(("Found \"%s\"\n", full_path));
793 return ((spif_charptr_t) full_path);
794 }
795 }
796 }
797 D_CONF(("spifconf_find_file(): File \"%s\" not found in path.\n", name));
798 return ((spif_charptr_t) NULL);
799 }
800
801 FILE *
spifconf_open_file(spif_charptr_t name)802 spifconf_open_file(spif_charptr_t name)
803 {
804 FILE *fp;
805 spif_cmp_t ver;
806 spif_str_t ver_str;
807 spif_char_t buff[256], test[30];
808 spif_charptr_t begin_ptr, end_ptr;
809 spif_stridx_t testlen;
810
811 ASSERT_RVAL(name != NULL, NULL);
812
813 snprintf(SPIF_CAST_C(char *) test, sizeof(test), "<%s-", libast_program_name);
814 testlen = SPIF_CAST(stridx) strlen(SPIF_CAST_C(char *) test);
815
816 /* Read first line from config file. Using spif_str_new_from_fp() would read the
817 * whole file, so we don't do that here. */
818 fp = fopen(SPIF_CAST_C(char *) name, "rt");
819 REQUIRE_RVAL(fp != NULL, NULL);
820 fgets(SPIF_CAST_C(char *) buff, 256, fp);
821 ver_str = spif_str_new_from_ptr(buff);
822
823 /* Check for magic string. */
824 if (spif_str_ncasecmp_with_ptr(ver_str, test, testlen)) {
825 libast_print_warning("%s exists but does not contain the proper magic string (<%s-%s>)\n",
826 name, libast_program_name, libast_program_version);
827 fclose(fp);
828 spif_str_del(ver_str);
829 return NULL;
830 }
831
832 /* Check version number against current application version. */
833 begin_ptr = SPIF_STR_STR(ver_str) + spif_str_index(ver_str, SPIF_CAST(char) '-') + 1;
834 end_ptr = SPIF_STR_STR(ver_str) + spif_str_index(ver_str, SPIF_CAST(char) '>');
835 D_CONF(("Begin pointer is %10p (%s), end pointer is %10p (%s), length is %d, buffer size is %d\n",
836 begin_ptr, begin_ptr, end_ptr, end_ptr, SPIF_CAST_C(int) (end_ptr - begin_ptr), sizeof(buff)));
837 if (SPIF_PTR_ISNULL(end_ptr)) {
838 spiftool_safe_strncpy(buff, begin_ptr, sizeof(buff));
839 } else {
840 testlen = MIN(SPIF_CAST_C(int) sizeof(buff), SPIF_CAST_C(int) (end_ptr - begin_ptr + 1));
841 spiftool_safe_strncpy(buff, begin_ptr, testlen);
842 }
843 ver = spiftool_version_compare(buff, libast_program_version);
844 if (SPIF_CMP_IS_GREATER(ver)) {
845 libast_print_warning("Config file is designed for a newer version of %s\n",
846 libast_program_name);
847 }
848 spif_str_del(ver_str);
849 return (fp);
850 }
851
852 #define SPIFCONF_PARSE_RET() do {if (!fp) {file_pop(); ctx_end();} return;} while (0)
853 void
spifconf_parse_line(FILE * fp,spif_charptr_t buff)854 spifconf_parse_line(FILE * fp, spif_charptr_t buff)
855 {
856 register unsigned long i = 0;
857 unsigned char id;
858 void *state = NULL;
859
860 ASSERT(buff != NULL);
861
862 if (!(*buff) || *buff == '\n' || *buff == '#' || *buff == '<') {
863 SPIFCONF_PARSE_RET();
864 }
865 if (fp == NULL) {
866 file_push(NULL, SPIF_CAST(charptr) "<argv>", NULL, 0, 0);
867 ctx_begin(1);
868 buff = spiftool_get_pword(2, buff);
869 if (!buff) {
870 SPIFCONF_PARSE_RET();
871 }
872 }
873 id = ctx_peek_id();
874 spiftool_chomp(buff);
875 D_CONF(("Parsing line #%lu of file %s\n", file_peek_line(), file_peek_path()));
876 switch (*buff) {
877 case '#':
878 case '\0':
879 SPIFCONF_PARSE_RET();
880 case '%':
881 if (!BEG_STRCASECMP(spiftool_get_pword(1, buff + 1), "include ")) {
882 spif_charptr_t path;
883 FILE *fp;
884
885 spifconf_shell_expand(SPIF_CAST(charptr) buff);
886 path = spiftool_get_word(2, buff + 1);
887 if ((fp = spifconf_open_file(path)) == NULL) {
888 libast_print_error("Parsing file %s, line %lu: Unable to locate %%included config file %s (%s), continuing\n", file_peek_path(),
889 file_peek_line(), path, strerror(errno));
890 } else {
891 file_push(fp, path, NULL, 1, 0);
892 }
893 } else if (!BEG_STRCASECMP(spiftool_get_pword(1, buff + 1), "preproc ")) {
894 spif_char_t cmd[PATH_MAX], fname[PATH_MAX];
895 spif_charptr_t outfile;
896 int fd;
897 FILE *fp;
898
899 if (file_peek_preproc()) {
900 SPIFCONF_PARSE_RET();
901 }
902 strcpy(SPIF_CAST_C(char *) fname, "Eterm-preproc-");
903 fd = spiftool_temp_file(fname, PATH_MAX);
904 outfile = SPIF_CAST(charptr) STRDUP(fname);
905 snprintf(SPIF_CAST_C(char *) cmd, PATH_MAX, "%s < %s > %s",
906 spiftool_get_pword(2, buff), file_peek_path(), fname);
907 system(SPIF_CAST_C(char *) cmd);
908 fp = fdopen(fd, "rt");
909 if (fp != NULL) {
910 fclose(file_peek_fp());
911 file_poke_fp(fp);
912 file_poke_preproc(1);
913 file_poke_outfile(outfile);
914 }
915 } else {
916 if (file_peek_skip()) {
917 SPIFCONF_PARSE_RET();
918 }
919 spifconf_shell_expand(SPIF_CAST(charptr) buff);
920 }
921 break;
922 case 'b':
923 if (file_peek_skip()) {
924 SPIFCONF_PARSE_RET();
925 }
926 if (!BEG_STRCASECMP(buff, "begin ")) {
927 ctx_begin(2);
928 break;
929 }
930 /* Intentional pass-through */
931 case 'e':
932 if (!BEG_STRCASECMP(buff, "end ") || !strcasecmp(SPIF_CAST_C(char *) buff, "end")) {
933 ctx_end();
934 break;
935 }
936 /* Intentional pass-through */
937 default:
938 if (file_peek_skip()) {
939 SPIFCONF_PARSE_RET();
940 }
941 spifconf_shell_expand(SPIF_CAST(charptr) buff);
942 ctx_poke_state((*ctx_id_to_func(id)) (buff, ctx_peek_state()));
943 }
944 SPIFCONF_PARSE_RET();
945 }
946
947 #undef SPIFCONF_PARSE_RET
948
949 spif_charptr_t
spifconf_parse(spif_charptr_t conf_name,const spif_charptr_t dir,const spif_charptr_t path)950 spifconf_parse(spif_charptr_t conf_name, const spif_charptr_t dir, const spif_charptr_t path)
951 {
952 FILE *fp;
953 spif_charptr_t name = NULL, p = SPIF_CAST(charptr) ".";
954 spif_char_t buff[CONFIG_BUFF], orig_dir[PATH_MAX];
955
956 REQUIRE_RVAL(conf_name != NULL, 0);
957
958 *orig_dir = 0;
959 if (path) {
960 if ((name = spifconf_find_file(conf_name, dir, path)) != NULL) {
961 if ((p = SPIF_CAST(charptr) strrchr(SPIF_CAST_C(char *) name, '/')) != NULL) {
962 getcwd(SPIF_CAST_C(char *) orig_dir, PATH_MAX);
963 *p = 0;
964 p = name;
965 chdir(SPIF_CAST_C(char *) name);
966 } else {
967 p = SPIF_CAST(charptr) ".";
968 }
969 } else {
970 return NULL;
971 }
972 }
973 if ((fp = spifconf_open_file(conf_name)) == NULL) {
974 return NULL;
975 }
976 /* Line count starts at 1 because spifconf_open_file() parses the first line. */
977 file_push(fp, conf_name, NULL, 1, 0);
978
979 for (; fstate_idx > 0;) {
980 for (; fgets(SPIF_CAST_C(char *) buff, CONFIG_BUFF, file_peek_fp());) {
981 file_inc_line();
982 if (!strchr(SPIF_CAST_C(char *) buff, '\n')) {
983 libast_print_error("Parse error in file %s, line %lu: line too long\n",
984 file_peek_path(), file_peek_line());
985 for (; fgets(SPIF_CAST_C(char *) buff, CONFIG_BUFF, file_peek_fp())
986 && !strrchr(SPIF_CAST_C(char *) buff, '\n'););
987 continue;
988 }
989 spifconf_parse_line(fp, buff);
990 }
991 fclose(file_peek_fp());
992 if (file_peek_preproc()) {
993 remove(SPIF_CAST_C(char *) file_peek_outfile());
994 FREE(file_peek_outfile());
995 }
996 file_pop();
997 }
998 if (*orig_dir) {
999 chdir(SPIF_CAST_C(char *) orig_dir);
1000 }
1001 D_CONF(("Returning \"%s\"\n", p));
1002 return (SPIF_CAST(charptr) STRDUP(p));
1003 }
1004
1005 static void *
parse_null(spif_charptr_t buff,void * state)1006 parse_null(spif_charptr_t buff, void *state)
1007 {
1008 ASSERT_RVAL(!SPIF_PTR_ISNULL(buff), SPIF_NULL_TYPE(ptr));
1009 if (*buff == SPIFCONF_BEGIN_CHAR) {
1010 return (NULL);
1011 } else if (*buff == SPIFCONF_END_CHAR) {
1012 return (NULL);
1013 } else {
1014 libast_print_error("Parse error in file %s, line %lu: Not allowed in \"null\" context: \"%s\"\n", file_peek_path(), file_peek_line(),
1015 buff);
1016 return (state);
1017 }
1018 }
1019
1020 /**
1021 * @defgroup DOXGRP_CONF Configuration File Parser
1022 *
1023 * This group of functions/defines/macros comprises the configuration
1024 * file parsing engine.
1025 *
1026 *
1027 * A small sample program demonstrating some of these routines can be
1028 * found @link conf_example.c here @endlink.
1029 */
1030
1031 /**
1032 * @example conf_example.c
1033 * Example code for using the config file parser.
1034 *
1035 */
1036
1037 /**
1038 * @defgroup DOXGRP_CONF_FSS File State Stack
1039 * @ingroup DOXGRP_CONF
1040 *
1041 * @note An understanding of the inner workings of the file state
1042 * stack is not necessary to use the config file parser. If you
1043 * aren't interested in understanding the LibAST internals, you should
1044 * skip most of this section and simply study the examples.
1045 *
1046 * Parsers must keep track of various state-related information when
1047 * parsing files, things like file name, line number, etc. And since
1048 * LibAST's config file parser supports the inclusion of sub-files via
1049 * its %include directive, it must keep track of multiple instances of
1050 * this information, one for each file. LibAST uses a structure array
1051 * called the file state stack.
1052 *
1053 * When config file parsing is initiated by a call to spifconf_parse(),
1054 * the information for that file is pushed onto the empty stack. For
1055 * monolithic config files, the stack retains its height throughout
1056 * the parsing cycle. However, if an @c %include directive is
1057 * encountered (and the file is successfully opened), a new set of
1058 * data is placed atop the stack via file_push(). The new file is
1059 * then parsed in its entirety, including any sub-files that it may
1060 * itself include, before its information is popped off the stack and
1061 * parsing of the original file can continue.
1062 *
1063 * Client programs should not need to modify the stack in any way.
1064 * However, use of the file_peek_path() and file_peek_line() macros
1065 * are encouraged, specifically for printing error/warning messages.
1066 * Many of the file state stack manipulation routines should probably
1067 * never be called by client programs (and are therefore marked as
1068 * internal); they are, however, made available on the off chance that
1069 * someone may get super-creative and do something neat with them.
1070 * Just don't blame LibAST if your (ab)use of internal functions
1071 * breaks the parser!
1072 */
1073
1074 /**
1075 * @defgroup DOXGRP_CONF_CTX Context Handling
1076 * @ingroup DOXGRP_CONF
1077 *
1078 * LibAST-style configuration files are organized into logical units
1079 * called "contexts." A begin/end pair is used to surround groups of
1080 * directives which should be evaluated in a given context. The begin
1081 * keyword is followed by the name of the context that will follow.
1082 * The end keyword may stand alone; anything after it is ignored.
1083 *
1084 * The parser starts out in a pseudo-context called @c null for which
1085 * LibAST employs a built-in handler that rejects any unexpected
1086 * directives with an error message. Any other context must be dealt
1087 * with by a client-specified context handler.
1088 *
1089 * Context handlers defined by the client program must conform to the
1090 * following specification:
1091 * - Accept two parameters as follows:
1092 * -# A spif_charptr_t containing the line of text to be parsed
1093 * -# A void * containing optional state information, or NULL
1094 * - Return a void * containing optional state information, or NULL
1095 *
1096 * Although nothing else is strictly @em required by LibAST, if you
1097 * want your parser to actually work, it needs to handle the LibAST
1098 * context handler calling conventions. The following is a
1099 * step-by-step walk-through of how LibAST calls parser functions:
1100 *
1101 * -# When LibAST encounters a @c begin keyword followed by a one or
1102 * more additional words (words are separated by whitespace
1103 * according to shell conventions), the word immediately following
1104 * the @c begin keyword is interpreted as the context name.
1105 * -# LibAST checks its list of registered context handlers for one
1106 * that matches the given context name. If none is found, an error
1107 * is printed, and the parser skips the entire context (until the
1108 * next @c end keyword). Otherwise, go to the next step.
1109 * -# The registered context handler function is called. The value
1110 * #SPIFCONF_BEGIN_STRING is passed as the first parameter (which I'll
1111 * call @a buff ), and NULL is passed as the second parameter
1112 * (which I'll call @a state ).
1113 * -# The context handler should handle this using a statement similar
1114 * to the following:
1115 * @code
1116 * if (*buff == SPIFCONF_BEGIN_CHAR) {
1117 * @endcode
1118 * (The value of #SPIFCONF_BEGIN_CHAR is such that it should never
1119 * occur in normal config file text.)
1120 * If the handler does not require any persistent state information
1121 * to be kept between calls, it may simply return NULL here.
1122 * Otherwise, this portion of the handler should perform any
1123 * initialization required for the state information and return a
1124 * pointer to that information.
1125 * -# The value returned by the context handler is stored by LibAST
1126 * for later use, and parsing of the config file continues with the
1127 * next line.
1128 * -# Each subsequent line encountered in the config file which does
1129 * not start with the keyword @c end is passed to the context
1130 * handler function as the first parameter. The second parameter
1131 * will contain the handler's previous return value, the persistent
1132 * state information pointer. The handler, of course, should
1133 * continue returning the state information pointer.
1134 * -# Once the @c end keyword is encountered, the context handler is
1135 * called with #SPIFCONF_END_STRING as the first parameter and the
1136 * state information pointer as the second parameter. This
1137 * situation should be caught by some code like this:
1138 * @code
1139 * if (*buff == SPIFCONF_END_CHAR) {
1140 * @endcode
1141 * Again, the handler should simply return NULL if no state
1142 * information is being kept. Otherwise, any post-processing or
1143 * cleanup needed should be done, possibly including the freeing of
1144 * the state pointer, etc. The handler should then return NULL.
1145 * -# LibAST reverts to the aforementioned @c null context and
1146 * continues parsing as above.
1147 *
1148 * A sample implementation of context handlers which demonstrate use
1149 * of this mechanism can be found in the @link conf_example.c config
1150 * file parser example @endlink.
1151 */
1152
1153