configfile.c

Go to the documentation of this file.
00001 /*      config_read(), _delete(), _length() - Generic config file routines.
00002  *                                                      Author: Kees J. Bot
00003  *                                                              5 Jun 1999
00004  */
00005 #define nil ((void*)0)
00006 #if __minix_vmd
00007 #include <minix/stubs.h>
00008 #else
00009 #define fstat _fstat
00010 #define stat _stat
00011 #endif
00012 #include <sys/types.h>
00013 #include <stdio.h>
00014 #include <stddef.h>
00015 #include <stdlib.h>
00016 #include <errno.h>
00017 #include <string.h>
00018 #include <time.h>
00019 #include <sys/stat.h>
00020 #if __minix_vmd
00021 #include <minix/asciictype.h>
00022 #else
00023 #include <ctype.h>
00024 #endif
00025 #define _c /* not const */
00026 #include <configfile.h>
00027 
00028 typedef struct configfile {     /* List of (included) configuration files. */
00029         struct configfile *next;        /* A list indeed. */
00030         time_t          ctime;          /* Last changed time, -1 if no file. */
00031         char            name[1];        /* File name. */
00032 } configfile_t;
00033 
00034 /* Size of a configfile_t given a file name of length 'len'. */
00035 #define configfilesize(len)     (offsetof(configfile_t, name) + 1 + (len))
00036 
00037 typedef struct firstconfig {    /* First file and first word share a slot. */
00038         configfile_t    *filelist;
00039         char            new;            /* Set when created. */
00040         config_t        config1;
00041 } firstconfig_t;
00042 
00043 /* Size of a config_t given a word of lenght 'len'.  Same for firstconfig_t. */
00044 #define config0size()           (offsetof(config_t, word))
00045 #define configsize(len)         (config0size() + 1 + (len))
00046 #define firstconfigsize(len)    \
00047                         (offsetof(firstconfig_t, config1) + configsize(len))
00048 
00049 /* Translate address of first config word to enclosing firstconfig_t and vv. */
00050 #define cfg2fcfg(p)     \
00051     ((firstconfig_t *) ((char *) (p) - offsetof(firstconfig_t, config1)))
00052 #define fcfg2cfg(p)     (&(p)->config1)
00053 
00054 /* Variables used while building data. */
00055 static configfile_t *c_files;           /* List of (included) config files. */
00056 static int c_flags;                     /* Flags argument of config_read(). */
00057 static FILE *c_fp;                      /* Current open file. */
00058 static char *c_file;                    /* Current open file name. */
00059 static unsigned c_line;                 /* Current line number. */
00060 static int c;                           /* Next character. */
00061 
00062 static void *allocate(void *mem, size_t size)
00063 /* Like realloc(), but checked. */
00064 {
00065     if ((mem= realloc(mem, size)) == nil) {
00066         fprintf(stderr, "\"%s\", line %u: Out of memory\n", c_file, c_line);
00067         exit(1);
00068     }
00069     return mem;
00070 }
00071 
00072 #define deallocate(mem) free(mem)
00073 
00074 static void delete_filelist(configfile_t *cfgf)
00075 /* Delete configuration file list. */
00076 {
00077     void *junk;
00078 
00079     while (cfgf != nil) {
00080         junk= cfgf;
00081         cfgf= cfgf->next;
00082         deallocate(junk);
00083     }
00084 }
00085 
00086 static void delete_config(config_t *cfg)
00087 /* Delete configuration file data. */
00088 {
00089     config_t *next, *list, *junk;
00090 
00091     next= cfg;
00092     list= nil;
00093     for (;;) {
00094         if (next != nil) {
00095             /* Push the 'next' chain in reverse on the 'list' chain, putting
00096              * a leaf cell (next == nil) on top of 'list'.
00097              */
00098             junk= next;
00099             next= next->next;
00100             junk->next= list;
00101             list= junk;
00102         } else
00103         if (list != nil) {
00104             /* Delete the leaf cell.  If it has a sublist then that becomes
00105              * the 'next' chain.
00106              */
00107             junk= list;
00108             next= list->list;
00109             list= list->next;
00110             deallocate(junk);
00111         } else {
00112             /* Both chains are gone. */
00113             break;
00114         }
00115     }
00116 }
00117 
00118 void config_delete(config_t *cfg1)
00119 /* Delete configuration file data, being careful with the odd first one. */
00120 {
00121     firstconfig_t *fcfg= cfg2fcfg(cfg1);
00122 
00123     delete_filelist(fcfg->filelist);
00124     delete_config(fcfg->config1.next);
00125     delete_config(fcfg->config1.list);
00126     deallocate(fcfg);
00127 }
00128 
00129 static void nextc(void)
00130 /* Read the next character of the current file into 'c'. */
00131 {
00132     if (c == '\n') c_line++;
00133     c= getc(c_fp);
00134     if (c == EOF && ferror(c_fp)) {
00135         fprintf(stderr, "\"%s\", line %u: %s\n",
00136             c_file, c_line, strerror(errno));
00137         exit(1);
00138     }
00139 }
00140 
00141 static void skipwhite(void)
00142 /* Skip whitespace and comments. */
00143 {
00144     while (isspace(c)) {
00145         nextc();
00146         if (c == '#') {
00147             do nextc(); while (c != EOF && c != '\n');
00148         }
00149     }
00150 }
00151 
00152 static void parse_err(void)
00153 /* Tell user that you can't parse past the current character. */
00154 {
00155     char sc[2];
00156 
00157     sc[0]= c;
00158     sc[1]= 0;
00159     fprintf(stderr, "\"%s\", line %u: parse error at '%s'\n",
00160         c_file, c_line, c == EOF ? "EOF" : sc);
00161     exit(1);
00162 }
00163 
00164 static config_t *read_word(void)
00165 /* Read a word or string. */
00166 {
00167     config_t *w;
00168     size_t i, len;
00169     int q;
00170     static char SPECIAL[] = "!#$%&*+-./:<=>?[\\]^_|~";
00171 
00172     i= 0;
00173     len= 32;
00174     w= allocate(nil, configsize(32));
00175     w->next= nil;
00176     w->list= nil;
00177     w->file= c_file;
00178     w->line= c_line;
00179     w->flags= 0;
00180 
00181     /* Is it a quoted string? */
00182     if (c == '\'' || c == '"') {
00183         q= c;   /* yes */
00184         nextc();
00185     } else {
00186         q= -1;  /* no */
00187     }
00188 
00189     for (;;) {
00190         if (i == len) {
00191             len+= 32;
00192             w= allocate(w, configsize(len));
00193         }
00194 
00195         if (q == -1) {
00196             /* A word consists of letters, numbers and a few special chars. */
00197             if (!isalnum(c) && c < 0x80 && strchr(SPECIAL, c) == nil) break;
00198         } else {
00199             /* Strings are made up of anything except newlines. */
00200             if (c == EOF || c == '\n') {
00201                 fprintf(stderr,
00202                     "\"%s\", line %u: string at line %u not closed\n",
00203                     c_file, c_line, w->line);
00204                 exit(1);
00205                 break;
00206             }
00207             if (c == q) {       /* Closing quote? */
00208                 nextc();
00209                 break;
00210             }
00211         }
00212 
00213         if (c != '\\') {        /* Simply add non-escapes. */
00214             w->word[i++]= c;
00215             nextc();
00216         } else {                /* Interpret an escape. */
00217             nextc();
00218             if (isspace(c)) {
00219                 skipwhite();
00220                 continue;
00221             }
00222 
00223             if (c_flags & CFG_ESCAPED) {
00224                 w->word[i++]= '\\';     /* Keep the \ for the caller. */
00225                 if (i == len) {
00226                     len+= 32;
00227                     w= allocate(w, configsize(len));
00228                 }
00229                 w->flags |= CFG_ESCAPED;
00230             }
00231 
00232             if (isdigit(c)) {           /* Octal escape */
00233                 int n= 3;
00234                 int d= 0;
00235 
00236                 do {
00237                     d= d * 010 + (c - '0');
00238                     nextc();
00239                 } while (--n > 0 && isdigit(c));
00240                 w->word[i++]= d;
00241             } else
00242             if (c == 'x' || c == 'X') { /* Hex escape */
00243                 int n= 2;
00244                 int d= 0;
00245 
00246                 nextc();
00247                 if (!isxdigit(c)) {
00248                     fprintf(stderr, "\"%s\", line %u: bad hex escape\n",
00249                         c_file, c_line);
00250                     exit(1);
00251                 }
00252                 do {
00253                     d= d * 0x10 + (islower(c) ? (c - 'a' + 0xa) :
00254                                     isupper(c) ? (c - 'A' + 0xA) :
00255                                     (c - '0'));
00256                     nextc();
00257                 } while (--n > 0 && isxdigit(c));
00258                 w->word[i++]= d;
00259             } else {
00260                 switch (c) {
00261                 case 'a':       c= '\a';        break;
00262                 case 'b':       c= '\b';        break;
00263                 case 'e':       c= '\033';      break;
00264                 case 'f':       c= '\f';        break;
00265                 case 'n':       c= '\n';        break;
00266                 case 'r':       c= '\r';        break;
00267                 case 's':       c= ' ';         break;
00268                 case 't':       c= '\t';        break;
00269                 case 'v':       c= '\v';        break;
00270                 default:        /* Anything else is kept as-is. */;
00271                 }
00272                 w->word[i++]= c;
00273                 nextc();
00274             }
00275         }
00276     }
00277     w->word[i]= 0;
00278     if (q != -1) {
00279         w->flags |= CFG_STRING;
00280     } else {
00281         int f;
00282         char *end;
00283         static char base[]= { 0, 010, 10, 0x10 };
00284 
00285         if (i == 0) parse_err();
00286 
00287         /* Can the word be used as a number? */
00288         for (f= 0; f < 4; f++) {
00289             (void) strtol(w->word, &end, base[f]);
00290             if (*end == 0) w->flags |= 1 << (f + 0);
00291             (void) strtoul(w->word, &end, base[f]);
00292             if (*end == 0) w->flags |= 1 << (f + 4);
00293         }
00294     }
00295     return allocate(w, configsize(i));
00296 }
00297 
00298 static config_t *read_file(const char *file);
00299 static config_t *read_list(void);
00300 
00301 static config_t *read_line(void)
00302 /* Read and return one line of the config file. */
00303 {
00304     config_t *cline, **pcline, *clist;
00305 
00306     cline= nil;
00307     pcline= &cline;
00308 
00309     for (;;) {
00310         skipwhite();
00311 
00312         if (c == EOF || c == '}') {
00313 if(0)       if (cline != nil) parse_err();
00314             break;
00315         } else
00316         if (c == ';') {
00317             nextc();
00318             if (cline != nil) break;
00319         } else
00320         if (cline != nil && c == '{') {
00321             /* A sublist. */
00322             nextc();
00323             clist= allocate(nil, config0size());
00324             clist->next= nil;
00325             clist->file= c_file;
00326             clist->line= c_line;
00327             clist->list= read_list();
00328             clist->flags= CFG_SUBLIST;
00329             *pcline= clist;
00330             pcline= &clist->next;
00331             if (c != '}') parse_err();
00332             nextc();
00333         } else {
00334             *pcline= read_word();
00335             pcline= &(*pcline)->next;
00336         }
00337     }
00338     return cline;
00339 }
00340 
00341 static config_t *read_list(void)
00342 /* Read and return a list of config file commands. */
00343 {
00344     config_t *clist, **pclist, *cline;
00345 
00346     clist= nil;
00347     pclist= &clist;
00348 
00349     while ((cline= read_line()) != nil) {
00350         if (strcmp(cline->word, "include") == 0) {
00351             config_t *file= cline->next;
00352             if (file == nil || file->next != nil || !config_isatom(file)) {
00353                 fprintf(stderr,
00354                     "\"%s\", line %u: 'include' command requires an argument\n",
00355                     c_file, cline->line);
00356                 exit(1);
00357             }
00358             if (file->flags & CFG_ESCAPED) {
00359                 char *p, *q;
00360                 p= q= file->word;
00361                 for (;;) {
00362                     if ((*q = *p) == '\\') *q = *++p;
00363                     if (*q == 0) break;
00364                     p++;
00365                     q++;
00366                 }
00367             }
00368             file= read_file(file->word);
00369             delete_config(cline);
00370             *pclist= file;
00371             while (*pclist != nil) pclist= &(*pclist)->next;
00372         } else {
00373             config_t *cfg= allocate(nil, config0size());
00374             cfg->next= nil;
00375             cfg->list= cline;
00376             cfg->file= cline->file;
00377             cfg->line= cline->line;
00378             cfg->flags= CFG_SUBLIST;
00379             *pclist= cfg;
00380             pclist= &cfg->next;
00381         }
00382     }
00383     return clist;
00384 }
00385 
00386 static config_t *read_file(const char *file)
00387 /* Read and return a configuration file. */
00388 {
00389     configfile_t *cfgf;
00390     config_t *cfg;
00391     struct stat st;
00392     FILE *old_fp;       /* old_* variables store current file context. */
00393     char *old_file;
00394     unsigned old_line;
00395     int old_c;
00396     size_t n;
00397     char *slash;
00398 
00399     old_fp= c_fp;
00400     old_file= c_file;
00401     old_line= c_line;
00402     old_c= c;
00403 
00404     n= 0;
00405     if (file[0] != '/' && old_file != nil
00406                         && (slash= strrchr(old_file, '/')) != nil) {
00407         n= slash - old_file + 1;
00408     }
00409     cfgf= allocate(nil, configfilesize(n + strlen(file)));
00410     memcpy(cfgf->name, old_file, n);
00411     strcpy(cfgf->name + n, file);
00412     cfgf->next= c_files;
00413     c_files= cfgf;
00414 
00415     c_file= cfgf->name;
00416     c_line= 0;
00417 
00418     if ((c_fp= fopen(file, "r")) == nil || fstat(fileno(c_fp), &st) < 0) {
00419         if (errno != ENOENT) {
00420             fprintf(stderr, "\"%s\", line 1: %s\n", file, strerror(errno));
00421             exit(1);
00422         }
00423         cfgf->ctime= -1;
00424         c= EOF;
00425     } else {
00426         cfgf->ctime= st.st_ctime;
00427         c= '\n';
00428     }
00429 
00430     cfg= read_list();
00431     if (c != EOF) parse_err();
00432 
00433     if (c_fp != nil) fclose(c_fp);
00434     c_fp= old_fp;
00435     c_file= old_file;
00436     c_line= old_line;
00437     c= old_c;
00438     return cfg;
00439 }
00440 
00441 config_t *config_read(const char *file, int flags, config_t *cfg)
00442 /* Read and parse a configuration file. */
00443 {
00444     if (cfg != nil) {
00445         /* First check if any of the involved files has changed. */
00446         firstconfig_t *fcfg;
00447         configfile_t *cfgf;
00448         struct stat st;
00449 
00450         fcfg= cfg2fcfg(cfg);
00451         for (cfgf= fcfg->filelist; cfgf != nil; cfgf= cfgf->next) {
00452             if (stat(cfgf->name, &st) < 0) {
00453                 if (errno != ENOENT) break;
00454                 st.st_ctime= -1;
00455             }
00456             if (st.st_ctime != cfgf->ctime) break;
00457         }
00458 
00459         if (cfgf == nil) return cfg;    /* Everything as it was. */
00460         config_delete(cfg);             /* Otherwise delete and reread. */
00461     }
00462 
00463     errno= 0;
00464     c_files= nil;
00465     c_flags= flags;
00466     cfg= read_file(file);
00467 
00468     if (cfg != nil) {
00469         /* Change first word to have a hidden pointer to a file list. */
00470         size_t len= strlen(cfg->word);
00471         firstconfig_t *fcfg;
00472 
00473         fcfg= allocate(cfg, firstconfigsize(len));
00474         memmove(&fcfg->config1, fcfg, configsize(len));
00475         fcfg->filelist= c_files;
00476         fcfg->new= 1;
00477         return fcfg2cfg(fcfg);
00478     }
00479     /* Couldn't read (errno != 0) of nothing read (errno == 0). */
00480     delete_filelist(c_files);
00481     delete_config(cfg);
00482     return nil;
00483 }
00484 
00485 int config_renewed(config_t *cfg)
00486 {
00487     int new;
00488 
00489     if (cfg == nil) {
00490         new= 1;
00491     } else {
00492         new= cfg2fcfg(cfg)->new;
00493         cfg2fcfg(cfg)->new= 0;
00494     }
00495     return new;
00496 }
00497 
00498 size_t config_length(config_t *cfg)
00499 /* Count the number of items on a list. */
00500 {
00501     size_t n= 0;
00502 
00503     while (cfg != nil) {
00504         n++;
00505         cfg= cfg->next;
00506     }
00507     return n;
00508 }
00509 
00510 #if TEST
00511 #include <unistd.h>
00512 
00513 static void print_list(int indent, config_t *cfg);
00514 
00515 static void print_words(int indent, config_t *cfg)
00516 {
00517     while (cfg != nil) {
00518         if (config_isatom(cfg)) {
00519             if (config_isstring(cfg)) fputc('"', stdout);
00520             printf("%s", cfg->word);
00521             if (config_isstring(cfg)) fputc('"', stdout);
00522         } else {
00523             printf("{\n");
00524             print_list(indent+4, cfg->list);
00525             printf("%*s}", indent, "");
00526         }
00527         cfg= cfg->next;
00528         if (cfg != nil) fputc(' ', stdout);
00529     }
00530     printf(";\n");
00531 }
00532 
00533 static void print_list(int indent, config_t *cfg)
00534 {
00535     while (cfg != nil) {
00536         if (!config_issub(cfg)) {
00537             fprintf(stderr, "Cell at \"%s\", line %u is not a sublist\n");
00538             break;
00539         }
00540         printf("%*s", indent, "");
00541         print_words(indent, cfg->list);
00542         cfg= cfg->next;
00543     }
00544 }
00545 
00546 static void print_config(config_t *cfg)
00547 {
00548     if (!config_renewed(cfg)) {
00549         printf("# Config didn't change\n");
00550     } else {
00551         print_list(0, cfg);
00552     }
00553 }
00554 
00555 int main(int argc, char **argv)
00556 {
00557     config_t *cfg;
00558     int c;
00559 
00560     if (argc != 2) {
00561         fprintf(stderr, "One config file name please\n");
00562         exit(1);
00563     }
00564 
00565     cfg= nil;
00566     do {
00567         cfg= config_read(argv[1], CFG_ESCAPED, cfg);
00568         print_config(cfg);
00569         if (!isatty(0)) break;
00570         while ((c= getchar()) != EOF && c != '\n') {}
00571     } while (c != EOF);
00572     return 0;
00573 }
00574 #endif /* TEST */

Generated on Fri Apr 14 22:57:26 2006 for minix by  doxygen 1.4.6