1 /* config_read(), _delete(), _length() - Generic config file routines.
7 #include <minix/stubs.h>
12 #include <sys/types.h>
21 #include <minix/asciictype.h>
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. */
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. */
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. */
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
);
72 #define deallocate(mem) free(mem)
74 static void delete_filelist(configfile_t
*cfgf
)
75 /* Delete configuration file list. */
86 static void delete_config(config_t
*cfg
)
87 /* Delete configuration file data. */
89 config_t
*next
, *list
, *junk
;
95 /* Push the 'next' chain in reverse on the 'list' chain, putting
96 * a leaf cell (next == nil) on top of 'list'.
104 /* Delete the leaf cell. If it has a sublist then that becomes
112 /* Both chains are gone. */
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
);
129 static void nextc(void)
130 /* Read the next character of the current file into 'c'. */
132 if (c
== '\n') c_line
++;
134 if (c
== EOF
&& ferror(c_fp
)) {
135 fprintf(stderr
, "\"%s\", line %u: %s\n",
136 c_file
, c_line
, strerror(errno
));
141 static void skipwhite(void)
142 /* Skip whitespace and comments. */
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. */
159 fprintf(stderr
, "\"%s\", line %u: parse error at '%s'\n",
160 c_file
, c_line
, c
== EOF
? "EOF" : sc
);
164 static config_t
*read_word(void)
165 /* Read a word or string. */
170 static char SPECIAL
[] = "!#$%&*+-./:<=>?[\\]^_|~";
174 w
= allocate(nil
, configsize(32));
181 /* Is it a quoted string? */
182 if (c
== '\'' || c
== '"') {
192 w
= allocate(w
, configsize(len
));
196 /* A word consists of letters, numbers and a few special chars. */
197 if (!isalnum(c
) && c
< 0x80 && strchr(SPECIAL
, c
) == nil
) break;
199 /* Strings are made up of anything except newlines. */
200 if (c
== EOF
|| c
== '\n') {
202 "\"%s\", line %u: string at line %u not closed\n",
203 c_file
, c_line
, w
->line
);
207 if (c
== q
) { /* Closing quote? */
213 if (c
!= '\\') { /* Simply add non-escapes. */
216 } else { /* Interpret an escape. */
223 if (c_flags
& CFG_ESCAPED
) {
224 w
->word
[i
++]= '\\'; /* Keep the \ for the caller. */
227 w
= allocate(w
, configsize(len
));
229 w
->flags
|= CFG_ESCAPED
;
232 if (isdigit(c
)) { /* Octal escape */
237 d
= d
* 010 + (c
- '0');
239 } while (--n
> 0 && isdigit(c
));
242 if (c
== 'x' || c
== 'X') { /* Hex escape */
248 fprintf(stderr
, "\"%s\", line %u: bad hex escape\n",
253 d
= d
* 0x10 + (islower(c
) ? (c
- 'a' + 0xa) :
254 isupper(c
) ? (c
- 'A' + 0xA) :
257 } while (--n
> 0 && isxdigit(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. */;
279 w
->flags
|= CFG_STRING
;
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
;
312 if (c
== EOF
|| c
== '}') {
313 if(0) if (cline
!= nil
) parse_err();
318 if (cline
!= nil
) break;
320 if (cline
!= nil
&& c
== '{') {
323 clist
= allocate(nil
, config0size());
327 clist
->list
= read_list();
328 clist
->flags
= CFG_SUBLIST
;
330 pcline
= &clist
->next
;
331 if (c
!= '}') parse_err();
334 *pcline
= read_word();
335 pcline
= &(*pcline
)->next
;
341 static config_t
*read_list(void)
342 /* Read and return a list of config file commands. */
344 config_t
*clist
, **pclist
, *cline
;
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
)) {
354 "\"%s\", line %u: 'include' command requires an argument\n",
355 c_file
, cline
->line
);
358 if (file
->flags
& CFG_ESCAPED
) {
362 if ((*q
= *p
) == '\\') *q
= *++p
;
368 file
= read_file(file
->word
);
369 delete_config(cline
);
371 while (*pclist
!= nil
) pclist
= &(*pclist
)->next
;
373 config_t
*cfg
= allocate(nil
, config0size());
376 cfg
->file
= cline
->file
;
377 cfg
->line
= cline
->line
;
378 cfg
->flags
= CFG_SUBLIST
;
386 static config_t
*read_file(const char *file
)
387 /* Read and return a configuration file. */
392 FILE *old_fp
; /* old_* variables store current file context. */
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
);
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
));
426 cfgf
->ctime
= st
.st_ctime
;
431 if (c
!= EOF
) parse_err();
433 if (c_fp
!= nil
) fclose(c_fp
);
441 config_t
*config_read(const char *file
, int flags
, config_t
*cfg
)
442 /* Read and parse a configuration file. */
445 /* First check if any of the involved files has changed. */
451 for (cfgf
= fcfg
->filelist
; cfgf
!= nil
; cfgf
= cfgf
->next
) {
452 if (stat(cfgf
->name
, &st
) < 0) {
453 if (errno
!= ENOENT
) break;
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. */
466 cfg
= read_file(file
);
469 /* Change first word to have a hidden pointer to a file list. */
470 size_t len
= strlen(cfg
->word
);
473 fcfg
= allocate(cfg
, firstconfigsize(len
));
474 memmove(&fcfg
->config1
, fcfg
, configsize(len
));
475 fcfg
->filelist
= c_files
;
477 return fcfg2cfg(fcfg
);
479 /* Couldn't read (errno != 0) of nothing read (errno == 0). */
480 delete_filelist(c_files
);
485 int config_renewed(config_t
*cfg
)
492 new= cfg2fcfg(cfg
)->new;
493 cfg2fcfg(cfg
)->new= 0;
498 size_t config_length(config_t
*cfg
)
499 /* Count the number of items on a list. */
513 static void print_list(int indent
, config_t
*cfg
);
515 static void print_words(int indent
, config_t
*cfg
)
518 if (config_isatom(cfg
)) {
519 if (config_isstring(cfg
)) fputc('"', stdout
);
520 printf("%s", cfg
->word
);
521 if (config_isstring(cfg
)) fputc('"', stdout
);
524 print_list(indent
+4, cfg
->list
);
525 printf("%*s}", indent
, "");
528 if (cfg
!= nil
) fputc(' ', stdout
);
533 static void print_list(int indent
, config_t
*cfg
)
536 if (!config_issub(cfg
)) {
537 fprintf(stderr
, "Cell at \"%s\", line %u is not a sublist\n");
540 printf("%*s", indent
, "");
541 print_words(indent
, cfg
->list
);
546 static void print_config(config_t
*cfg
)
548 if (!config_renewed(cfg
)) {
549 printf("# Config didn't change\n");
555 int main(int argc
, char **argv
)
561 fprintf(stderr
, "One config file name please\n");
567 cfg
= config_read(argv
[1], CFG_ESCAPED
, cfg
);
569 if (!isatty(0)) break;
570 while ((c
= getchar()) != EOF
&& c
!= '\n') {}