/***             analog 4.16             http://www.analog.cx/             ***/
/*** This program is copyright (c) Stephen R. E. Turner 1995 - 2001 except as
 *** stated otherwise. Distribution, usage and modification of this program is
 *** subject to the conditions of the Licence which you should have received
 *** with it. This program comes with no warranty, expressed or implied.   ***/

/*** init2.c; subsiduary functions for parsing config files */
/* See also init.c */

#include "anlghea3.h"

/*** All config commands have the following structure:
  void config.....(void *opt, char *cmd, char *arg1, char *arg2, int rc)
  opt is the variable to be changed, "cmd arg1 arg2" is the config command
  issued, rc is the number of arguments retrieved from the config line (0..3),
  or -1 if the config command was issued by the program internally,
  or -2 if it was issued by the program in parsing command line (the last
  don't go through confline(), so cmd is command line option). -3 is
  occasionally used for special cases. ***/

/* First some warnings of things that can go wrong in config commands.
   NB rc = -1 announced to be because anlghead.h got messed up. But it
   could be an internal error caused by a bug. */

void shortwarn(char *cmd, char *arg1, int rc) {
  char *s = "Not enough arguments for configuration command: ignoring it";
  if (rc == -1)
    error("Default given for %s in anlghead.h too short");
  else if (arg1 == NULL)
    warn('C', TRUE, "%s:\n%s", s, cmd);
  else
    warn('C', TRUE, "%s:\n%s %s", s, cmd, delimit(arg1));
}

void longwarn(char *cmd, char *arg1, char *arg2, int rc) {
  char *s = "Too many arguments for configuration command: "
    "ignoring end of line starting";
  if (rc == -1)
    warn('C', TRUE, "Default given for %s in anlghead.h too long: "
	 "ignoring end of it", cmd);
  else if (arg2 == NULL)
    warn('C', TRUE, "%s:\n%s %s", s, cmd, delimit(arg1));
  else
    warn('C', TRUE, "%s:\n%s %s %s", s, cmd, delimit(arg1), delimit(arg2));
}

void badwarn(char *cmd, choice domess, char *arg1, char *arg2, int rc) {
  char *s = "Bad argument in configuration command: ignoring it";
  if (rc == -2)
    warn('C', TRUE, "Bad argument in command line argument %s: ignoring it",
	 cmd);
  else if (rc == -1)
    error("Incorrect default given for %s in anlghead.h", cmd);
  else if (arg2 == NULL)
    warn('C', domess, "%s:\n%s %s", s, cmd, delimit(arg1));
  else
    warn('C', domess, "%s:\n%s %s %s", s, cmd, delimit(arg1), delimit(arg2));
}

void unknownwarn(char *cmd, char *arg1, char *arg2) {
  char *s = "Unknown configuration command: ignoring it";
  if (arg1 == NULL)
    warn('C', TRUE, "%s:\n%s", s, cmd);  
  else if (arg2 == NULL)
    warn('C', TRUE, "%s:\n%s %s", s, cmd, delimit(arg1));
  else
    warn('C', TRUE, "%s:\n%s %s %s", s, cmd, delimit(arg1), delimit(arg2));
}

void configcall(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  /* calling function will then call config() */
}

void configcols(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *cols = (choice *)opt;  /* see also configallcols() */

  logical warn1 = FALSE, warn2 = FALSE, warn3;
  char *c;
  int i = 0, j;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  for (c = arg1; *c != '\0' && i < COL_NUMBER - 1; c++, i++) {
    if (*c == 'R')
      cols[i] = COL_REQS;
    else if (*c == 'P')
      cols[i] = COL_PAGES;
    else if (*c == 'B')
      cols[i] = COL_BYTES;
    else if (*c == 'r')
      cols[i] = COL_PREQS;
    else if (*c == 'p')
      cols[i] = COL_PPAGES;
    else if (*c == 'b')
      cols[i] = COL_PBYTES;
    else if (*c == 'd')
      cols[i] = COL_DATE;
    else if (*c == 'D')
      cols[i] = COL_TIME;
    else if (*c == 'N')
      cols[i] = COL_INDEX;
    else {
      i--;
      if (!warn1) {
	warn('C', TRUE, "Ignoring unknown columns in\n%s %s", cmd, arg1);
	warn1 = TRUE;
      }
    }
    if (cols[i] == COL_DATE && strchr(arg1, 'D') != NULL) {
      i--;      /* cancel i++ in for loop, so overwriting this column */
      if (!warn2) {
	warn('C', TRUE,
	     "Ignoring repeated columns in configuration command\n%s %s",
	     cmd, arg1);
	warn2 = TRUE;
      }
    }
    else {
      for (j = 0, warn3 = FALSE; j < i && !warn3; j++) { 
	if (cols[j] == cols[i]) {         /* shortest code, not least work */
	  warn3 = TRUE;
	  i--;   /* see above */
	  if (!warn2) {
	    warn('C', TRUE,
		 "Ignoring repeated columns in configuration command\n%s %s",
		 cmd, arg1);
	    warn2 = TRUE;
	  }
	}
      }
    }
  }
  if (*c != '\0' /* can this happen? */ || rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  cols[i] = COL_NUMBER;
}

void configallcols(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *cols = (choice *)opt;
  int j;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  for (j = 0; j < DATEREP_NUMBER; j++)    /* possibility of dup. warns */
    configcols((void *)&(cols[j * COL_NUMBER]), cmd, arg1, NULL, -3);
}

/* now a set of similar configs that use symbolic words */
void configsortby(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *sortby = (choice *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "REQUESTS"))
    *sortby = REQUESTS;
  else if (strcaseeq(arg1, "PAGES"))
    *sortby = PAGES;
  else if (strcaseeq(arg1, "BYTES"))
    *sortby = BYTES;
  else if (strcaseeq(arg1, "DATE"))
    *sortby = DATESORT;
  else if (strcaseeq(arg1, "ALPHABETICAL"))
    *sortby = ALPHABETICAL;
  else if (strcaseeq(arg1, "RANDOM"))
    *sortby = RANDOM;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

void configdebug(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char **args = (char **)opt;
  char *c;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "ON") || strcaseeq(arg1, "TRUE"))
    configstr(args, NULL, "CDEFLMRSUV", NULL, -1);
  /* kludge: include all of both sets: same string in settings.c */
  else if (strcaseeq(arg1, "OFF") || strcaseeq(arg1, "FALSE"))
    configstr(args, NULL, "", NULL, -1);
  else if (arg1[0] == '-') {
    while ((c = strpbrk(*args, arg1 + 1)) != NULL)
      memmove((void *)c, (void *)(c + 1), strlen(c));
  }
  else if (arg1[0] == '+') {  /* c.f. configstr() */
    *args = (char *)xrealloc((void *)(*args), strlen(arg1) + strlen(*args));
    memcpy((void *)strchr(*args, '\0'), (void *)(arg1 + 1), strlen(arg1));
  }
  else {
    if (arg1[0] == '+')
      arg1++;
    configstr(args, NULL, arg1, NULL, -1);
  }
}

void configonoff(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  logical *q = (logical *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "ON") || strcaseeq(arg1, "TRUE"))
    *q = TRUE;
  else if (strcaseeq(arg1, "OFF") || strcaseeq(arg1, "FALSE"))
    *q = FALSE;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

void configall(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  logical *q = (logical *)opt;
  int i;
  logical result;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strcaseeq(arg1, "ON") || strcaseeq(arg1, "TRUE"))
    result = TRUE;
  else if (strcaseeq(arg1, "OFF") || strcaseeq(arg1, "FALSE"))
    result = FALSE;
  else {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  for (i = 0; i < REP_NUMBER; i++) {
    if (i != REP_GENSUM)
      q[i] = result;
  }
}

void configallback(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  logical *q = (logical *)opt;   /* same as configall(), but only DATEREPs */
  int i;
  logical result;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strcaseeq(arg1, "ON") || strcaseeq(arg1, "TRUE"))
    result = TRUE;
  else if (strcaseeq(arg1, "OFF") || strcaseeq(arg1, "FALSE"))
    result = FALSE;
  else {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  for (i = 0; i < DATEREP_NUMBER; i++)
    q[i] = result;
}

void configgotos(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *q = (choice *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "ON") || strcaseeq(arg1, "TRUE"))
    *q = TRUE;
  else if (strcaseeq(arg1, "OFF") || strcaseeq(arg1, "FALSE"))
    *q = FALSE;
  else if (strcaseeq(arg1, "FEW"))
    *q = FEW;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

void configcase(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  logical *q = (logical *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "INSENSITIVE"))
    *q = TRUE;
  else if (strcaseeq(arg1, "SENSITIVE"))
    *q = FALSE;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

void configweekday(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *d = (choice *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "SUNDAY"))
    *d = SUNDAY;
  else if (strcaseeq(arg1, "MONDAY"))
    *d = MONDAY;
  else if (strcaseeq(arg1, "TUESDAY"))
    *d = TUESDAY;
  else if (strcaseeq(arg1, "WEDNESDAY"))
    *d = WEDNESDAY;
  else if (strcaseeq(arg1, "THURSDAY"))
    *d = THURSDAY;
  else if (strcaseeq(arg1, "FRIDAY"))
    *d = FRIDAY;
  else if (strcaseeq(arg1, "SATURDAY"))
    *d = SATURDAY;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

void configoutstyle(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *s = (choice *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "HTML"))
    *s = HTML;
  else if (strcaseeq(arg1, "PLAIN"))
    *s = PLAIN;
  else if (strcaseeq(arg1, "ASCII"))
    *s = ASCII;
  else if (strcaseeq(arg1, "COMPUTER") || strcaseeq(arg1, "PREFORMATTED") ||
	   strcaseeq(arg1, "CRO"))
    *s = COMPUTER;
  else if (strcaseeq(arg1, "CACHE") || strcaseeq(arg1, "NONE"))
    *s = OUT_NONE;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}

#ifndef NODNS
void configdns(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *s = (choice *)opt;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "NONE"))
    *s = DNS_NONE;
  else if (strcaseeq(arg1, "READ"))
    *s = DNS_READ;
  else if (strcaseeq(arg1, "LOOKUP"))
    *s = DNS_LOOKUP;
  else if (strcaseeq(arg1, "WRITE"))
    *s = DNS_WRITE;
  else
    badwarn(cmd, TRUE, arg1, arg2, rc);
}
#endif

void configlang(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Lang *lang = (Lang *)opt;
  logical v3lang = FALSE;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "ARMENIAN"))
    lang->code = ARMENIAN;
  else if (strcaseeq(arg1, "BOSNIAN"))
    lang->code = BOSNIAN;
  else if (strcaseeq(arg1, "BULGARIAN"))
    lang->code = BULGARIAN;
  else if (strcaseeq(arg1, "CATALAN"))
    lang->code = CATALAN;
  else if (strcaseeq(arg1, "SIMP-CHINESE") || strcaseeq(arg1, "CHINESE"))
    lang->code = SIMP_CHINESE;
  else if (strcaseeq(arg1, "TRAD-CHINESE"))
    lang->code = TRAD_CHINESE;
  else if (strcaseeq(arg1, "CROATIAN"))
    lang->code = CROATIAN;
  else if (strcaseeq(arg1, "CZECH"))
    lang->code = CZECH;
  else if (strcaseeq(arg1, "DANISH"))
    lang->code = DANISH;
  else if (strcaseeq(arg1, "FLEMISH") || strcaseeq(arg1, "DUTCH"))
    lang->code = DUTCH;
  else if (strcaseeq(arg1, "ENGLISH"))
    lang->code = ENGLISH;
  else if (strcaseeq(arg1, "US-ENGLISH"))
    lang->code = US_ENGLISH;
  else if (strcaseeq(arg1, "FINNISH"))
    lang->code = FINNISH;
  else if (strcaseeq(arg1, "FRENCH"))
    lang->code = FRENCH;
  else if (strcaseeq(arg1, "GERMAN"))
    lang->code = GERMAN;
  else if (strcaseeq(arg1, "GREEK"))
    lang->code = GREEK;
  else if (strcaseeq(arg1, "HUNGARIAN"))
    lang->code = HUNGARIAN;
  else if (strcaseeq(arg1, "ICELANDIC"))
    lang->code = ICELANDIC;
  else if (strcaseeq(arg1, "ITALIAN"))
    lang->code = ITALIAN;
  else if (strcaseeq(arg1, "JAPANESE"))
    lang->code = JAPANESE;
  else if (strcaseeq(arg1, "KOREAN"))
    lang->code = KOREAN;
  else if (strcaseeq(arg1, "LATVIAN"))
    /* lang->code = LATVIAN */ v3lang = TRUE;
  else if (strcaseeq(arg1, "LITHUANIAN"))
    /* lang->code = LITHUANIAN */ v3lang = TRUE;
  else if (strcaseeq(arg1, "NORWEGIAN"))
    lang->code = NORWEGIAN;
  else if (strcaseeq(arg1, "NYNORSK"))
    lang->code = NYNORSK;
  else if (strcaseeq(arg1, "POLISH"))
    lang->code = POLISH;
  else if (strcaseeq(arg1, "PORTUGUESE") || strcaseeq(arg1, "PORTUGESE"))
    lang->code = PORTUGUESE;
  else if (strcaseeq(arg1, "BR-PORTUGUESE") || strcaseeq(arg1, "BR-PORTUGESE"))
    lang->code = BR_PORTUGUESE;
  else if (strcaseeq(arg1, "ROMANIAN"))
    lang->code = ROMANIAN;
  else if (strcaseeq(arg1, "RUSSIAN"))
    lang->code = RUSSIAN;
  else if (strcaseeq(arg1, "SERBIAN") || strcaseeq(arg1, "SERBOCROATIAN") ||
	   strcaseeq(arg1, "SERBOCROAT"))
    lang->code = SERBIAN;
  else if (strcaseeq(arg1, "SLOVAK"))
    lang->code = SLOVAK;
  else if (strcaseeq(arg1, "SLOVENE") || strcaseeq(arg1, "SLOVENIAN"))
    lang->code = SLOVENE;
  else if (strcaseeq(arg1, "SPANISH"))
    lang->code = SPANISH;
  else if (strcaseeq(arg1, "SWEDISH"))
    lang->code = SWEDISH;
  else if (strcaseeq(arg1, "TURKISH"))
    lang->code = TURKISH;
  else if (strcaseeq(arg1, "UKRAINIAN") || strcaseeq(arg1, "UKRANIAN"))
    lang->code = UKRAINIAN;
  else {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  if (v3lang) {
    badwarn(cmd, FALSE, arg1, arg2, rc);
    warn('C', CONTINUATION, "  (language not yet translated for version 4)");
    return;
  }
  lang->file = NULL;   /* if successful */
}

void selectlang(char *country, Outchoices *op) {
  /* Select langfile and domains file with country code. If outstyle is HTML
     or ASCII, look first for special files for those styles. */
  extern char *workspace;
  FILE *f;

  if (op->outstyle == HTML || op->outstyle == ASCII) {
    sprintf(workspace, "%s%s%c%s", LANGDIR, country,
	    (op->outstyle == HTML)?'h':'a', EXTSEP"lng");
  }
  if ((op -> outstyle != HTML && op->outstyle != ASCII) ||
      (f = FOPENR(workspace)) == NULL)
    sprintf(workspace, "%s%s%s", LANGDIR, country, EXTSEP"lng");
  else
    fclose(f);
  configstr(&(op->lang.file), NULL, workspace, NULL, -1);

  if (op->domainsfile == NULL) {
    if (op->outstyle == HTML || op->outstyle == ASCII) {
      sprintf(workspace, "%s%s%c%s", LANGDIR, country,
	      (op->outstyle == HTML)?'h':'a', "dom"EXTSEP"tab");
    }
    if ((op->outstyle == HTML || op->outstyle == ASCII) &&
	(f = FOPENR(workspace)) != NULL) {
      fclose(f);
      configstr(&(op->domainsfile), NULL, workspace, NULL, -1);
    }
    else {
      sprintf(workspace, "%s%s%s", LANGDIR, country, "dom"EXTSEP"tab");
      if ((f = FOPENR(workspace)) != NULL) {
	fclose(f);
	configstr(&(op->domainsfile), NULL, workspace, NULL, -1);
      }  /* otherwise will get set to ukdom.tab on return to finalinit() */
    }
  }
}

void configlogfmt(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  extern Memman *xmemman;
  Inputformatlist **logfmt = (Inputformatlist **)opt;
  Inputformatlist *fp;
  Inputformat *form;
  choice count[INPUT_NUMBER];
  choice rc2;
  int i;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strcaseeq(arg1, "DEFAULT")) {
    (*logfmt)->used = TRUE;
    configlogfmt(opt, NULL, "%x0", NULL, -1);
    (*logfmt)->used = TRUE;
  }
  else if (strcaseeq(arg1, "AUTO")) {
    (*logfmt)->used = TRUE;
    configlogfmt(opt, NULL, "%x1", NULL, -1);
    (*logfmt)->used = TRUE;
  }
  else if (strcaseeq(arg1, "COMMON")) {
    configlogfmt(opt, NULL, LOG_COMMON1, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMMON2, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMMON3, NULL, -1);
  }
  else if (strcaseeq(arg1, "MS-COMMON")) {
    configlogfmt(opt, NULL, LOG_MS_COMMON1, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMMON2, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMMON3, NULL, -1);
  }
  else if (strcaseeq(arg1, "COMBINED")) {
    configlogfmt(opt, NULL, LOG_COMBINED1, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMBINED2, NULL, -1);
    configlogfmt(opt, NULL, LOG_COMBINED3, NULL, -1);
  }
  else if (strcaseeq(arg1, "MICROSOFT-NA")) {
    configlogfmt(opt, NULL, LOG_MS_NA1, NULL, -1);
    configlogfmt(opt, NULL, LOG_MS_NA2, NULL, -1);
  }
  else if (strcaseeq(arg1, "MICROSOFT-INT")) {
    configlogfmt(opt, NULL, LOG_MS_INT1, NULL, -1);
    configlogfmt(opt, NULL, LOG_MS_INT2, NULL, -1);
  }
  else if (strcaseeq(arg1, "WEBSITE-NA"))
    configlogfmt(opt, NULL, LOG_WEBSITE_NA, NULL, -1);
  else if (strcaseeq(arg1, "WEBSITE-INT"))
    configlogfmt(opt, NULL, LOG_WEBSITE_INT, NULL, -1);
  else if (strcaseeq(arg1, "WEBSTAR")) {
    configlogfmt(opt, NULL, LOG_WEBSTAR1, NULL, -1);
    configlogfmt(opt, NULL, LOG_WEBSTAR2, NULL, -1);
  }
  else if (strcaseeq(arg1, "EXTENDED")) {
    configlogfmt(opt, NULL, LOG_EXTENDED1, NULL, -1);
    configlogfmt(opt, NULL, LOG_EXTENDED2, NULL, -1);
  }
  else if (strcaseeq(arg1, "MS-EXTENDED")) {
    configlogfmt(opt, NULL, LOG_MS_EXTENDED1, NULL, -1);
    configlogfmt(opt, NULL, LOG_EXTENDED2, NULL, -1);
  }
  else if (strcaseeq(arg1, "WEBSTAR-EXTENDED")) {
    configlogfmt(opt, NULL, LOG_WEBSTAR_EXTENDED1, NULL, -1);
    configlogfmt(opt, NULL, LOG_EXTENDED2, NULL, -1);
  }
  else if (strcaseeq(arg1, "NETSCAPE"))
    configlogfmt(opt, NULL, LOG_NETSCAPE, NULL, -1);
  else if (strcaseeq(arg1, "BROWSER")) 
    configlogfmt(opt, NULL, LOG_BROWSER, NULL, -1);
  else if (strcaseeq(arg1, "REFERRER") || strcaseeq(arg1, "REFERER")) {
    configlogfmt(opt, NULL, LOG_REFERRER1, NULL, -1);
    configlogfmt(opt, NULL, LOG_REFERRER2, NULL, -1);
  }
  else if ((rc2 = strtoinfmt(&form, arg1, count)) != FMT_OK) {
    if (rc == -3)
      warn('C', FALSE, "Ignoring corrupt format line in logfile");
    else
      badwarn(cmd, FALSE, arg1, arg2, rc);
    if (rc2 == FMT_DUP)
      warn('C', CONTINUATION, "  (reason: one item occurs twice in format)");
    else if (rc2 == FMT_PARTTIME)
      warn('C', CONTINUATION, "  (reason: time without date or vice versa)");
    else if (rc2 == FMT_BADPC)
      warn('C', CONTINUATION, "  (reason: an unknown item code is present)");
    else if (rc2 == FMT_NOPC)
      warn('C', CONTINUATION, "  (reason: no item codes are present)");
    else if (rc2 == FMT_BADCHAR)
      warn('C', CONTINUATION,
	   "  (reason: an unknown escape sequence is present)");
    else if (rc2 == FMT_NOTERM)
      warn('C', CONTINUATION, "  (reason: an unterminated string is present)");
    else if (rc2 == FMT_BADBUILTIN)
      warn('C', CONTINUATION, "  (reason: non-existent built-in format)");
    else if (rc2 == FMT_QBUTNOR)
      warn('C', CONTINUATION, "  (reason: query string without filename)");
    /* despite finishing "else if", that should be all the rc2's */
    return;
  }
  else {
    if ((*logfmt)->used) {
      fp = (Inputformatlist *)submalloc(xmemman, sizeof(Inputformatlist));
      *logfmt = fp;
      (*logfmt)->used = FALSE;
    }
    else {
      for (fp = *logfmt; fp->next != NULL; TO_NEXT(fp))
	;
      fp->next = (Inputformatlist *)submalloc(xmemman,
					      sizeof(Inputformatlist));
      TO_NEXT(fp);
    }
    fp->form = form;
    for (i = 0; i < INPUT_NUMBER; i++)
      fp->count[i] = count[i];
    fp->next = NULL;
  }
}

void configapachelogfmt(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *p;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if ((p = apachelogfmt(arg1)) == NULL)
    warn('C', TRUE,
	 "Sorry, can't parse \"%%...{format}t\" in %s: ignoring whole line",
	 cmd);

  else if (STREQ(cmd, "APACHELOGFORMAT"))
    configlogfmt(opt, "APACHELOGFORMAT -> LOGFORMAT", p, NULL, 1);
  else
    configlogfmt(opt, "APACHEDAFAULTLOGFORMAT -> DEFAULTLOGFORMAT", p,
		 NULL, 1);
}

void configrepord(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  extern char repcodes[];
  char *s1 = "REPORTORDER";
  char *s2 = "REPORTORDER in anlghead.h";
  char *s = (rc == -1)?s2:s1;
  choice *order = (choice *)opt;
  logical used[REP_NUMBER];
  int i, j, k;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }

  for (i = 0; i < REP_NUMBER; i++)
    used[i] = 0;
  for (i = 0, k = 0; k < REP_NUMBER && arg1[i] != '\0'; i++) {
    if (ISALNUM(arg1[i])) {  /* else ignore */
      for (j = 0; repcodes[j] != arg1[i] && repcodes[j] != '\0'; j++)
	;
      if (repcodes[j] == '\0')
	warn('C', TRUE, "Spurious character %c in %s: ignoring it", arg1[i],
	     s);
      else if (used[j] == 0) {
	order[k++] = j;
	used[j] = 1;
      }
      else if (used[j] == 1) {
	warn('C', TRUE, "Character %c used more than once in %s: "
	     "ignoring later occurrences", arg1[i], s);
	used[j] = 2;
      }
    }
  }

  if (rc > 1 || arg1[i] != '\0')
    longwarn(cmd, arg1, arg2, rc);

  if (k < REP_NUMBER) {
    for (i = 0; i < REP_NUMBER && k < REP_NUMBER; i++) {
      if (used[i] == 0) {     /* k should stay < R_N automatically, but... */
	warn('C', TRUE, "Character %c not used in %s: adding it at end",
	     repcodes[i], s);
	order[k++] = i;
      }
    }
  }
  order[k] = -1;
}

void configstr(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char **s = (char **)opt;
  size_t len;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  len = strlen(arg1);
  *s = (char *)xrealloc((void *)(*s), len + 1);
  strcpy(*s, arg1);
}

void configstrlist(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Strlist **list = (Strlist **)opt;
  Strlist *lp = NULL;
  char *t;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc == -1)  /* have to be careful because can't strtok a const string */
    t = arg1;
  else if ((t = strtok(arg1, ",")) == NULL) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (*list != NULL) {
    for (lp = *list; lp->next != NULL; TO_NEXT(lp))
      ;  /* find end of list */
  }
  for ( ; t != NULL; t = ((rc == -1)?NULL:strtok((char *)NULL, ","))) {
    if (*list == NULL) {
      lp = (Strlist *)xmalloc(sizeof(Strlist));
      *list = lp;
    }
    else {
      lp->next = (Strlist *)xmalloc(sizeof(Strlist));
      TO_NEXT(lp);
    }
    DEFAULTSTR(lp->name, t);
    lp->next = NULL;
  }
  return;
}

void configerrfile(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  /* NB Don't use freopen(), coz fatal errors sent to both errfile & stderr. */
  FILE **err = (FILE **)opt;
  FILE *tmp;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strcaseeq(arg1, "stderr")) {
    if (*err != stderr) {
      debug('F', "Opening stderr as new ERRFILE");
      warn('E', TRUE, "Redirecting future diagnostic messages to stderr");
      fclose(*err);
      *err = stderr;
    }
  }
  else if ((tmp = FOPENW(arg1)) == NULL)
    warn('F', TRUE, "Failed to open ERRFILE %s: ignoring it", arg1);
  else {
    debug('F', "Opening %s as new ERRFILE", arg1);
    warn('E', TRUE, "Redirecting future diagnostic messages to %s", arg1);
    if (*err != stderr)
      fclose(*err);
    *err = tmp;
    setvbuf(*err, NULL, ERRBUFMODE, BUFSIZ);
  }
}

#ifndef NODIRENT
#ifndef VMSDIRENT
#ifndef WIN32DIRENT  /* i.e. if POSIX dirent */
void configwildlogs(void *opt, char *arg1, char *arg2, int rc) {
  struct dirent *filep;
  struct stat buf;
  DIR *dirp;
  char *dirname, *filename, *c;
  size_t dirlen, len = 0;
  logical matched = FALSE;

  if ((c = strrchr(arg1, DIRSEP)) == NULL) {
    filename = arg1;
    dirname = (char *)xmalloc(3);
#ifdef MAC
    dirname[0] = '\0';
#else
    sprintf(dirname, ".%c", DIRSEP);
#endif
  }
  else {
    filename = c + 1;
    dirname = (char *)xmalloc((size_t)(c - arg1) + 2);
    memcpy((void *)dirname, (void *)arg1, (size_t)(c - arg1) + 1);
    dirname[(c - arg1) + 1] = '\0';
  }

  if ((dirp = opendir(dirname)) == NULL) {
    if (strchr(dirname, '*') != NULL || strchr(dirname, '?') != NULL)
      warn('F', TRUE, "Cannot open directory %s: ignoring logfiles %s\n"
	   "(wildcards not allowed in directory name)", dirname, arg1);
    else
      warn('F', TRUE, "Cannot open directory %s: ignoring logfiles %s",
	   dirname, arg1);
    return;
  }

  dirlen = strlen(dirname);
  while ((filep = readdir(dirp)) != NULL) {
    if (MATCHES(filep->d_name, filename)) {
      if (strlen(filep->d_name) > len) {
	len = strlen(filep->d_name);  /* d_namlen is not portable */
	dirname = (char *)xrealloc((void *)dirname, dirlen + len + 1);
      }
      memcpy((void *)(dirname + dirlen), (void *)(filep->d_name),
		   len + 1);
      stat(dirname, &buf);  /* dirname now contains complete filename */
      if (S_ISREG(buf.st_mode)) {
	configlogfile(opt, "LOGFILE", dirname, arg2, rc);
	matched = TRUE;
      }
    }
  }
  closedir(dirp);
  if (!matched)
    warn('F', TRUE, "Cannot find logfile %s: ignoring it", arg1);
  free((void *)dirname);
}

#else    /* WIN32DIRENT */
void configwildlogs(void *opt, char *arg1, char *arg2, int rc) {
  struct _stat buf;
  char *dirname, *c;
  struct _finddata_t file;
  long code;
  int rc2;
  size_t dirlen, len = 0;
  logical matched = FALSE;

  if ((code = _findfirst(arg1, &file)) != (long)(-1)) {
    if ((c = strrchr(arg1, DIRSEP)) == NULL) {
      dirname = (char *)xmalloc(1);
      dirname[0] = '\0';
    }
    else {
      dirname = (char *)xmalloc((size_t)(c - arg1) + 2);
      memcpy((void *)dirname, (void *)arg1, (size_t)(c - arg1) + 1);
      dirname[(c - arg1) + 1] = '\0';
    }
    dirlen = strlen(dirname);
    for (rc2 = 0; rc2 == 0; rc2 = _findnext(code, &file)) {
      if (strlen(file.name) > len) {
	len = strlen(file.name);
	dirname = (char *)xrealloc((void *)dirname, dirlen + len + 1);
      }
      memcpy((void *)(dirname + dirlen), (void *)(file.name), len + 1);
      _stat(dirname, &buf);  /* dirname now contains complete filename */
      if (_S_IFREG & buf.st_mode) {  /* bitwise & */
	configlogfile(opt, "LOGFILE", dirname, arg2, rc);
	matched = TRUE;
      }
    }
    _findclose(code);
    free((void *)dirname);
  }
  if (!matched)
    warn('F', TRUE, "Cannot find logfile %s: ignoring it", arg1);
}
#endif
#else    /* VMSDIRENT */
/* This function is due to Dave Jones */
void configwildlogs(void *opt, char *arg1, char *arg2, int rc) {
  static char fspec[VMS_FSPEC_MAX], related[VMS_FSPEC_MAX];
  static char result[VMS_FSPEC_MAX];
  static $DESCRIPTOR(fspec_dx,fspec);
  static $DESCRIPTOR(related_dx,"");
  static $DESCRIPTOR(default_dx,".log");
  static $DESCRIPTOR(result_dx,result);
  char *space, *ques;
  long int context;
  int status, stsval, length, LIB$FIND_FILE(), LIB$FIND_FILE_END();
  logical matched;
  
  length = strlen (arg1);
  if ( length >= VMS_FSPEC_MAX ) length = VMS_FSPEC_MAX - 1;
  strncpy ( fspec, arg1, length ); fspec[length] = '\0';
  while ( ques = strchr(fspec,'?') ) *ques = '%';
  fspec_dx.dsc$w_length = length;
  for ( matched = FALSE, context = 0; 
	1&(status=LIB$FIND_FILE ( &fspec_dx, &result_dx, &context, &default_dx,
				  &related_dx, &stsval, (long *) 0 )); 
	matched = TRUE) {
    space = strchr ( result, ' ' );
    if ( !space ) space = &result[VMS_FSPEC_MAX-1];
    *space = '\0';
    configlogfile ( opt, "LOGFILE", result, arg2, rc );
    /* Save last result to use as default for next lookup */
    strcpy ( related, result );
    related_dx.dsc$w_length = strlen(result);
    related_dx.dsc$a_pointer = related;
  }
  if ( context ) LIB$FIND_FILE_END ( &context );
  if (!matched)
    warn('F', TRUE, "Cannot find logfile %s: ignoring it", arg1);
}
#endif
#endif

void configlogfile(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  extern Inputformatlist *logformat;
  extern logical newloglist;
  extern int tz;

  Filelist **logfile = (Filelist **)opt;
  Filelist *lf;
  char *s, *t;
  int i;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 2)
    longwarn(cmd, arg1, arg2, rc);

  if (strchr(arg1, ',') != NULL) {
    s = (char *)xmalloc(strlen(arg1) + 1);
    strcpy(s, arg1);  /* can't strtok arg1 directly in case it's const */
    if ((t = strtok(s, ",")) == NULL) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      free((void *)s);
      return;
    }
    for ( ; t != NULL; t = strtok((char *)NULL, ","))
      configlogfile(opt, "LOGFILE", t, arg2, rc);
    free((void *)s);
    return;
  }
#ifndef NODIRENT
  else if (strchr(arg1, '*') || strchr(arg1, '?')) {
    configwildlogs(opt, arg1, arg2, rc);
    return;
  }
#endif
  else if (strcaseeq(arg1, "NONE")) {
    configlogfmt((void *)&logformat, "LOGFORMAT", "DEFAULT", NULL, -1);
    *logfile = NULL;
    return;
  }
  else if (*logfile == NULL || newloglist == TRUE) {
    *logfile = (Filelist *)xmalloc(sizeof(Filelist));
    lf = *logfile;
    newloglist = FALSE;
  }
  else {
    for (lf = *logfile; lf->next != NULL; TO_NEXT(lf))
      ;
    lf->next = (Filelist *)xmalloc(sizeof(Filelist));
    TO_NEXT(lf);
  }
  DEFAULTSTR(lf->name, arg1);
  lf->format = logformat;
  lf->from = LAST_TIME;
  lf->to = FIRST_TIME;
  for (i = 0; i < LOGDATA_NUMBER; i++)
    lf->data[i] = 0;
  lf->bytes = 0;
  lf->bytes7 = 0;
  lf->tz = tz;
  if (rc >= 2) {
    DEFAULTSTR(lf->prefix, arg2);
    lf->prefixlen = strlen(lf->prefix);
    if (strstr(arg2, "%v") == NULL)
      lf->pvpos = UNSET;
    else
      lf->pvpos = strstr(arg2, "%v") - arg2;
  }
  else
    lf->prefix = NULL;
  lf->ispipe = FALSE;
  lf->next = NULL;
  logformat->used = TRUE;
}

void configcachefile(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  /* a wrapper to configlogfile(), but we have to catch a few cases first */
  Filelist **cachefile = (Filelist **)opt;
  extern Inputformatlist *logformat;
  extern logical newloglist;
  logical lfu, nll;

  if (rc > 1) {
    longwarn(cmd, arg1, arg2, rc);
    rc = 1;
  }
  if (strcaseeq(arg1, "NONE")) {
    *cachefile = NULL;
    return;
  }
  lfu = logformat->used;
  nll = newloglist;
  newloglist = FALSE;
  configlogfile(opt, cmd, arg1, arg2, rc);
  logformat->used = lfu;
  newloglist = nll;
}

void configchar(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *c = (char *)opt;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  if (strcaseeq(arg1, "none"))
    *c = '\0';
  else {
    if (arg1[1] != '\0')
      longwarn(cmd, arg1, arg2, rc);
    *c = arg1[0];
  }
}

void configbarstyle(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *g = (char *)opt;
  char h;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1 || arg1[1] != '\0')
    longwarn(cmd, arg1, arg2, rc);
  h = TOLOWER(arg1[0]);
  if (h != 'a' && h != 'b' && h != 'c' && h != 'd' && h != 'e' && h != 'f' &&
      h != 'g' && h != 'h')
    badwarn(cmd, TRUE, arg1, arg2, rc);
  else
    *g = h;
}

void configgraph(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *g = (char *)opt;  /* see also configallgraph() below */

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1 || arg1[1] != '\0')
    longwarn(cmd, arg1, arg2, rc);
  if (arg1[0] != 'R' && arg1[0] != 'P' && arg1[0] != 'B' && arg1[0] != 'r' &&
      arg1[0] != 'p' && arg1[0] != 'b')
    badwarn(cmd, TRUE, arg1, arg2, rc);
  else
    *g = arg1[0];
}

void configallgraph(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *g = (char *)opt;   /* same as configgraph() */
  int i;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1 || arg1[1] != '\0')
    longwarn(cmd, arg1, arg2, rc);
  if (arg1[0] != 'R' && arg1[0] != 'P' && arg1[0] != 'B' && arg1[0] != 'r' &&
      arg1[0] != 'p' && arg1[0] != 'b')
    badwarn(cmd, TRUE, arg1, arg2, rc);
  else {
    for (i = 0; i < DATEREP_NUMBER; i++)
      g[i] = arg1[0];
  }
}

void configfloor(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  /* This function parses the following formats for floor
     -100(r|p|b|d)           // top 100
     1000(r|p|b)             // at least 1000
     9.5(k|M|G|T|P|E|Z|Y)b   // ditto
     0.5%(r|p|B)             // at least 0.5% of total
     0.5:(r|p|B)             // at least 0.5% of largest
     970701d                 // last access since
     -00-0201d               // same with a relative date
     Upper case letters for r|p|b|d are also acceptable.       */
  extern time_t starttime;
  extern char *byteprefix;

  Floor *floor = (Floor *)opt;
  char **b, *c, d;
  choice floorby;
  char qual = '\0';
  double mn;
  timecode_t t;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);
  c = arg1 + strlen(arg1) - 1;
  d = TOUPPER(*c);
  if (d == 'R')
    floorby = REQUESTS;
  else if (d == 'P')
    floorby = PAGES;
  else if (d == 'B')
    floorby = BYTES;
  else if (d == 'D')
    floorby = DATESORT;
  else {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  if (floorby != DATESORT) {
    b = (char **)xmalloc(sizeof(char *));
    mn = strtod(arg1, b);
    if (*b == arg1) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    else if (*b == c - 1) {
      if (mn < 0) {
	badwarn(cmd, TRUE, arg1, arg2, rc);
	return;
      }
      else if (**b == '%' || **b == ':' ||
	       (floorby == BYTES && strchr(byteprefix + 1, **b)))
	qual = **b;
      else {
	badwarn(cmd, TRUE, arg1, arg2, rc);
	return;
      }
    }
    free((void *)b);
  }
  else { /* floorby == DATESORT */
    if (arg1[0] == '-' && arg1[1] != '0' && ISDIGIT(arg1[1]))
      mn = atof(arg1);
    else if (parsedate(starttime, arg1, &t, TRUE, FALSE) == ERR) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    else
      mn = (double)t;
  }
  floor->min = mn;
  floor->qual = qual;
  floor->floorby = floorby;
}

void configtree(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Tree **treex = (Tree **)opt;
  Hashindex item;
  Hashentry own;
  char *name, *nameend, *t;
  int i;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strchr(arg1, ',') != NULL) {
    if ((t = strtok(arg1, ",")) == NULL) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    for ( ; t != NULL; t = strtok((char *)NULL, ","))
      configtree(opt, cmd, t, NULL, -1);
    return;
  }
  for (i = 0; i < DATA_NUMBER; i++)
    own.data[i] = 0;    /* set up dummy item */
  own.bytes = 0;
  item.own = &own;
  item.name = arg1;
  name = NULL;
  (*treex)->cutfn(&name, &nameend, item.name, TRUE);
  (void)treefind(name, nameend, &((*treex)->tree), &item, (*treex)->cutfn,
		 TRUE, TRUE, FALSE, (*treex)->space);
}

void configulong(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  unsigned int *x = (unsigned int *)opt;
  char **c;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (!ISDIGIT(arg1[0])) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  c = (char **)xmalloc(sizeof(char *));
  *x = strtoul(arg1, c, 10);
  if (rc > 1 || **c != '\0')
    longwarn(cmd, arg1, arg2, rc);
  free((void *)c);
}

void configuint(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  unsigned int *x = (unsigned int *)opt;
  char **c;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (!ISDIGIT(arg1[0])) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  c = (char **)xmalloc(sizeof(char *));
  *x = (unsigned int)strtoul(arg1, c, 10);
  if (rc > 1 || **c != '\0')
    longwarn(cmd, arg1, arg2, rc);
  free((void *)c);
}

void configoffset(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  /* configint with extra checks */
  int *x = (int *)opt;
  char **c;
  int y;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (arg1[0] == '+') {
    if (!ISDIGIT(arg1[1])) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    arg1++;
  }
  else if (!ISDIGIT(arg1[0]) && !(arg1[0] == '-' && ISDIGIT(arg1[1]))) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  c = (char **)xmalloc(sizeof(char *));
  y = (int)strtol(arg1, c, 10);
  if (rc > 1 || **c != '\0')
    longwarn(cmd, arg1, arg2, rc);
  if (y > 40320 || y < -40320)
    warn('C', TRUE, "Ignoring offset of more than 28 days in configuration "
	 "command\n%s %s", cmd, arg1);
  else {
    *x = y;
    if (*x % 30 != 0)
      warn('D', TRUE,
	   "Offset not a multiple of 30 in configuration command\n%s %s",
	   cmd, arg1);
    else if (*x > 1500 || *x < -1500)
      warn('D', TRUE,
	   "Offset more than 25 hours in configuration command\n%s %s",
	   cmd, arg1);
  }
  free((void *)c);
}

void configlowmem(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  choice *x = (choice *)opt;

  if (rc == 0 || IS_EMPTY_STRING(arg1)) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (!(arg1[0] >= '0' && arg1[0] <= '3')) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  if (rc > 1 || arg1[1] != '\0')
    longwarn(cmd, arg1, arg2, rc);
  *x = (choice)(arg1[0] - '0');
}

int aliastocount(char *s) {
  int n = (strchr(s, '*') != NULL);
  while ((s = strchr(s, '$')) != NULL) {
    s++;
    if ((*s < '1' || *s > '9') && *s != '$')
      return(ERR);
    else if (*s == '$')
      s++;
    else
      n = MAX(n, (*s - '0'));
  }
  return(n);
}

AliasTo *configaliasto(char *s, logical is_regex) {
  /* already checked parseable in aliastocount() */
  /* Can prob be optimised somewhat, but with loss of any remaining clarity. */
  AliasTo *ans, *atp;
  char *t, next;

  ans = (AliasTo *)xmalloc(sizeof(AliasTo));
  atp = ans;
  while (TRUE) {
    t = strpbrk(s, "*$");
    while (t != NULL && *t == '$' && *(t + 1) == '$')  /* ignore "$$" */
      t = strpbrk(t + 2, "*$");
    if (t == NULL)
      t = strchr(s, '\0');
    atp->string = (char *)xmalloc(t - s + 1);
    memcpy(atp->string, s, t - s);
    atp->string[t - s] = '\0';
    for (s = atp->string; (s = strstr(s, "$$")) != NULL; s++)
      memmove(s + 1, s + 2, strlen(s + 1));    /* "$$"->"$"   (reuse s) */
    if (*t == '\0')
      atp->after = -1;
    else {
      if (*t == '*')
	next = '1';
      else  /* *t == '$' */
	next = *(++t);
      atp->after = 2 * ((int)next - (int)(is_regex?'0':'1'));
      /* internal index of *'s is from 0; of regex brackets is from 1 */
    }
    if (*t == '\0' || *(t + 1) == '\0') {
      atp->next = NULL;
      return(ans);
    }
    else {
      atp->next = (AliasTo *)xmalloc(sizeof(AliasTo));
      TO_NEXT(atp);
    }
    s = t + 1;  /* set s to rest of original string */
  }
}

void configalias(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Alias **alias = (Alias **)opt;
  Alias *ap;
  unsigned int leftstars;
  int maxrightstar;
  char starchar;
  logical is_regex = FALSE;
  pcre *pattern = NULL;
  char *errstr;
  int erroffset;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc == 1) {
    if (strcaseeq(arg1, "none"))
      *alias = NULL;
    else
      shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 2)
    longwarn(cmd, arg1, arg2, rc);

  if (headcasematch(arg1, "REGEXP:") || headcasematch(arg1, "REGEXPI:")) {
    if ((pattern =
	 pcre_compile(arg1 + 7 + (arg1[6] != ':'),
		      PCRE_DOTALL | ((arg1[6] == ':')?0:PCRE_CASELESS),
		      (const char **)(&errstr), &erroffset, NULL)) == NULL) {
      badwarn(cmd, FALSE, arg1, arg2, rc);
      warn('C', CONTINUATION, "  (%s in regular expression)", errstr);
      return;
    }
    starchar = '(';
    pcre_fullinfo(pattern, NULL, PCRE_INFO_CAPTURECOUNT, (void *)&leftstars);
    is_regex = TRUE;
  }
  else {
    starchar = '*';
    leftstars = chrdistn(arg1, starchar);
  }
  if (leftstars >= 2 && strchr(arg2, '*') != NULL) {
    badwarn(cmd, FALSE, arg1, arg2, rc);
    warn('C', CONTINUATION,
	 "  (Can't have * on RHS with two %c's on LHS: use $1, $2 etc.)",
	 starchar);
    return;
  }
  if ((maxrightstar = aliastocount(arg2)) == ERR) {
    badwarn(cmd, FALSE, arg1, arg2, rc);
    warn('C', CONTINUATION, "  (Can't parse $ substitutions on RHS)");
    return;
  }
  if (maxrightstar > (int)leftstars) {
    badwarn(cmd, FALSE, arg1, arg2, rc);
    warn('C', CONTINUATION, "  (More substitutions on RHS than %c's on LHS)",
	 starchar);
    return;
  }

  /* add new one to front for speed; swap round in reversealias() later */
  ap = (Alias *)xmalloc(sizeof(Alias));
  DEFAULTSTR(ap->from, arg1);   /* save string even for regex: might l.c. */
  ap->to = configaliasto(arg2, is_regex);
  ap->isregex = is_regex;
  if (is_regex)
    ap->pattern = pattern;
  ap->next = *alias;
  *alias = ap;
}

void configstrpair(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Strpair **pair = (Strpair **)opt;
  Strpair *ap;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc == 1) {
    if (strcaseeq(arg1, "none"))
      *pair = NULL;
    else
      shortwarn(cmd, arg1, rc);
    return;
  }
  if (chrn(arg1, '*') >= 2 && strchr(arg2, '*') != NULL) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  if (rc > 2)
    longwarn(cmd, arg1, arg2, rc);

  if (*pair == NULL) {
    ap = (Strpair *)xmalloc(sizeof(Strpair));
    *pair = ap;
  }
  else {
    for (ap = *pair; ap->next != NULL; TO_NEXT(ap))
      ;  /* find end of list */
    ap->next = (Strpair *)xmalloc(sizeof(Strpair));
    TO_NEXT(ap);
  }
  DEFAULTSTR(ap->name, arg1);
  DEFAULTSTR(ap->data, arg2);
  ap->next = NULL;
}

void configstrpairlist(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  char *t;

  if ((t = strtok(arg1, ",")) == NULL) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  for ( ; t != NULL; t = strtok((char *)NULL, ","))
    configstrpair(opt, cmd, t, arg2, rc);
  return;
}

void configstrpair2list(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  /* comma-separated list in arg2 instead of arg1 */
  char *t;

  if ((t = strtok(arg2, ",")) == NULL) {
    badwarn(cmd, TRUE, arg1, arg2, rc);
    return;
  }
  for ( ; t != NULL; t = strtok((char *)NULL, ","))
    configstrpair(opt, cmd, arg1, t, rc);
  return;
}

void configdomlevel(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  Strpair ***levels = (Strpair ***)opt;
  unsigned int c;

  c = 26 * ((int)(*arg1 - 'a'));
  if (*arg1 != '\0')
    c += (int)(*(arg1 + 1) - 'a');
  if (c >= DOMLEVEL_NUMBER)  /* this shouldn't happen */
    c = DOMLEVEL_NUMBER - 1;
  configstrpair((void *)(&((*levels)[c])), cmd, arg1, arg2, rc);
}

void configinex(void *opt, char *cmd, char *arg1, char *arg2, int rc,
		logical in) {
  Include **include = (Include **)opt;
  Include *ip;
  char *errstr;
  int erroffset;
  char *t;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strchr(arg1, ',') != NULL && !headcasematch(arg1, "REGEXP:") &&
      !headcasematch(arg1, "REGEXPI:")) {
    /* This still allows a REGEXP in a comma-separated list if it's not the
       first item and doesn't contain a comma. The docs don't admit this. :) */
    if ((t = strtok(arg1, ",")) == NULL) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    for ( ; t != NULL; t = strtok((char *)NULL, ","))
      configinex(opt, cmd, t, NULL, -1, in);
    return;
  }

  /* NB Put new include on front of list (so will test it earlier) */
  ip = *include;
  *include = (Include *)xmalloc(sizeof(Include));
  DEFAULTSTR((*include)->name, arg1);
  (*include)->next = ip;   /* save name even for regex: might l.c. */
  if (headcasematch(arg1, "REGEXP:") || headcasematch(arg1, "REGEXPI:")) {
    if (((*include)->pattern =
	 pcre_compile(arg1 + 7 + (arg1[6] != ':'), 
		      PCRE_DOTALL | ((arg1[6] == ':')?0:PCRE_CASELESS),
		      (const char **)(&errstr), &erroffset, NULL)) == NULL) {
      *include = ip;
      badwarn(cmd, FALSE, arg1, arg2, rc);
      warn('C', CONTINUATION, "  (%s in regular expression)", errstr);
      return;
    }
    (*include)->type = in?(REGEX_INC):(REGEX_EXC);
  }
  else {
    if (strcaseeq((*include)->name, "pages"))
      strcpy((*include)->name, "pages");
    (*include)->type = in?(NORMAL_INC):(NORMAL_EXC);
  }
}

void configinc(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  configinex(opt, cmd, arg1, arg2, rc, TRUE);
}

void configexc(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  configinex(opt, cmd, arg1, arg2, rc, FALSE);
}

/* same, omitting initial dot */
void configincd(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  if (arg1 != NULL && arg1[0] == '.')
    configinex(opt, cmd, arg1 + 1, arg2, rc, TRUE);
  else
    configinex(opt, cmd, arg1, arg2, rc, TRUE);
}

void configexcd(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  if (arg1 != NULL && arg1[0] == '.')
    configinex(opt, cmd, arg1 + 1, arg2, rc, FALSE);
  else
    configinex(opt, cmd, arg1, arg2, rc, FALSE);
}

/* same, omitting trailing slash */
void configincs(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  if (arg1 != NULL && !IS_EMPTY_STRING(arg1) && arg1[strlen(arg1) - 1] == '/')
    arg1[strlen(arg1) - 1] = '\0';
  configinex(opt, cmd, arg1, arg2, rc, TRUE);
}

void configexcs(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  if (arg1 != NULL && !IS_EMPTY_STRING(arg1) && arg1[strlen(arg1) - 1] == '/')
    arg1[strlen(arg1) - 1] = '\0';
  configinex(opt, cmd, arg1, arg2, rc, FALSE);
}

/* for status codes */
void configscinex(void *opt, char *cmd, char *arg1, char *arg2, int rc,
		  logical in) {
  choice *code2type = (choice *)opt;
  unsigned int bottom, top, i;
  char *c, *d;
  logical f = FALSE;

  if (rc == 0) {
    shortwarn(cmd, arg1, rc);
    return;
  }
  if (rc > 1)
    longwarn(cmd, arg1, arg2, rc);

  if (strchr(arg1, ',') != NULL) {
    if ((c = strtok(arg1, ",")) == NULL) {
      badwarn(cmd, TRUE, arg1, arg2, rc);
      return;
    }
    for ( ; c != NULL; c = strtok((char *)NULL, ","))
      configscinex(opt, cmd, c, NULL, 1, in);
    return;
  }

  if (STREQ(arg1, "*")) {
    bottom = MIN_SC;
    top = SC_NUMBER - 1;
  }
  else {
    if (*arg1 == '-') {
      bottom = MIN_SC;
      c = arg1 + 1;
      if (!ISDIGIT(*c)) {
	badwarn(cmd, TRUE, arg1, arg2, rc);
	return;
      }
    }
    else {
      bottom = (unsigned int)strtoul(arg1, &c, 10);
      if (*c == '-') {
	c++;
	f = TRUE;
      }
      if (bottom < MIN_SC || bottom >= SC_NUMBER ||
	  (*c != '\0' && !ISDIGIT(*c))) {
	badwarn(cmd, TRUE, arg1, arg2, rc);
	return;
      }
    }
    if (*c == '\0') {
      if (f)
	top = SC_NUMBER - 1;
      else
	top = bottom;
    }
    else {
      top = (unsigned int)strtoul(c, &d, 10);
      if (top < bottom || top >= SC_NUMBER || *d != '\0') {
	badwarn(cmd, TRUE, arg1, arg2, rc);
	return;
      }
    }
  }

  if (code2type[0] == UNSET)
    code2type[0] = in?SUCCESS:UNWANTED; /* mark first in/ex: see finalinit() */
  for (i = bottom; i <= top; i++)
    code2type[i] = in?SUCCESS:UNWANTED; /* SUCCESS set more carefully there */
}

void configscinc(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  configscinex(opt, cmd, arg1, arg2, rc, TRUE);
}

void configscexc(void *opt, char *cmd, char *arg1, char *arg2, int rc) {
  configscinex(opt, cmd, arg1, arg2, rc, FALSE);
}
