xref: /minix/minix/lib/libc/gen/configfile.c (revision e3b78ef1)
1 /*	config_read(), _delete(), _length() - Generic config file routines.
2  *							Author: Kees J. Bot
3  *								5 Jun 1999
4  */
5 #define nil ((void*)0)
6 #if __minix_vmd
7 #include <minix/stubs.h>
8 #else
9 #define fstat _fstat
10 #define stat _stat
11 #endif
12 #include <sys/types.h>
13 #include <stdio.h>
14 #include <stddef.h>
15 #include <stdlib.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <time.h>
19 #include <sys/stat.h>
20 #if __minix_vmd
21 #include <minix/asciictype.h>
22 #else
23 #include <ctype.h>
24 #endif
25 #define _c /* not const */
26 #include <configfile.h>
27 
28 typedef struct configfile {	/* List of (included) configuration files. */
29 	struct configfile *next;	/* A list indeed. */
30 	time_t		ctime;		/* Last changed time, -1 if no file. */
31 	char		name[1];	/* File name. */
32 } configfile_t;
33 
34 /* Size of a configfile_t given a file name of length 'len'. */
35 #define configfilesize(len)	(offsetof(configfile_t, name) + 1 + (len))
36 
37 typedef struct firstconfig {	/* First file and first word share a slot. */
38 	configfile_t	*filelist;
39 	char		new;		/* Set when created. */
40 	config_t	config1;
41 } firstconfig_t;
42 
43 /* Size of a config_t given a word of lenght 'len'.  Same for firstconfig_t. */
44 #define config0size()		(offsetof(config_t, word))
45 #define configsize(len)		(config0size() + 1 + (len))
46 #define firstconfigsize(len)	\
47 			(offsetof(firstconfig_t, config1) + configsize(len))
48 
49 /* Translate address of first config word to enclosing firstconfig_t and vv. */
50 #define cfg2fcfg(p)	\
51     ((firstconfig_t *) ((char *) (p) - offsetof(firstconfig_t, config1)))
52 #define fcfg2cfg(p)	(&(p)->config1)
53 
54 /* Variables used while building data. */
55 static configfile_t *c_files;		/* List of (included) config files. */
56 static int c_flags;			/* Flags argument of config_read(). */
57 static FILE *c_fp;			/* Current open file. */
58 static char *c_file;			/* Current open file name. */
59 static unsigned c_line;			/* Current line number. */
60 static int c;				/* Next character. */
61 
62 static void *allocate(void *mem, size_t size)
63 /* Like realloc(), but checked. */
64 {
65     if ((mem= realloc(mem, size)) == nil) {
66 	fprintf(stderr, "\"%s\", line %u: Out of memory\n", c_file, c_line);
67 	exit(1);
68     }
69     return mem;
70 }
71 
72 #define deallocate(mem)	free(mem)
73 
74 static void delete_filelist(configfile_t *cfgf)
75 /* Delete configuration file list. */
76 {
77     void *junk;
78 
79     while (cfgf != nil) {
80 	junk= cfgf;
81 	cfgf= cfgf->next;
82 	deallocate(junk);
83     }
84 }
85 
86 static void delete_config(config_t *cfg)
87 /* Delete configuration file data. */
88 {
89     config_t *next, *list, *junk;
90 
91     next= cfg;
92     list= nil;
93     for (;;) {
94 	if (next != nil) {
95 	    /* Push the 'next' chain in reverse on the 'list' chain, putting
96 	     * a leaf cell (next == nil) on top of 'list'.
97 	     */
98 	    junk= next;
99 	    next= next->next;
100 	    junk->next= list;
101 	    list= junk;
102 	} else
103 	if (list != nil) {
104 	    /* Delete the leaf cell.  If it has a sublist then that becomes
105 	     * the 'next' chain.
106 	     */
107 	    junk= list;
108 	    next= list->list;
109 	    list= list->next;
110 	    deallocate(junk);
111 	} else {
112 	    /* Both chains are gone. */
113 	    break;
114 	}
115     }
116 }
117 
118 void config_delete(config_t *cfg1)
119 /* Delete configuration file data, being careful with the odd first one. */
120 {
121     firstconfig_t *fcfg= cfg2fcfg(cfg1);
122 
123     delete_filelist(fcfg->filelist);
124     delete_config(fcfg->config1.next);
125     delete_config(fcfg->config1.list);
126     deallocate(fcfg);
127 }
128 
129 static void nextc(void)
130 /* Read the next character of the current file into 'c'. */
131 {
132     if (c == '\n') c_line++;
133     c= getc(c_fp);
134     if (c == EOF && ferror(c_fp)) {
135 	fprintf(stderr, "\"%s\", line %u: %s\n",
136 	    c_file, c_line, strerror(errno));
137 	exit(1);
138     }
139 }
140 
141 static void skipwhite(void)
142 /* Skip whitespace and comments. */
143 {
144     while (isspace(c)) {
145 	nextc();
146 	if (c == '#') {
147 	    do nextc(); while (c != EOF && c != '\n');
148 	}
149     }
150 }
151 
152 static void __dead parse_err(void)
153 /* Tell user that you can't parse past the current character. */
154 {
155     char sc[2];
156 
157     sc[0]= c;
158     sc[1]= 0;
159     fprintf(stderr, "\"%s\", line %u: parse error at '%s'\n",
160 	c_file, c_line, c == EOF ? "EOF" : sc);
161     exit(1);
162 }
163 
164 static config_t *read_word(void)
165 /* Read a word or string. */
166 {
167     config_t *w;
168     size_t i, len;
169     int q;
170     static char SPECIAL[] = "!#$%&*+-./:<=>?[\\]^_|~";
171 
172     i= 0;
173     len= 32;
174     w= allocate(nil, configsize(32));
175     w->next= nil;
176     w->list= nil;
177     w->file= c_file;
178     w->line= c_line;
179     w->flags= 0;
180 
181     /* Is it a quoted string? */
182     if (c == '\'' || c == '"') {
183 	q= c;	/* yes */
184 	nextc();
185     } else {
186 	q= -1;	/* no */
187     }
188 
189     for (;;) {
190 	if (i == len) {
191 	    len+= 32;
192 	    w= allocate(w, configsize(len));
193 	}
194 
195 	if (q == -1) {
196 	    /* A word consists of letters, numbers and a few special chars. */
197 	    if (!isalnum(c) && c < 0x80 && strchr(SPECIAL, c) == nil) break;
198 	} else {
199 	    /* Strings are made up of anything except newlines. */
200 	    if (c == EOF || c == '\n') {
201 		fprintf(stderr,
202 		    "\"%s\", line %u: string at line %u not closed\n",
203 		    c_file, c_line, w->line);
204 		exit(1);
205 		break;
206 	    }
207 	    if (c == q) {	/* Closing quote? */
208 		nextc();
209 		break;
210 	    }
211 	}
212 
213 	if (c != '\\') {	/* Simply add non-escapes. */
214 	    w->word[i++]= c;
215 	    nextc();
216 	} else {		/* Interpret an escape. */
217 	    nextc();
218 	    if (isspace(c)) {
219 		skipwhite();
220 		continue;
221 	    }
222 
223 	    if (c_flags & CFG_ESCAPED) {
224 		w->word[i++]= '\\';	/* Keep the \ for the caller. */
225 		if (i == len) {
226 		    len+= 32;
227 		    w= allocate(w, configsize(len));
228 		}
229 		w->flags |= CFG_ESCAPED;
230 	    }
231 
232 	    if (isdigit(c)) {		/* Octal escape */
233 		int n= 3;
234 		int d= 0;
235 
236 		do {
237 		    d= d * 010 + (c - '0');
238 		    nextc();
239 		} while (--n > 0 && isdigit(c));
240 		w->word[i++]= d;
241 	    } else
242 	    if (c == 'x' || c == 'X') {	/* Hex escape */
243 		int n= 2;
244 		int d= 0;
245 
246 		nextc();
247 		if (!isxdigit(c)) {
248 		    fprintf(stderr, "\"%s\", line %u: bad hex escape\n",
249 			c_file, c_line);
250 		    exit(1);
251 		}
252 		do {
253 		    d= d * 0x10 + (islower(c) ? (c - 'a' + 0xa) :
254 				    isupper(c) ? (c - 'A' + 0xA) :
255 				    (c - '0'));
256 		    nextc();
257 		} while (--n > 0 && isxdigit(c));
258 		w->word[i++]= d;
259 	    } else {
260 		switch (c) {
261 		case 'a':	c= '\a';	break;
262 		case 'b':	c= '\b';	break;
263 		case 'e':	c= '\033';	break;
264 		case 'f':	c= '\f';	break;
265 		case 'n':	c= '\n';	break;
266 		case 'r':	c= '\r';	break;
267 		case 's':	c= ' ';		break;
268 		case 't':	c= '\t';	break;
269 		case 'v':	c= '\v';	break;
270 		default:	/* Anything else is kept as-is. */;
271 		}
272 		w->word[i++]= c;
273 		nextc();
274 	    }
275 	}
276     }
277     w->word[i]= 0;
278     if (q != -1) {
279 	w->flags |= CFG_STRING;
280     } else {
281 	int f;
282 	char *end;
283 	static char base[]= { 0, 010, 10, 0x10 };
284 
285 	if (i == 0) parse_err();
286 
287 	/* Can the word be used as a number? */
288 	for (f= 0; f < 4; f++) {
289 	    (void) strtol(w->word, &end, base[f]);
290 	    if (*end == 0) w->flags |= 1 << (f + 0);
291 	    (void) strtoul(w->word, &end, base[f]);
292 	    if (*end == 0) w->flags |= 1 << (f + 4);
293 	}
294     }
295     return allocate(w, configsize(i));
296 }
297 
298 static config_t *read_file(const char *file);
299 static config_t *read_list(void);
300 
301 static config_t *read_line(void)
302 /* Read and return one line of the config file. */
303 {
304     config_t *cline, **pcline, *clist;
305 
306     cline= nil;
307     pcline= &cline;
308 
309     for (;;) {
310 	skipwhite();
311 
312 	if (c == EOF || c == '}') {
313 if(0)	    if (cline != nil) parse_err();
314 	    break;
315 	} else
316 	if (c == ';') {
317 	    nextc();
318 	    if (cline != nil) break;
319 	} else
320 	if (cline != nil && c == '{') {
321 	    /* A sublist. */
322 	    nextc();
323 	    clist= allocate(nil, config0size());
324 	    clist->next= nil;
325 	    clist->file= c_file;
326 	    clist->line= c_line;
327 	    clist->list= read_list();
328 	    clist->flags= CFG_SUBLIST;
329 	    *pcline= clist;
330 	    pcline= &clist->next;
331 	    if (c != '}') parse_err();
332 	    nextc();
333 	} else {
334 	    *pcline= read_word();
335 	    pcline= &(*pcline)->next;
336 	}
337     }
338     return cline;
339 }
340 
341 static config_t *read_list(void)
342 /* Read and return a list of config file commands. */
343 {
344     config_t *clist, **pclist, *cline;
345 
346     clist= nil;
347     pclist= &clist;
348 
349     while ((cline= read_line()) != nil) {
350 	if (strcmp(cline->word, "include") == 0) {
351 	    config_t *file= cline->next;
352 	    if (file == nil || file->next != nil || !config_isatom(file)) {
353 		fprintf(stderr,
354 		    "\"%s\", line %u: 'include' command requires an argument\n",
355 		    c_file, cline->line);
356 		exit(1);
357 	    }
358 	    if (file->flags & CFG_ESCAPED) {
359 		char *p, *q;
360 		p= q= file->word;
361 		for (;;) {
362 		    if ((*q = *p) == '\\') *q = *++p;
363 		    if (*q == 0) break;
364 		    p++;
365 		    q++;
366 		}
367 	    }
368 	    file= read_file(file->word);
369 	    delete_config(cline);
370 	    *pclist= file;
371 	    while (*pclist != nil) pclist= &(*pclist)->next;
372 	} else {
373 	    config_t *cfg= allocate(nil, config0size());
374 	    cfg->next= nil;
375 	    cfg->list= cline;
376 	    cfg->file= cline->file;
377 	    cfg->line= cline->line;
378 	    cfg->flags= CFG_SUBLIST;
379 	    *pclist= cfg;
380 	    pclist= &cfg->next;
381 	}
382     }
383     return clist;
384 }
385 
386 static config_t *read_file(const char *file)
387 /* Read and return a configuration file. */
388 {
389     configfile_t *cfgf;
390     config_t *cfg;
391     struct stat st;
392     FILE *old_fp;	/* old_* variables store current file context. */
393     char *old_file;
394     unsigned old_line;
395     int old_c;
396     size_t n;
397     char *slash;
398 
399     old_fp= c_fp;
400     old_file= c_file;
401     old_line= c_line;
402     old_c= c;
403 
404     n= 0;
405     if (file[0] != '/' && old_file != nil
406 			&& (slash= strrchr(old_file, '/')) != nil) {
407 	n= slash - old_file + 1;
408     }
409     cfgf= allocate(nil, configfilesize(n + strlen(file)));
410     memcpy(cfgf->name, old_file, n);
411     strcpy(cfgf->name + n, file);
412     cfgf->next= c_files;
413     c_files= cfgf;
414 
415     c_file= cfgf->name;
416     c_line= 0;
417 
418     if ((c_fp= fopen(file, "r")) == nil || fstat(fileno(c_fp), &st) < 0) {
419 	if (errno != ENOENT) {
420 	    fprintf(stderr, "\"%s\", line 1: %s\n", file, strerror(errno));
421 	    exit(1);
422 	}
423 	cfgf->ctime= -1;
424 	c= EOF;
425     } else {
426 	cfgf->ctime= st.st_ctime;
427 	c= '\n';
428     }
429 
430     cfg= read_list();
431     if (c != EOF) parse_err();
432 
433     if (c_fp != nil) fclose(c_fp);
434     c_fp= old_fp;
435     c_file= old_file;
436     c_line= old_line;
437     c= old_c;
438     return cfg;
439 }
440 
441 config_t *config_read(const char *file, int flags, config_t *cfg)
442 /* Read and parse a configuration file. */
443 {
444     if (cfg != nil) {
445 	/* First check if any of the involved files has changed. */
446 	firstconfig_t *fcfg;
447 	configfile_t *cfgf;
448 	struct stat st;
449 
450 	fcfg= cfg2fcfg(cfg);
451 	for (cfgf= fcfg->filelist; cfgf != nil; cfgf= cfgf->next) {
452 	    if (stat(cfgf->name, &st) < 0) {
453 		if (errno != ENOENT) break;
454 		st.st_ctime= -1;
455 	    }
456 	    if (st.st_ctime != cfgf->ctime) break;
457 	}
458 
459 	if (cfgf == nil) return cfg;	/* Everything as it was. */
460 	config_delete(cfg);		/* Otherwise delete and reread. */
461     }
462 
463     errno= 0;
464     c_files= nil;
465     c_flags= flags;
466     cfg= read_file(file);
467 
468     if (cfg != nil) {
469 	/* Change first word to have a hidden pointer to a file list. */
470 	size_t len= strlen(cfg->word);
471 	firstconfig_t *fcfg;
472 
473 	fcfg= allocate(cfg, firstconfigsize(len));
474 	memmove(&fcfg->config1, fcfg, configsize(len));
475 	fcfg->filelist= c_files;
476 	fcfg->new= 1;
477 	return fcfg2cfg(fcfg);
478     }
479     /* Couldn't read (errno != 0) of nothing read (errno == 0). */
480     delete_filelist(c_files);
481     delete_config(cfg);
482     return nil;
483 }
484 
485 int config_renewed(config_t *cfg)
486 {
487     int new;
488 
489     if (cfg == nil) {
490 	new= 1;
491     } else {
492 	new= cfg2fcfg(cfg)->new;
493 	cfg2fcfg(cfg)->new= 0;
494     }
495     return new;
496 }
497 
498 size_t config_length(config_t *cfg)
499 /* Count the number of items on a list. */
500 {
501     size_t n= 0;
502 
503     while (cfg != nil) {
504 	n++;
505 	cfg= cfg->next;
506     }
507     return n;
508 }
509 
510 #if TEST
511 #include <unistd.h>
512 
513 static void print_list(int indent, config_t *cfg);
514 
515 static void print_words(int indent, config_t *cfg)
516 {
517     while (cfg != nil) {
518 	if (config_isatom(cfg)) {
519 	    if (config_isstring(cfg)) fputc('"', stdout);
520 	    printf("%s", cfg->word);
521 	    if (config_isstring(cfg)) fputc('"', stdout);
522 	} else {
523 	    printf("{\n");
524 	    print_list(indent+4, cfg->list);
525 	    printf("%*s}", indent, "");
526 	}
527 	cfg= cfg->next;
528 	if (cfg != nil) fputc(' ', stdout);
529     }
530     printf(";\n");
531 }
532 
533 static void print_list(int indent, config_t *cfg)
534 {
535     while (cfg != nil) {
536 	if (!config_issub(cfg)) {
537 	    fprintf(stderr, "Cell at \"%s\", line %u is not a sublist\n");
538 	    break;
539 	}
540 	printf("%*s", indent, "");
541 	print_words(indent, cfg->list);
542 	cfg= cfg->next;
543     }
544 }
545 
546 static void print_config(config_t *cfg)
547 {
548     if (!config_renewed(cfg)) {
549 	printf("# Config didn't change\n");
550     } else {
551 	print_list(0, cfg);
552     }
553 }
554 
555 int main(int argc, char **argv)
556 {
557     config_t *cfg;
558     int c;
559 
560     if (argc != 2) {
561 	fprintf(stderr, "One config file name please\n");
562 	exit(1);
563     }
564 
565     cfg= nil;
566     do {
567 	cfg= config_read(argv[1], CFG_ESCAPED, cfg);
568 	print_config(cfg);
569 	if (!isatty(0)) break;
570 	while ((c= getchar()) != EOF && c != '\n') {}
571     } while (c != EOF);
572     return 0;
573 }
574 #endif /* TEST */
575