[9] | 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 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 */
|
---|