source: trunk/minix/lib/other/configfile.c@ 20

Last change on this file since 20 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 12.5 KB
Line 
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
28typedef 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
37typedef 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. */
55static configfile_t *c_files; /* List of (included) config files. */
56static int c_flags; /* Flags argument of config_read(). */
57static FILE *c_fp; /* Current open file. */
58static char *c_file; /* Current open file name. */
59static unsigned c_line; /* Current line number. */
60static int c; /* Next character. */
61
62static 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
74static 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
86static 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
118void 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
129static 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
141static 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
152static void 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
164static 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
298static config_t *read_file(const char *file);
299static config_t *read_list(void);
300
301static 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 == '}') {
313if(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
341static 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
386static 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
441config_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
485int 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
498size_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
513static void print_list(int indent, config_t *cfg);
514
515static 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
533static 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
546static 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
555int 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 */
Note: See TracBrowser for help on using the repository browser.