patch(1) problems workaround
[minix3.git] / lib / libcompat_minix / configfile.c
blob4c6300fea6eccec64c4caf783e0817b5062c3eae
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>
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;
34 /* Size of a configfile_t given a file name of length 'len'. */
35 #define configfilesize(len) (offsetof(configfile_t, name) + 1 + (len))
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;
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))
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)
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. */
62 static void *allocate(void *mem, size_t size)
63 /* Like realloc(), but checked. */
65 if ((mem= realloc(mem, size)) == nil) {
66 fprintf(stderr, "\"%s\", line %u: Out of memory\n", c_file, c_line);
67 exit(1);
69 return mem;
72 #define deallocate(mem) free(mem)
74 static void delete_filelist(configfile_t *cfgf)
75 /* Delete configuration file list. */
77 void *junk;
79 while (cfgf != nil) {
80 junk= cfgf;
81 cfgf= cfgf->next;
82 deallocate(junk);
86 static void delete_config(config_t *cfg)
87 /* Delete configuration file data. */
89 config_t *next, *list, *junk;
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'.
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.
107 junk= list;
108 next= list->list;
109 list= list->next;
110 deallocate(junk);
111 } else {
112 /* Both chains are gone. */
113 break;
118 void config_delete(config_t *cfg1)
119 /* Delete configuration file data, being careful with the odd first one. */
121 firstconfig_t *fcfg= cfg2fcfg(cfg1);
123 delete_filelist(fcfg->filelist);
124 delete_config(fcfg->config1.next);
125 delete_config(fcfg->config1.list);
126 deallocate(fcfg);
129 static void nextc(void)
130 /* Read the next character of the current file into 'c'. */
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);
141 static void skipwhite(void)
142 /* Skip whitespace and comments. */
144 while (isspace(c)) {
145 nextc();
146 if (c == '#') {
147 do nextc(); while (c != EOF && c != '\n');
152 static void parse_err(void)
153 /* Tell user that you can't parse past the current character. */
155 char sc[2];
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);
164 static config_t *read_word(void)
165 /* Read a word or string. */
167 config_t *w;
168 size_t i, len;
169 int q;
170 static char SPECIAL[] = "!#$%&*+-./:<=>?[\\]^_|~";
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;
181 /* Is it a quoted string? */
182 if (c == '\'' || c == '"') {
183 q= c; /* yes */
184 nextc();
185 } else {
186 q= -1; /* no */
189 for (;;) {
190 if (i == len) {
191 len+= 32;
192 w= allocate(w, configsize(len));
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;
207 if (c == q) { /* Closing quote? */
208 nextc();
209 break;
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;
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));
229 w->flags |= CFG_ESCAPED;
232 if (isdigit(c)) { /* Octal escape */
233 int n= 3;
234 int d= 0;
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;
246 nextc();
247 if (!isxdigit(c)) {
248 fprintf(stderr, "\"%s\", line %u: bad hex escape\n",
249 c_file, c_line);
250 exit(1);
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. */;
272 w->word[i++]= c;
273 nextc();
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 };
285 if (i == 0) parse_err();
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);
295 return allocate(w, configsize(i));
298 static config_t *read_file(const char *file);
299 static config_t *read_list(void);
301 static config_t *read_line(void)
302 /* Read and return one line of the config file. */
304 config_t *cline, **pcline, *clist;
306 cline= nil;
307 pcline= &cline;
309 for (;;) {
310 skipwhite();
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;
338 return cline;
341 static config_t *read_list(void)
342 /* Read and return a list of config file commands. */
344 config_t *clist, **pclist, *cline;
346 clist= nil;
347 pclist= &clist;
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);
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++;
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;
383 return clist;
386 static config_t *read_file(const char *file)
387 /* Read and return a configuration file. */
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;
399 old_fp= c_fp;
400 old_file= c_file;
401 old_line= c_line;
402 old_c= c;
404 n= 0;
405 if (file[0] != '/' && old_file != nil
406 && (slash= strrchr(old_file, '/')) != nil) {
407 n= slash - old_file + 1;
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;
415 c_file= cfgf->name;
416 c_line= 0;
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);
423 cfgf->ctime= -1;
424 c= EOF;
425 } else {
426 cfgf->ctime= st.st_ctime;
427 c= '\n';
430 cfg= read_list();
431 if (c != EOF) parse_err();
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;
441 config_t *config_read(const char *file, int flags, config_t *cfg)
442 /* Read and parse a configuration file. */
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;
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;
456 if (st.st_ctime != cfgf->ctime) break;
459 if (cfgf == nil) return cfg; /* Everything as it was. */
460 config_delete(cfg); /* Otherwise delete and reread. */
463 errno= 0;
464 c_files= nil;
465 c_flags= flags;
466 cfg= read_file(file);
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;
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);
479 /* Couldn't read (errno != 0) of nothing read (errno == 0). */
480 delete_filelist(c_files);
481 delete_config(cfg);
482 return nil;
485 int config_renewed(config_t *cfg)
487 int new;
489 if (cfg == nil) {
490 new= 1;
491 } else {
492 new= cfg2fcfg(cfg)->new;
493 cfg2fcfg(cfg)->new= 0;
495 return new;
498 size_t config_length(config_t *cfg)
499 /* Count the number of items on a list. */
501 size_t n= 0;
503 while (cfg != nil) {
504 n++;
505 cfg= cfg->next;
507 return n;
510 #if TEST
511 #include <unistd.h>
513 static void print_list(int indent, config_t *cfg);
515 static void print_words(int indent, config_t *cfg)
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, "");
527 cfg= cfg->next;
528 if (cfg != nil) fputc(' ', stdout);
530 printf(";\n");
533 static void print_list(int indent, config_t *cfg)
535 while (cfg != nil) {
536 if (!config_issub(cfg)) {
537 fprintf(stderr, "Cell at \"%s\", line %u is not a sublist\n");
538 break;
540 printf("%*s", indent, "");
541 print_words(indent, cfg->list);
542 cfg= cfg->next;
546 static void print_config(config_t *cfg)
548 if (!config_renewed(cfg)) {
549 printf("# Config didn't change\n");
550 } else {
551 print_list(0, cfg);
555 int main(int argc, char **argv)
557 config_t *cfg;
558 int c;
560 if (argc != 2) {
561 fprintf(stderr, "One config file name please\n");
562 exit(1);
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;
574 #endif /* TEST */