/* This file is in the public domain. */ /* * Config-file reading and parsing code. */ #include #include #include #include #include #include #include extern const char *__progname; #include "vars.h" #include "util.h" #include "repo.h" #include "ctype.h" #include "listen.h" #include "structs.h" #include "linereader.h" #include "config.h" /* * Transient state used during parsing. * * ops and sectpriv are the dispatch vector and instance variables for * the section type we're currently in; if we're not yet in any * section, ops is nil and sectpriv is undefined. * * filename is the name of the file being parsed. This is kept around * for error messages. * * lr is a LINEREADER for breaking data from the file into lines. * * errf is a FILE * to which error messages are to be written. This is * a wrapped version of something else (see stdio_wrap_w), usually * stderr, to prepend file name and line number. * * newline is 1 if errf is at the beginning of a new line, that is, if * we should prepend name-and-line info. * * err_throw should be called on error. It will throw out. * * When an error occurs, a message describing it should be printed to * errf and then err_throw should be called. This will usually throw * back to reload_config(), but it is sometimes wrapped further (see * config_close_current_sect() for an example). If the `error' is * really just a warning, print it to errf as if it were an error, but * don't call err_throw. (These will normally be done by config_err * and friends rather than direct calls.) */ struct config_parse_temp { const SECT_OPS *ops; void *sectpriv; const char *filename; LINEREADER *lr; FILE *errf; int newline; void (*err_throw)(void); } ; /* * SECT_OPS vectors for the various kinds of sections. We iterate over * all section types when checking to see if a line begins a new * section. */ extern const SECT_OPS sectops_repo; extern const SECT_OPS sectops_listen; static const SECT_OPS * const sects[] = { §ops_repo, §ops_listen }; /* * Nothing to add here to the description in config.h. */ void config_warn(CONFIG *cfg, const char *fmt, ...) { va_list ap; if (! cfg->pt) abort(); va_start(ap,fmt); vfprintf(cfg->pt->errf,fmt,ap); va_end(ap); putc('\n',cfg->pt->errf); } /* * Nothing to add here to the description in config.h. */ void config_err(CONFIG *cfg, const char *fmt, ...) { va_list ap; if (! cfg->pt) abort(); va_start(ap,fmt); vfprintf(cfg->pt->errf,fmt,ap); va_end(ap); putc('\n',cfg->pt->errf); (*cfg->pt->err_throw)(); } /* * Nothing to add here to the description in config.h. */ void config_err_post(void (*post)(void), CONFIG *cfg, const char *fmt, ...) { va_list ap; if (! cfg->pt) abort(); va_start(ap,fmt); vfprintf(cfg->pt->errf,fmt,ap); va_end(ap); putc('\n',cfg->pt->errf); (*post)(); (*cfg->pt->err_throw)(); } /* * Close the current section. This is called when a line beginning a * new section is found. * * We basically just call through the ops vector to close off the old * section. This is complicated by wanting errors for the old section * to be marked as such, since the line number is the beginning line * for the new section. So we wrap errf and err_throw - the former to * prepend a note pointing out that the error/warning actually applies * to the previous section, the latter so we can regain control on a * throw (we need to unwrap errf, and we also don't want errors to be * fatal to the beginning of the new section); we return a "we got an * error" boolean rather than throwing on errors. */ static int config_close_current_sect(CONFIG *cfg) { int err; err = 0; if (cfg->pt->ops) { __label__ errjmp; FILE *fsave; void (*saved_err_throw)(void); int newline; void err_throw(void) { err = 1; goto errjmp; } void err_wrap(FILE *f, void *ptv, const char *buf, int len) { CONFIG_PARSE_TEMP *pt; pt = ptv; for (;len>0;buf++,len--) { if (newline) fprintf(f,"ending previous section: "); putc(*buf,f); newline = (*buf == '\n'); } } saved_err_throw = cfg->pt->err_throw; cfg->pt->err_throw = &err_throw; fsave = cfg->pt->errf; cfg->pt->errf = stdio_wrap_w(fsave,cfg->pt,&err_wrap,0); newline = 1; (*cfg->pt->ops->done)(cfg->pt->sectpriv); errjmp:; fclose(cfg->pt->errf); cfg->pt->errf = fsave; cfg->pt->err_throw = saved_err_throw; } return(err); } /* * Process a config-file line. * * This handles comments itself. Other lines are passed to the parser * for the current section; if there is no current section yet, or if * that parser doesn't recognize the line, we scan all sections, * calling their start methods to see if we're beginning a new * section. If all that fails, we just report an unrecognized line. */ static void config_process(CONFIG *cfg, const char *line) { int i; for (i=0;line[i]&&UCisspace(line[i]);i++) ; switch (line[i]) { case '\0': case '#': return; break; } if (cfg->pt->ops) { switch ((*cfg->pt->ops->parse)(cfg->pt->sectpriv,line)) { case SPRV_GOOD: return; break; case SPRV_UNKNOWN: break; default: abort(); break; } } for (i=(sizeof(sects)/sizeof(sects[0]))-1;i>=0;i--) { void *pp; pp = (*sects[i]->start)(line,cfg); if (pp) { int err; err = config_close_current_sect(cfg); cfg->pt->ops = sects[i]; cfg->pt->sectpriv = pp; if (err) (*cfg->pt->err_throw)(); return; } } if (cfg->pt->ops) { config_err(cfg,"not valid in this section and not a valid section start line"); } else { config_err(cfg,"not a valid section start line"); } } /* * We've reached EOF; do end-of-parse actions. Close the current * section (if any) and clear variables to help catch potential bugs. */ static void config_close_parse(CONFIG *cfg) { int err; err = config_close_current_sect(cfg); cfg->pt->ops = 0; cfg->pt->sectpriv = 0; if (err) (*cfg->pt->err_throw)(); } /* * Free a CONFIG. This is used by reload_config. * * There is a subtlety here. LISTENs are refcounted, as explained in * structs.h. But this means that a LISTEN can out-survive its * CONFIG. To avoid using a dangling CONFIG pointer, we clear the * conf pointer in all our LISTENs before dereffing them. */ static void config_free(CONFIG *cfg) { LISTEN *l; if (cfg->pt) abort(); repo_free_chain(cfg->repos); for (l=cfg->listens;l;l=l->link) l->conf = 0; listen_deref_chain(cfg->listens); free(cfg); } /* * Shut down config-file parser, freeing all associated storage. */ static void config_parse_done(CONFIG *cfg) { lr_done(cfg->pt->lr); fclose(cfg->pt->errf); free(cfg->pt); cfg->pt = 0; } /* * Switch from an existing config to a newly-parsed config. * * This is a somewhat complex dance; we have to figure out exactly how * much we can move over from the old config before committing to * switching. This is why we have rmatches here and the four-call * *_transfer paradigm for LEPs. */ static int config_switch(CONFIG *old, CONFIG *new) { __label__ errjmp; REPO *oldru; REPO *newru; PAIRLIST *rmatches; REPO_PRIV *rp; REPO **orp; REPO *or; REPO **nrp; REPO *nr; int errs; PAIRLIST *pl; LISTEN *ol; LISTEN *nl; LEP *olep; LEP *nlep; char *errmsg; void err(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vasprintf(&errmsg,fmt,ap); va_end(ap); goto errjmp; } errs = 0; rmatches = 0; oldru = old ? old->repos : 0; newru = new->repos; nrp = &newru; while <"newr"> ((nr = *nrp)) { rp = repo_open(nr->filename,&err); if (0) { errjmp:; fprintf(stderr,"%s: opening repo %s: %s\n",__progname,nr->name,errmsg); free(errmsg); errs = 1; } else { for (orp=&oldru;(or=*orp);orp=&or->link) { if (repo_same(nr->priv,rp)) { repo_close(rp); *orp = or->link; *nrp = nr->link; pl = malloc(sizeof(PAIRLIST)); pl->a = or; pl->b = nr; pl->link = rmatches; rmatches = pl; continue <"newr">; } } nr->priv = rp; } nrp = &nr->link; } for (ol=old?old->listens:0;ol;ol=ol->link) { for (olep=ol->endpoints;olep;olep=olep->link) { (*olep->ops->pre_transfer)(olep->priv); } } for (nl=new->listens;nl;nl=nl->link) { for <"new"> (nlep=nl->endpoints;nlep;nlep=nlep->link) { for (ol=old?old->listens:0;ol;ol=ol->link) { for (olep=ol->endpoints;olep;olep=olep->link) { if (olep->ops == nlep->ops) (*olep->ops->setup_transfer)(olep->priv,nlep->priv); } } if ((*nlep->ops->openfds)(nlep->priv)) errs = 1; } } if (errs) { while (rmatches) { pl = rmatches; rmatches = pl->link; ((REPO *)pl->a)->link = old->repos; old->repos = pl->a; ((REPO *)pl->b)->link = new->repos; new->repos = pl->b; free(pl); } for (ol=old?old->listens:0;ol;ol=ol->link) { for (olep=ol->endpoints;olep;olep=olep->link) { (*olep->ops->no_transfer)(olep->priv); } } return(1); } while (rmatches) { pl = rmatches; rmatches = pl->link; or = pl->a; nr = pl->b; nr->priv = or->priv; or->priv = 0; free(pl); } for (nl=new->listens;nl;nl=nl->link) { for (nlep=nl->endpoints;nlep;nlep=nlep->link) { (*nlep->ops->do_transfer)(nlep->priv); (*nlep->ops->setup_poll)(nlep->priv); } } return(0); } /* * This is the stdio wrap function for prepending filename and line * number information to errors and warnings. */ static void config_err_wrap(FILE *f, void *ptv, const char *buf, int len) { CONFIG_PARSE_TEMP *pt; pt = ptv; for (;len>0;buf++,len--) { if (pt->newline) fprintf(f,"%s: line %d: ",pt->filename,lr_lineno(pt->lr)); putc(*buf,f); pt->newline = (*buf == '\n'); } } /* * Main entry to this file. Does a config-file reload, parsing the new * config and, if it succeeds, switching to the new configuration. * * This consists of opening the file, setting up the parser, fetching * the lines, processing them, shutting down the parser, closing the * file, possibly switching configs, and freeing now-unneeded configs * (the old one, if we switched, or the new one, if we didn't). */ int reload_config(void) { __label__ errjmp; CONFIG *cfg; int fd; int err; int errs; void err_throw(void) { err = 1; goto errjmp; } fd = open(configfile,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: %s: open: %s\n",__progname,configfile,strerror(errno)); return(-1); } cfg = malloc(sizeof(CONFIG)); cfg->repos = 0; cfg->listens = 0; cfg->pt = malloc(sizeof(CONFIG_PARSE_TEMP)); cfg->pt->ops = 0; cfg->pt->sectpriv = 0; cfg->pt->filename = configfile; cfg->pt->lr = lr_setup(fd); cfg->pt->errf = stdio_wrap_w(stderr,cfg->pt,&config_err_wrap,0); cfg->pt->newline = 1; cfg->pt->err_throw = 0; if (fstat(fd,&cfg->confstat) < 0) { fprintf(stderr,"%s: %s: fstat %s\n",__progname,configfile,strerror(errno)); config_parse_done(cfg); free(cfg); close(fd); return(-1); } errs = 0; while <"lines"> (1) { switch (lr_getline(cfg->pt->lr)) { case LRGL_ERROR: errs = 1; break <"lines">; case LRGL_EOF: break <"lines">; case LRGL_LINE: break; default: abort(); break; } cfg->pt->err_throw = &err_throw; err = 0; config_process(cfg,lr_ptr(cfg->pt->lr)); errjmp:; fflush(cfg->pt->errf); cfg->pt->err_throw = 0; if (err) errs = 1; if (err < 0) break; } config_close_parse(cfg); config_parse_done(cfg); close(fd); if (errs || config_switch(conf,cfg)) { config_free(cfg); return(-1); } else { if (conf) config_free(conf); conf = cfg; return(0); } }