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