minix/lib/libc/other/configfile.c

574 lines
12 KiB
C

/* config_read(), _delete(), _length() - Generic config file routines.
* Author: Kees J. Bot
* 5 Jun 1999
*/
#define nil ((void*)0)
#if __minix_vmd
#include <minix/stubs.h>
#else
#define fstat _fstat
#define stat _stat
#endif
#include <sys/types.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#if __minix_vmd
#include <minix/asciictype.h>
#else
#include <ctype.h>
#endif
#define _c /* not const */
#include <configfile.h>
typedef struct configfile { /* List of (included) configuration files. */
struct configfile *next; /* A list indeed. */
time_t ctime; /* Last changed time, -1 if no file. */
char name[1]; /* File name. */
} configfile_t;
/* Size of a configfile_t given a file name of length 'len'. */
#define configfilesize(len) (offsetof(configfile_t, name) + 1 + (len))
typedef struct firstconfig { /* First file and first word share a slot. */
configfile_t *filelist;
char new; /* Set when created. */
config_t config1;
} firstconfig_t;
/* Size of a config_t given a word of lenght 'len'. Same for firstconfig_t. */
#define config0size() (offsetof(config_t, word))
#define configsize(len) (config0size() + 1 + (len))
#define firstconfigsize(len) \
(offsetof(firstconfig_t, config1) + configsize(len))
/* Translate address of first config word to enclosing firstconfig_t and vv. */
#define cfg2fcfg(p) \
((firstconfig_t *) ((char *) (p) - offsetof(firstconfig_t, config1)))
#define fcfg2cfg(p) (&(p)->config1)
/* Variables used while building data. */
static configfile_t *c_files; /* List of (included) config files. */
static int c_flags; /* Flags argument of config_read(). */
static FILE *c_fp; /* Current open file. */
static char *c_file; /* Current open file name. */
static unsigned c_line; /* Current line number. */
static int c; /* Next character. */
static void *allocate(void *mem, size_t size)
/* Like realloc(), but checked. */
{
if ((mem= realloc(mem, size)) == nil) {
fprintf(stderr, "\"%s\", line %u: Out of memory\n", c_file, c_line);
exit(1);
}
return mem;
}
#define deallocate(mem) free(mem)
static void delete_filelist(configfile_t *cfgf)
/* Delete configuration file list. */
{
void *junk;
while (cfgf != nil) {
junk= cfgf;
cfgf= cfgf->next;
deallocate(junk);
}
}
static void delete_config(config_t *cfg)
/* Delete configuration file data. */
{
config_t *next, *list, *junk;
next= cfg;
list= nil;
for (;;) {
if (next != nil) {
/* Push the 'next' chain in reverse on the 'list' chain, putting
* a leaf cell (next == nil) on top of 'list'.
*/
junk= next;
next= next->next;
junk->next= list;
list= junk;
} else
if (list != nil) {
/* Delete the leaf cell. If it has a sublist then that becomes
* the 'next' chain.
*/
junk= list;
next= list->list;
list= list->next;
deallocate(junk);
} else {
/* Both chains are gone. */
break;
}
}
}
void config_delete(config_t *cfg1)
/* Delete configuration file data, being careful with the odd first one. */
{
firstconfig_t *fcfg= cfg2fcfg(cfg1);
delete_filelist(fcfg->filelist);
delete_config(fcfg->config1.next);
delete_config(fcfg->config1.list);
deallocate(fcfg);
}
static void nextc(void)
/* Read the next character of the current file into 'c'. */
{
if (c == '\n') c_line++;
c= getc(c_fp);
if (c == EOF && ferror(c_fp)) {
fprintf(stderr, "\"%s\", line %u: %s\n",
c_file, c_line, strerror(errno));
exit(1);
}
}
static void skipwhite(void)
/* Skip whitespace and comments. */
{
while (isspace(c)) {
nextc();
if (c == '#') {
do nextc(); while (c != EOF && c != '\n');
}
}
}
static void parse_err(void)
/* Tell user that you can't parse past the current character. */
{
char sc[2];
sc[0]= c;
sc[1]= 0;
fprintf(stderr, "\"%s\", line %u: parse error at '%s'\n",
c_file, c_line, c == EOF ? "EOF" : sc);
exit(1);
}
static config_t *read_word(void)
/* Read a word or string. */
{
config_t *w;
size_t i, len;
int q;
static char SPECIAL[] = "!#$%&*+-./:<=>?[\\]^_|~";
i= 0;
len= 32;
w= allocate(nil, configsize(32));
w->next= nil;
w->list= nil;
w->file= c_file;
w->line= c_line;
w->flags= 0;
/* Is it a quoted string? */
if (c == '\'' || c == '"') {
q= c; /* yes */
nextc();
} else {
q= -1; /* no */
}
for (;;) {
if (i == len) {
len+= 32;
w= allocate(w, configsize(len));
}
if (q == -1) {
/* A word consists of letters, numbers and a few special chars. */
if (!isalnum(c) && c < 0x80 && strchr(SPECIAL, c) == nil) break;
} else {
/* Strings are made up of anything except newlines. */
if (c == EOF || c == '\n') {
fprintf(stderr,
"\"%s\", line %u: string at line %u not closed\n",
c_file, c_line, w->line);
exit(1);
break;
}
if (c == q) { /* Closing quote? */
nextc();
break;
}
}
if (c != '\\') { /* Simply add non-escapes. */
w->word[i++]= c;
nextc();
} else { /* Interpret an escape. */
nextc();
if (isspace(c)) {
skipwhite();
continue;
}
if (c_flags & CFG_ESCAPED) {
w->word[i++]= '\\'; /* Keep the \ for the caller. */
if (i == len) {
len+= 32;
w= allocate(w, configsize(len));
}
w->flags |= CFG_ESCAPED;
}
if (isdigit(c)) { /* Octal escape */
int n= 3;
int d= 0;
do {
d= d * 010 + (c - '0');
nextc();
} while (--n > 0 && isdigit(c));
w->word[i++]= d;
} else
if (c == 'x' || c == 'X') { /* Hex escape */
int n= 2;
int d= 0;
nextc();
if (!isxdigit(c)) {
fprintf(stderr, "\"%s\", line %u: bad hex escape\n",
c_file, c_line);
exit(1);
}
do {
d= d * 0x10 + (islower(c) ? (c - 'a' + 0xa) :
isupper(c) ? (c - 'A' + 0xA) :
(c - '0'));
nextc();
} while (--n > 0 && isxdigit(c));
w->word[i++]= d;
} else {
switch (c) {
case 'a': c= '\a'; break;
case 'b': c= '\b'; break;
case 'e': c= '\033'; break;
case 'f': c= '\f'; break;
case 'n': c= '\n'; break;
case 'r': c= '\r'; break;
case 's': c= ' '; break;
case 't': c= '\t'; break;
case 'v': c= '\v'; break;
default: /* Anything else is kept as-is. */;
}
w->word[i++]= c;
nextc();
}
}
}
w->word[i]= 0;
if (q != -1) {
w->flags |= CFG_STRING;
} else {
int f;
char *end;
static char base[]= { 0, 010, 10, 0x10 };
if (i == 0) parse_err();
/* Can the word be used as a number? */
for (f= 0; f < 4; f++) {
(void) strtol(w->word, &end, base[f]);
if (*end == 0) w->flags |= 1 << (f + 0);
(void) strtoul(w->word, &end, base[f]);
if (*end == 0) w->flags |= 1 << (f + 4);
}
}
return allocate(w, configsize(i));
}
static config_t *read_file(const char *file);
static config_t *read_list(void);
static config_t *read_line(void)
/* Read and return one line of the config file. */
{
config_t *cline, **pcline, *clist;
cline= nil;
pcline= &cline;
for (;;) {
skipwhite();
if (c == EOF || c == '}') {
if(0) if (cline != nil) parse_err();
break;
} else
if (c == ';') {
nextc();
if (cline != nil) break;
} else
if (cline != nil && c == '{') {
/* A sublist. */
nextc();
clist= allocate(nil, config0size());
clist->next= nil;
clist->file= c_file;
clist->line= c_line;
clist->list= read_list();
clist->flags= CFG_SUBLIST;
*pcline= clist;
pcline= &clist->next;
if (c != '}') parse_err();
nextc();
} else {
*pcline= read_word();
pcline= &(*pcline)->next;
}
}
return cline;
}
static config_t *read_list(void)
/* Read and return a list of config file commands. */
{
config_t *clist, **pclist, *cline;
clist= nil;
pclist= &clist;
while ((cline= read_line()) != nil) {
if (strcmp(cline->word, "include") == 0) {
config_t *file= cline->next;
if (file == nil || file->next != nil || !config_isatom(file)) {
fprintf(stderr,
"\"%s\", line %u: 'include' command requires an argument\n",
c_file, cline->line);
exit(1);
}
if (file->flags & CFG_ESCAPED) {
char *p, *q;
p= q= file->word;
for (;;) {
if ((*q = *p) == '\\') *q = *++p;
if (*q == 0) break;
p++;
q++;
}
}
file= read_file(file->word);
delete_config(cline);
*pclist= file;
while (*pclist != nil) pclist= &(*pclist)->next;
} else {
config_t *cfg= allocate(nil, config0size());
cfg->next= nil;
cfg->list= cline;
cfg->file= cline->file;
cfg->line= cline->line;
cfg->flags= CFG_SUBLIST;
*pclist= cfg;
pclist= &cfg->next;
}
}
return clist;
}
static config_t *read_file(const char *file)
/* Read and return a configuration file. */
{
configfile_t *cfgf;
config_t *cfg;
struct stat st;
FILE *old_fp; /* old_* variables store current file context. */
char *old_file;
unsigned old_line;
int old_c;
size_t n;
char *slash;
old_fp= c_fp;
old_file= c_file;
old_line= c_line;
old_c= c;
n= 0;
if (file[0] != '/' && old_file != nil
&& (slash= strrchr(old_file, '/')) != nil) {
n= slash - old_file + 1;
}
cfgf= allocate(nil, configfilesize(n + strlen(file)));
memcpy(cfgf->name, old_file, n);
strcpy(cfgf->name + n, file);
cfgf->next= c_files;
c_files= cfgf;
c_file= cfgf->name;
c_line= 0;
if ((c_fp= fopen(file, "r")) == nil || fstat(fileno(c_fp), &st) < 0) {
if (errno != ENOENT) {
fprintf(stderr, "\"%s\", line 1: %s\n", file, strerror(errno));
exit(1);
}
cfgf->ctime= -1;
c= EOF;
} else {
cfgf->ctime= st.st_ctime;
c= '\n';
}
cfg= read_list();
if (c != EOF) parse_err();
if (c_fp != nil) fclose(c_fp);
c_fp= old_fp;
c_file= old_file;
c_line= old_line;
c= old_c;
return cfg;
}
config_t *config_read(const char *file, int flags, config_t *cfg)
/* Read and parse a configuration file. */
{
if (cfg != nil) {
/* First check if any of the involved files has changed. */
firstconfig_t *fcfg;
configfile_t *cfgf;
struct stat st;
fcfg= cfg2fcfg(cfg);
for (cfgf= fcfg->filelist; cfgf != nil; cfgf= cfgf->next) {
if (stat(cfgf->name, &st) < 0) {
if (errno != ENOENT) break;
st.st_ctime= -1;
}
if (st.st_ctime != cfgf->ctime) break;
}
if (cfgf == nil) return cfg; /* Everything as it was. */
config_delete(cfg); /* Otherwise delete and reread. */
}
errno= 0;
c_files= nil;
c_flags= flags;
cfg= read_file(file);
if (cfg != nil) {
/* Change first word to have a hidden pointer to a file list. */
size_t len= strlen(cfg->word);
firstconfig_t *fcfg;
fcfg= allocate(cfg, firstconfigsize(len));
memmove(&fcfg->config1, fcfg, configsize(len));
fcfg->filelist= c_files;
fcfg->new= 1;
return fcfg2cfg(fcfg);
}
/* Couldn't read (errno != 0) of nothing read (errno == 0). */
delete_filelist(c_files);
delete_config(cfg);
return nil;
}
int config_renewed(config_t *cfg)
{
int new;
if (cfg == nil) {
new= 1;
} else {
new= cfg2fcfg(cfg)->new;
cfg2fcfg(cfg)->new= 0;
}
return new;
}
size_t config_length(config_t *cfg)
/* Count the number of items on a list. */
{
size_t n= 0;
while (cfg != nil) {
n++;
cfg= cfg->next;
}
return n;
}
#if TEST
#include <unistd.h>
static void print_list(int indent, config_t *cfg);
static void print_words(int indent, config_t *cfg)
{
while (cfg != nil) {
if (config_isatom(cfg)) {
if (config_isstring(cfg)) fputc('"', stdout);
printf("%s", cfg->word);
if (config_isstring(cfg)) fputc('"', stdout);
} else {
printf("{\n");
print_list(indent+4, cfg->list);
printf("%*s}", indent, "");
}
cfg= cfg->next;
if (cfg != nil) fputc(' ', stdout);
}
printf(";\n");
}
static void print_list(int indent, config_t *cfg)
{
while (cfg != nil) {
if (!config_issub(cfg)) {
fprintf(stderr, "Cell at \"%s\", line %u is not a sublist\n");
break;
}
printf("%*s", indent, "");
print_words(indent, cfg->list);
cfg= cfg->next;
}
}
static void print_config(config_t *cfg)
{
if (!config_renewed(cfg)) {
printf("# Config didn't change\n");
} else {
print_list(0, cfg);
}
}
int main(int argc, char **argv)
{
config_t *cfg;
int c;
if (argc != 2) {
fprintf(stderr, "One config file name please\n");
exit(1);
}
cfg= nil;
do {
cfg= config_read(argv[1], CFG_ESCAPED, cfg);
print_config(cfg);
if (!isatty(0)) break;
while ((c= getchar()) != EOF && c != '\n') {}
} while (c != EOF);
return 0;
}
#endif /* TEST */