575 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			575 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 */
 |