/***             analog 4.14             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.   ***/

/*** init.c; initialisation routines ***/
/* See also init2.c and globals.c */

#include "anlghea3.h"

void initialise(int argc, char *argv[], Options *op) {
#ifdef MAC_EVENTS
  MacInit(&argc, &argv);
#endif
#ifdef WIN32
  Win32Init();
#endif
  globals(argv[0]);
  defaults(op);
  settings(op, argc, argv);
  correct(op);
  finalinit(op);
}

void confline(Options *op, char *cmd, char *arg1, char *arg2, int rc) {
  extern Options opts;
  extern Configfns cf[];
  extern char *pos;

  char u[520];   /* see nextconfline(); why this high though? */
  char *savepos;
  int i;
  logical done = FALSE;

  strtoupper(cmd);
  if (rc >= 2 && STREQ(cmd, "SUBDOMAIN")) {   /* ugly but easy */
    sprintf(u, "%s (%s)", arg1, arg2);
    if (strchr(u, '$') != NULL || strchr(u, '*') != NULL)
      warn('C', TRUE, "Can't have $ or * in argument to SUBDOMAIN");
    else
      configalias((void *)&(op->outopts.aliashead[G(REP_DOM)]), "SUBDOMAIN",
		  arg1, u, -1);
    rc--;  /* to avoid error message */
  }
  for (i = 0; cf[i].fn != NULL && !done; i++) {
    if (STREQ(cmd, cf[i].name)) {
      memcpy((void *)&opts, (void *)op, sizeof(Options));
      cf[i].fn(cf[i].opt, cmd, arg1, arg2, rc);
      memcpy((void *)op, (void *)&opts, sizeof(Options));
      if (cf[i].fn == &configcall && rc != '\0' && !IS_EMPTY_STRING(arg1)) {
	savepos = pos;
	strcpy(u, arg1); /* or name will get obliterated before fclose */
	(void)config(u, op);
	pos = savepos;
      }
      done = TRUE;
    }
  }
  if (!done)
    unknownwarn(cmd, arg1, arg2);
}

choice config(char *filename, Options *op) {
  extern Inputformatlist *logformat;
  extern char *pos;

  static int no_confs = 0;
  FILE *f;
  char *cmd, *arg1, *arg2;
  int rc;

  if ((f = my_fopen(filename, "configuration file")) == NULL)
    return(ERR);
  if (no_confs++ >= MAX_CONFIGS)
    error("Attempted to read more than %d configuration files", MAX_CONFIGS);
  configstrlist((void *)&(op->conffilelist), "", filename, NULL, -1);
  pos = NULL;
  while ((rc = nextconfline(f, &cmd, &arg1, &arg2)) != EOF)
    confline(op, cmd, arg1, arg2, rc);
  (void)my_fclose(f, filename, "configuration file");
  if (!(logformat->used))
    warn('D', TRUE,
	 "LOGFORMAT in configuration file %s with no subsequent LOGFILE",
	 filename);
  configlogfmt((void *)&logformat, "LOGFORMAT", "DEFAULT", NULL, -1);
  return(OK);
}

void settings(Options *op, int argc, char *argv[]) {
  extern Inputformatlist *logformat;
  extern logical newloglist;

  int i;
  logical done;

  newloglist = TRUE;
  /* once through command line arguments to see if we just want help */
  for (i = 0; i < argc; i++) {
    if (STREQ(argv[i], "-help") || STREQ(argv[i], "-version") ||
	STREQ(argv[i], "--help") || STREQ(argv[i], "--version")) {
      fprintf(stderr, "This is analog version %s\n", VERSION);
      fprintf(stderr, "For help see docs/Readme.html, or %s\n", ANALOGURL);
      my_exit(EXIT_SUCCESS);
    }
  }

  /* once through command line arguments to see if default config wanted */
  if (!strcaseeq(DEFAULTCONFIGFILE, "none")) {
    for (i = argc - 1, done = FALSE; i >= 1 && !done; i--) {
      if (!IS_EMPTY_STRING(argv[i]) && argv[i][1] == 'G' &&
	  (argv[i][0] == '+' || argv[i][0] == '-')) {
	done = TRUE;
	CLLONGCHECK(if (argv[i][0] == '+') (void)config(DEFAULTCONFIGFILE, op);)
      }
    }
    if (!done)
      (void)config(DEFAULTCONFIGFILE, op);
  }

  /* now read in rest of command line arguments */
  newloglist = TRUE;
  clargs(op, argc, argv);
  if (!(logformat->used))
    warn('D', TRUE, "LOGFORMAT on command line with no subsequent logfile");
  configlogfmt((void *)&logformat, "LOGFORMAT", "DEFAULT", NULL, -1);
  newloglist = TRUE;

  /* finally, read in mandatory config file, aborting if not found */
  if (!strcaseeq(MANDATORYCONFIGFILE, "none") &&
      config(MANDATORYCONFIGFILE, op) == ERR)
    error("Cannot ignore mandatory configuration file");
}

Inputformatlist *correctlogfmt(Filelist *lp, Include **wanthead,
			       choice *code2type, Dateman *dman) {
  extern choice wantitem[];
  extern Inputfns inpfns[], pjinpfn;
  extern char *item_type[];

  Inputformatlist *fmt;
  Inputformat *fns;
  logical noitem[ITEM_NUMBER], nodates, nocodes, nonames, nobytes;
  int count, i, j;

  for (count = 0, fmt = lp->format; fmt != NULL; TO_NEXT(fmt)) {
    if (!wantitem[INP_CODE] && fmt->count[INP_CODE] == 2)
      fmt->count[INP_CODE] = 1;
    if (!wantitem[INP_DATE] && fmt->count[INP_DATE] == 2)
      fmt->count[INP_DATE] = 1;  /* NB read date even if not filtered */
    for (fns = fmt->form; fns->inpfns != NULL; TO_NEXT(fns)) {
      for (i = 0; i < ITEMFNS_NUMBER; i++) {
	if (fns->inpfns == &inpfns[i]) {
	  j = inpfns[i].type;
	  if (!wantitem[j] && fmt->count[j] == 2 &&
	      !(j == ITEM_FILE && (fmt->count[INP_DATE] == 2 ||
				   fmt->count[INP_CODE] > 0 ||
				   fmt->count[INP_BYTES] > 0 ||
				   fmt->count[INP_PROCTIME] > 0)))
	    fmt->count[j] = 1;
	  if (wanthead[j] == NULL && fmt->count[j] == 1 && j != ITEM_FILE &&
	      !(j == ITEM_VHOST && lp->pvpos != UNSET)) {
	    if (inpfns[i].fn != &parseref)
	      fns->inpfns = &pjinpfn;
	    fmt->count[j] = 0;
	  }
	  count += (int)(fmt->count[j] == 2);
	}
      }     /* end for i through ITEMFNS */
      if (fns->inpfns->fn == &parselogfmt) {
	count++;
	for (i = 0; i < INPUT_NUMBER; i++)
	  fmt->count[i] = 1;   /* just so doesn't trigger warnings below */
      }
    }       /* end for fns in fmt */
  }         /* end for fmt through formats */

  if (count == 0)
    lp->format = NULL;  /* i.e., mark logfile to be ignored */
  else {
    /*  logical noitem[ITEM_NUMBER], nodates, nocodes, nonames, nobytes;*/
    for (i = 0; i < ITEM_NUMBER; i++)
      noitem[i] = FALSE;
    nodates = FALSE;
    nocodes = FALSE;
    nonames = FALSE;
    nobytes = FALSE;
    for (fmt = lp->format; fmt != NULL; TO_NEXT(fmt)) {
      for (count = 0, j = 0; j < INPUT_NUMBER; j++)
	count += (int)(fmt->count[j] == 2);
      if (count > 0) {
	for (i = 0; i < ITEM_NUMBER; i++) {
	  if (fmt->count[i] == 0)
	    noitem[i] = TRUE;
	}
	if (fmt->count[INP_DATE] == 0)
	  nodates = TRUE;
	if (fmt->count[INP_CODE] == 0)
	  nocodes = TRUE;
	if (fmt->count[ITEM_FILE] == 0)
	  nonames = TRUE;
	if (fmt->count[INP_BYTES] == 0)
	  nobytes = TRUE;
      }
    }
    for (i = 0; i < ITEM_NUMBER; i++) {
      if (wanthead[i] != NULL && noitem[i])
	warn('M', TRUE, "Logfile %s contains lines with no %s, which are "
	     "being filtered", lp->name, item_type[i]);
    }
    if (code2type[0] != UNSET && nocodes)
      warn('M', TRUE, "Logfile %s contains lines with no status codes, "
	   "which are being filtered", lp->name);
    if ((dman->from > FIRST_TIME || dman->to < LAST_TIME) && nodates)
      warn('M', TRUE, "Logfile %s contains lines with no dates, which are "
	   "being filtered", lp->name);
    if (nonames)
      warn('M', TRUE, "Logfile %s contains lines with no file names: "
	   "page counts may be low", lp->name);
    if (nobytes)
      warn('M', TRUE, "Logfile %s contains lines with no bytes: byte counts "
	   "may be low", lp->name);
  }
  return(lp->format);
}

void correct(Options *op) {
  extern time_t origstarttime, starttime;
  extern timecode_t starttimec;
  extern int stz;
  extern Inputformatlist *deflogformat;
  extern char *repname[], *methodname[];
  extern unsigned int *rep2gran;
  extern choice *rep2type;
  extern choice wantitem[];
  extern logical vblesonly;

  char *sf = "SUBFLOOR";
  char *af = "ARGSFLOOR";
  char *ss = "SUBSORTBY";
  char *as = "ARGSSORTBY";
  Filelist *lp;
  Include *incp, *lastincp;
  choice rep, *cols, floor, subfloor, sortby, subsortby;
  char *subf, *subs;
  logical colsinc[COL_NUMBER], istree, templ;
  char graph;
  int i, j, k;

  /* NB some options, e.g. reportorder, corrected when parsed */
  origstarttime = starttime;
  starttime = shifttime(starttime, stz);
  starttimec += stz;
  if (op->outopts.markchar == '\0') {
    warn('C', TRUE, "MARKCHAR none not allowed. Using + instead.");
    op->outopts.markchar = '+';
  }
  if (op->outopts.decpt == '\0') {
    warn('C', TRUE, "DECPOINT none not allowed. Using '.' instead.");
    op->outopts.decpt = '.';
  }
  op->dman.from = FIRST_TIME;
  if (op->dman.fromstr != NULL &&  /* so parse unless fromstr is NULL */
      parsedate(starttime, op->dman.fromstr, &(op->dman.from), TRUE, FALSE)
      == ERR)
    warn('C', TRUE, "Invalid FROM string %s: ignoring it", op->dman.fromstr);
  op->dman.to = LAST_TIME;
  if (op->dman.tostr != NULL &&
      parsedate(starttime, op->dman.tostr, &(op->dman.to), FALSE, FALSE)
      == ERR)
    warn('C', TRUE, "Invalid TO string %s: ignoring it", op->dman.tostr);
  if (op->dman.from > op->dman.to) {
    warn('C', TRUE, "FROM time is later than TO time: "
	 "would exclude everything so ignoring them");
    op->dman.from = FIRST_TIME;
    op->dman.to = LAST_TIME;
  }
  for (i = 0, lp = op->miscopts.logfile; lp != NULL; TO_NEXT(lp))
    i = MAX(i, lp->tz);
  if (op->dman.from > starttimec + i + 60)  /* one hour's grace */
    warn('D', TRUE, "FROM time is later than the present");
  if (op->dman.to < LAST_TIME) {
    op->dman.last7from = op->dman.to - MINS_IN_WEEK;
    op->dman.last7to = op->dman.to;
  }
  else {
    op->dman.last7from = starttimec - MINS_IN_WEEK;
    op->dman.last7to = starttimec;
  }
  if (op->outopts.outstyle == HTML) {
    if (op->outopts.htmlpagewidth == 0)
      op->outopts.htmlpagewidth = 1;
    else if (op->outopts.htmlpagewidth > MAXPAGEWIDTH) {
      warn('C', TRUE, "HTMLPAGEWIDTH %u too large: using maximum allowed "
	   "value of %u", op->outopts.htmlpagewidth, MAXPAGEWIDTH);
      op->outopts.htmlpagewidth = MAXPAGEWIDTH;
    }
    op->outopts.pagewidth = op->outopts.htmlpagewidth;
  }
  else if (op->outopts.outstyle == ASCII || op->outopts.outstyle == PLAIN) {
    if (op->outopts.plainpagewidth == 0)
      op->outopts.plainpagewidth = 1;
    else if (op->outopts.plainpagewidth > MAXPAGEWIDTH) {
      warn('C', TRUE, "PLAINPAGEWIDTH %u too large: using maximum allowed "
	   "value of %u", op->outopts.plainpagewidth, MAXPAGEWIDTH);
      op->outopts.plainpagewidth = MAXPAGEWIDTH;
    }
    op->outopts.pagewidth = op->outopts.plainpagewidth;
  }
  for (i = 0, j = 0, k = 0; i < DATEREP_NUMBER; i++) {
    if (i != REP_DAYSUM && i != REP_HOURSUM) {
      j += (int)(op->outopts.repq[i] && op->outopts.back[i]);
      k += (int)(op->outopts.repq[i]);
    }
  }
  for (lastincp = NULL, incp = op->argshead, templ = FALSE; incp != NULL;
       TO_NEXT(incp)) {
    if (STREQ((char *)(incp->name), "pages")) {
      if (!templ)
	warn('C', TRUE, "ARGSINCLUDE/EXCLUDE can't include 'pages'");
      templ = TRUE;
      if (lastincp == NULL)
	op->argshead = incp->next;
      else
	lastincp->next = incp->next;
    }
    else
      lastincp = incp;
  }
  for (lastincp = NULL, incp = op->refargshead, templ = FALSE; incp != NULL;
       TO_NEXT(incp)) {
    if (STREQ((char *)(incp->name), "pages")) {
      if (!templ)
	warn('C', TRUE, "REFARGSINCLUDE/EXCLUDE can't include 'pages'");
      templ = TRUE;
      if (lastincp == NULL)
	op->refargshead = incp->next;
      else
	lastincp->next = incp->next;
    }
    else
      lastincp = incp;
  }
  if (j != 0 && j != k)
    warn('D', TRUE, "Time reports have not all got same value of BACK");
  /* The next bit is totally foul, so here's a guide. Numbers are repeated
     in comments below. [C] problems are overridden, [D] are just warned.
    for (i through reports turned on, except GENSUM) {
      if (time report) {                *** (1) ***
        check COLS don't include D, d or N   [C]
        check GRAPH included in COLS         [D]
      }
      else (non-time reports) {         *** (2) ***
        if (REP_REQ || REP_TYPE) {             *** (3) ***
          Check P not in COLS or (sub)SORTBY or (sub)FLOOR   [C]
        }
        else if (REP_REDIR || REP_FAIL || REP_REDIRREF || REP_FAILREF ||
                 REP_FAILUSER || REP_CODE) {   *** (4) ***
          Check P, p, B, b not in COLS or (sub)SORTBY or (sub)FLOOR   [C]
        }
        if (REP_SIZE || REP_PROCTIME) {        *** (5) ***
          Check N not in COLS    [C]
        }
        else {                                 *** (6) ***
          Check SORTBY matches SUBSORTBY   [D]
          Check FLOOR matches SUBFLOOR     [D]
          Check SORTBY matches FLOOR       [D]
          Check no column N if SORTBY ALPHABETICAL or RANDOM   [D]
          Check (sub)SORTBY and (sub)FLOOR in COLS   [D]
        }
      }
      Check COLS non-empty  [D]         *** (7) ***
    }
  */
  for (i = 0; op->outopts.reporder[i] != -1; i++) {
    rep = op->outopts.reporder[i];
    if (rep != REP_GENSUM && op->outopts.repq[rep]) {
      cols = op->outopts.cols[rep];
      for (j = 0; j < COL_NUMBER; j++)
	colsinc[j] = FALSE;
      for (j = 0; cols[j] != COL_NUMBER; j++)
	colsinc[cols[j]] = TRUE;
      if (rep < DATEREP_NUMBER) {   /* *** (1) ***   time reports */
	graph = op->outopts.graph[rep];
	if (colsinc[COL_DATE] || colsinc[COL_TIME] || colsinc[COL_INDEX]) {
	  warn('C', TRUE,
	       "In %s, D, d and N not allowed in COLS: ignoring them",
	       repname[rep]);
	  for (j = 0; cols[j] != COL_NUMBER; j++) {
	    if (cols[j] == COL_DATE || cols[j] == COL_TIME ||
		cols[j] == COL_INDEX) {
	      for (k = j; cols[k] != COL_NUMBER; k++)
		cols[k] = cols[k + 1];
	      j--;
	    }
	  }
	}
	if (cols[0] != COL_NUMBER) {  /* o/wise different warning below */
	  if (((graph == 'R' || graph == 'r') &&
	       !colsinc[COL_REQS] && !colsinc[COL_PREQS]) ||
	      ((graph == 'P' || graph == 'p') &&
	       !colsinc[COL_PAGES] && !colsinc[COL_PPAGES]) ||
	      ((graph == 'B' || graph == 'b') &&
	       !colsinc[COL_BYTES] && !colsinc[COL_PBYTES]))
	    warn('D', TRUE, "In %s, GRAPH (%c) isn't included in COLS",
		 repname[rep], graph);
	}
      }   /* end if time report */
      else {    /* *** (2) *** non-time reports */
	sortby = op->outopts.sortby[G(rep)];
	floor = op->outopts.floor[G(rep)].floorby;
	subsortby = op->outopts.subsortby[G(rep)];
	subfloor = op->outopts.subfloor[G(rep)].floorby;
	if (rep == REP_REQ || rep == REP_FAIL || rep == REP_REDIR ||
	    rep == REP_REF || rep == REP_FAILREF || rep == REP_REDIRREF) {
	  subf = af;      /* *** (3) *** */
	  subs = as;
	}
	else {
	  subf = sf;
	  subs = ss;
	}
	istree = sublevels(op->outopts.tree[G(rep)]->tree);
	/* this (istree) is not perfect, because ARGSINCLUDE isn't analysed
	   so there may not actually be a tree in practice */
	if (rep == REP_REQ || rep == REP_TYPE) {
	  if (colsinc[COL_PAGES]) {
	    warn('C', TRUE, "In %s, P not allowed in COLS: ignoring it",
		 repname[rep]);
	    for (j = 0; cols[j] != COL_NUMBER; j++) {
	      if (cols[j] == COL_PAGES) {
		for (k = j; cols[k] != COL_NUMBER; k++)
		  cols[k] = cols[k + 1];
		j--;
	      }
	    }
	    colsinc[COL_PAGES] = FALSE;
	  }
	  if (sortby == PAGES) {
	    warn('C', TRUE, "In %s, illegal SORTBY (pages): "
		 "will sort by requests instead", repname[rep]);
	    op->outopts.sortby[G(rep)] = REQUESTS;
	    sortby = REQUESTS;
	  }
	  if (floor == PAGES) {
	    warn('C', TRUE,
		 "In %s, illegal FLOOR (pages): will use requests instead",
		 repname[rep]);
	    op->outopts.floor[G(rep)].floorby = REQUESTS;
	    floor = REQUESTS;
	  }
	  if (istree) {
	    if (subsortby == PAGES) {
	      warn('C', TRUE, "In %s, illegal %s (pages): "
		   "will sort by requests instead", repname[rep], subs);
	      op->outopts.subsortby[G(rep)] = REQUESTS;
	      subsortby = REQUESTS;
	    }
	    if (subfloor == PAGES) {
	      warn('C', TRUE, "In %s, illegal %s (pages): will use "
		   "requests instead", repname[rep], subf);
	      op->outopts.subfloor[G(rep)].floorby = REQUESTS;
	      subfloor = REQUESTS;
	    }
	  }
	}    /* end rep == REP_REQ || rep == REP_TYPE */
	else if (rep == REP_REDIR || rep == REP_FAIL || rep == REP_REDIRREF ||
		 rep == REP_FAILREF || rep == REP_FAILUSER ||
		 rep == REP_CODE) {  /* *** (4) *** */
	  if (colsinc[COL_PAGES] || colsinc[COL_PPAGES] ||
	      colsinc[COL_BYTES] || colsinc[COL_PBYTES]) {
	    warn('C', TRUE,
		 "In %s, P, p, B and b not allowed in COLS: ignoring them",
		 repname[rep]);
	    for (j = 0; cols[j] != COL_NUMBER; j++) {
	      if (cols[j] == COL_PAGES || cols[j] == COL_PPAGES ||
		  cols[j] == COL_BYTES || cols[j] == COL_PBYTES) {
		for (k = j; cols[k] != COL_NUMBER; k++)
		  cols[k] = cols[k + 1];
		j--;
	      }
	    }
	    colsinc[COL_PAGES] = FALSE;
	    colsinc[COL_PPAGES] = FALSE;
	    colsinc[COL_BYTES] = FALSE;
	    colsinc[COL_PBYTES] = FALSE;
	  }
	  if (sortby == PAGES || sortby == BYTES) {
	    warn('C', TRUE,
		 "In %s, illegal SORTBY (%s): will sort by requests instead",
		 repname[rep], methodname[sortby]);
	    op->outopts.sortby[G(rep)] = REQUESTS;
	    sortby = REQUESTS;
	  }
	  if (floor == PAGES || floor == BYTES) {
	    warn('C', TRUE, "In %s, illegal FLOOR (%s): will use -50r instead",
		 repname[rep], methodname[floor]);
	    op->outopts.floor[G(rep)].min = -50;
	    op->outopts.floor[G(rep)].qual = '\0';
	    op->outopts.floor[G(rep)].floorby = REQUESTS;
	    floor = REQUESTS;
	  }
	  if (istree) {
	    if (subsortby == PAGES || subsortby == BYTES) {
	      warn('C', TRUE,
		   "In %s, illegal %s (%s): will sort by requests instead",
		   repname[rep], subs, methodname[subsortby]);
	      op->outopts.subsortby[G(rep)] = REQUESTS;
	      subsortby = REQUESTS;
	    }
	    if (subfloor == PAGES || subfloor == BYTES) {
	      warn('C', TRUE, "In %s, illegal %s (%s): will use -1r instead",
		   repname[rep], subf, methodname[subfloor]);
	      op->outopts.subfloor[G(rep)].min = -1;
	      op->outopts.subfloor[G(rep)].qual = '\0';
	      op->outopts.subfloor[G(rep)].floorby = REQUESTS;
	      subfloor = REQUESTS;
	    }
	  }
	}   /* end rep == (list of 6 reps) */
	/* "if" not "else if" is correct (to make next "else if" work) */
	if (rep == REP_SIZE || rep == REP_PROCTIME) {  /* *** (5) *** */
	  if (colsinc[COL_INDEX]) {
	    warn('C', TRUE, "In %s, N not allowed in COLS: ignoring it",
		 repname[rep]);
	    for (j = 0; cols[j] != COL_NUMBER; j++) {
	      if (cols[j] == COL_INDEX) {
		for (k = j; cols[k] != COL_NUMBER; k++)
		  cols[k] = cols[k + 1];
		j--;
	      }
	    }
	  }
	}
	else { /* *** (6) *** rep != REP_SIZE && rep != REP_PROCTIME */
	  /* check SORTBYs (SIZE & PROCTIME don't have SORTBY's) */
	  if (istree) {
	    if (sortby != subsortby && subsortby != ALPHABETICAL &&
		subsortby != RANDOM)
	      warn('D', TRUE, "In %s, SORTBY (%s) doesn't match %s (%s)",
		   repname[rep], methodname[sortby], subs,
		   methodname[subsortby]);
	    if (floor != subfloor)
	      warn('D', TRUE, "In %s, FLOOR (%s) doesn't match %s (%s)",
		   repname[rep], methodname[floor], subf,
		   methodname[subfloor]);
	    if ((subsortby == REQUESTS || subsortby == PAGES ||
		 subsortby == BYTES) &&
		(subfloor == REQUESTS || subfloor == PAGES ||
		 subfloor == BYTES) && subsortby != subfloor)
	      warn('D', TRUE, "In %s, %s (%s) doesn't match %s (%s)",
		   repname[rep], subs, methodname[subsortby], subf,
		   methodname[subfloor]);
	  }
	  if ((sortby == REQUESTS || sortby == PAGES || sortby == BYTES) &&
	      (floor == REQUESTS || floor == PAGES || floor == BYTES) &&
	      sortby != floor)
	    warn('D', TRUE, "In %s, SORTBY (%s) doesn't match FLOOR (%s)",
		 repname[rep], methodname[sortby], methodname[floor]);
	  if (sortby == ALPHABETICAL && colsinc[COL_INDEX])
	    warn('D', TRUE, "In %s, column N with SORTBY ALPHABETICAL",
		 repname[rep]);
	  else if (sortby == RANDOM && colsinc[COL_INDEX])
	    warn('D', TRUE, "In %s, column N with SORTBY RANDOM",
		 repname[rep]);
	  if (cols[0] != COL_NUMBER) {  /* o/wise different warning below */
	    if ((sortby == REQUESTS && !colsinc[COL_REQS] &&
		 !colsinc[COL_PREQS]) ||
		(sortby == PAGES && !colsinc[COL_PAGES] &&
		 !colsinc[COL_PPAGES]) ||
		(sortby == BYTES && !colsinc[COL_BYTES] &&
		 !colsinc[COL_PBYTES]) ||
		(sortby == DATESORT && !colsinc[COL_DATE] &&
		 !colsinc[COL_TIME]))
	      warn('D', TRUE, "In %s, SORTBY (%s) isn't included in COLS",
		   repname[rep], methodname[sortby]);
	    if ((floor == REQUESTS && !colsinc[COL_REQS] &&
		 !colsinc[COL_PREQS]) ||
		(floor == PAGES && !colsinc[COL_PAGES] &&
		 !colsinc[COL_PPAGES]) ||
		(floor == BYTES && !colsinc[COL_BYTES] &&
		 !colsinc[COL_PBYTES]) ||
		(floor == DATESORT && !colsinc[COL_DATE] &&
		 !colsinc[COL_TIME]))
	      warn('D', TRUE, "In %s, FLOOR (%s) isn't included in COLS",
		   repname[rep], methodname[floor]);
	    if (istree) {
	      if ((subsortby == REQUESTS && !colsinc[COL_REQS] &&
		   !colsinc[COL_PREQS]) ||
		  (subsortby == PAGES && !colsinc[COL_PAGES] &&
		   !colsinc[COL_PPAGES]) ||
		  (subsortby == BYTES && !colsinc[COL_BYTES] &&
		   !colsinc[COL_PBYTES]) ||
		  (subsortby == DATESORT && !colsinc[COL_DATE] &&
		   !colsinc[COL_TIME]))
		warn('D', TRUE, "In %s, %s (%s) isn't included in COLS",
		     repname[rep], subs, methodname[subsortby]);
	      if ((subfloor == REQUESTS && !colsinc[COL_REQS] &&
		   !colsinc[COL_PREQS]) ||
		  (subfloor == PAGES && !colsinc[COL_PAGES] &&
		   !colsinc[COL_PPAGES]) ||
		  (subfloor == BYTES && !colsinc[COL_BYTES] &&
		   !colsinc[COL_PBYTES]) ||
		  (subfloor == DATESORT && !colsinc[COL_DATE] &&
		   !colsinc[COL_TIME]))
		warn('D', TRUE, "In %s, %s (%s) isn't included in COLS",
		     repname[rep], subf, methodname[subfloor]);
	    }
	  }
	}
      }
      if (cols[0] == COL_NUMBER)   /* *** (7) *** */
	warn('D', TRUE, "%s contains no COLS", repname[rep]);
    }
  }  /* end for i through reports */
  /* change logformats to ignore items which are not wanted (see 28/10/97) */
  for (lp = op->miscopts.logfile; lp != NULL; TO_NEXT(lp)) {
    if (lp->format->form->inpfns->fn == &parselogfmt &&
	lp->format->form->sep == '0')  /* DEFAULT format */
      lp->format = deflogformat;
  }
  if (!vblesonly) {
    if (!STREQ(op->outopts.cacheoutfile, "none")) {
      if (op->outopts.outstyle != OUT_NONE) {
	if (STREQ(op->outopts.cacheoutfile, op->outopts.outfile) &&
	    !STREQ(op->outopts.outfile, "-") &&
	    !STREQ(op->outopts.outfile, "stdout"))
	  error("OUTFILE and CACHEOUTFILE are the same");
	/* won't catch same file under different names, but cache file opening
	   will still fail later in that case */
	if ((STREQ(op->outopts.outfile, "-") || STREQ(op->outopts.outfile,
							"stdout")) &&
	    (STREQ(op->outopts.cacheoutfile, "-") ||
	     STREQ(op->outopts.cacheoutfile, "stdout")))
	  error("OUTFILE and CACHEOUTFILE both set to stdout");
      }
      for (i = 0; i < INPUT_NUMBER; i++)
	wantitem[i] = TRUE;
      op->miscopts.granularity = rep2gran[REP_FIVE];
      for (i = 0, j = 0; i < ITEM_NUMBER; i++)
	j += (int)(op->miscopts.lowmem[i] >= 3);
      if (j != 0)
	warn('D', TRUE, "LOWMEM 3 prevents that item being cached");
    }
    else if (op->outopts.outstyle == OUT_NONE)
      error("OUTPUT NONE and CACHEOUTFILE none selected");
    else {  /* cachefile == none */
      for (i = 0; i < INPUT_NUMBER; i++)
	wantitem[i] = FALSE;
      for (op->miscopts.granularity = 1, i = 0;
	   op->outopts.reporder[i] != -1; i++) {
	rep = op->outopts.reporder[i];
	if (rep < DATEREP_NUMBER && op->outopts.repq[rep])
	  op->miscopts.granularity = MAX(op->miscopts.granularity,
					 rep2gran[rep]);
	if (op->outopts.repq[rep] && rep2type[rep] != UNSET)
	  wantitem[rep2type[rep]] = TRUE;
      }
    }  /* end cachefile == none */
    for (lp = op->miscopts.logfile, templ = FALSE; lp != NULL; TO_NEXT(lp)) {
      (void)correctlogfmt(lp, op->wanthead, op->code2type, &(op->dman));
      if (op->miscopts.lowmem[ITEM_VHOST] >= 3 && lp->pvpos != UNSET) {
	if (!templ) {
	  warn('C', TRUE,
	       "Ignoring %%v in logfile prefixes because of VHOSTLOWMEM 3");
	  templ = TRUE;
	}
	lp->pvpos = UNSET;
      }
    }
  }
  if (op->outopts.outstyle == COMPUTER) {
    op->outopts.sepchar = '\0';
    op->outopts.repsepchar = '\0';
    op->outopts.decpt = '.';
    op->outopts.rawbytes = TRUE;
  }
  /* lower case appropriate aliases and in/excludes */
  /* NB could send them through all fixed aliases, but others probably
     never needed and could cause confusion */
  toloweralias(op->aliashead[ITEM_HOST], TRUE);
  toloweralias(op->outopts.aliashead[G(REP_HOST)], FALSE);
  toloweralias(op->outopts.aliashead[G(REP_DOM)], FALSE);
  toloweralias(op->outopts.aliashead[G(REP_ORG)], FALSE);
  tolowerinc(op->wanthead[ITEM_HOST]);
  tolowerinc(op->outopts.wanthead[G(REP_HOST)]);
  tolowerinc(op->outopts.wanthead[G(REP_DOM)]);
  tolowerinc(op->outopts.wanthead[G(REP_ORG)]);
  toloweralias(op->aliashead[ITEM_VHOST], TRUE);
  toloweralias(op->outopts.aliashead[G(REP_VHOST)], FALSE);
  tolowerinc(op->wanthead[ITEM_VHOST]);
  tolowerinc(op->outopts.wanthead[G(REP_VHOST)]);
  toloweralias(op->aliashead[ITEM_USER], TRUE);
  toloweralias(op->outopts.aliashead[G(REP_USER)], FALSE);
  toloweralias(op->outopts.aliashead[G(REP_FAILUSER)], FALSE);
  tolowerinc(op->wanthead[ITEM_USER]);
  tolowerinc(op->outopts.wanthead[G(REP_USER)]);
  tolowerinc(op->outopts.wanthead[G(REP_FAILUSER)]);
  tolowerinc(op->outopts.wanthead[G(REP_SEARCHREP)]);
  tolowerinc(op->outopts.wanthead[G(REP_SEARCHSUM)]);
  if (op->miscopts.case_insensitive) {
    toloweralias(op->aliashead[ITEM_FILE], TRUE);
    toloweralias(op->outopts.aliashead[G(REP_REQ)], FALSE);
    toloweralias(op->outopts.aliashead[G(REP_REDIR)], FALSE);
    toloweralias(op->outopts.aliashead[G(REP_FAIL)], FALSE);
    toloweralias(op->outopts.aliashead[G(REP_TYPE)], FALSE);
    toloweralias(op->outopts.aliashead[G(REP_DIR)], FALSE);
    tolowerinc(op->wanthead[ITEM_FILE]);
    tolowerinc(op->ispagehead);  /* this gets refs too, but shame... */
    tolowerinc(op->argshead);
    tolowerinc(op->outopts.wanthead[G(REP_REQ)]);
    tolowerinc(op->outopts.wanthead[G(REP_REDIR)]);
    tolowerinc(op->outopts.wanthead[G(REP_FAIL)]);
    tolowerinc(op->outopts.wanthead[G(REP_TYPE)]);
    tolowerinc(op->outopts.wanthead[G(REP_DIR)]);
  }  
}

#define POSSTREE(r) if (op->miscopts.lowmem[rep2type[r]] >= 3 && \
			op->outopts.repq[r]) \
		       op->outopts.alltrees[i++] = r
#define POSSDERV(r) if (op->miscopts.lowmem[rep2type[r]] >= 3 && \
			op->outopts.repq[r]) \
		       op->outopts.alldervs[i++] = r
void finalinit(Options *op) {
#ifndef NODNS
  extern choice dnslevel;
  extern char *dnsfile, *dnslockfile;
  extern FILE *dnsfilep, *dnslock;
  extern Hashtable *dnstable;
  extern unsigned int dnsgoodhrs, dnsbadhrs;
  timecode_t timec, goodtimec, badtimec;
  char *name, *alias;
  choice rc;
#endif

  extern timecode_t starttimeuxc;
  extern char *country[];
  extern Memman *xmemman;
  extern choice *rep2type;
  extern char *pos;
  extern logical vblesonly;
  extern logical unprintable[256];

  FILE *tempf;
  Strlist *sp;
  int i;

  /* set code2type variable */
  for (i = MIN_SC; i < SC_NUMBER; i++) {
    if (op->code2type[i] == UNWANTED ||
	(op->code2type[i] == UNSET && op->code2type[0] == SUCCESS))
      op->code2type[i] = UNWANTED;
    else if (i <= 199)
      op->code2type[i] = INFO;
    else if (i <= 299 || (i == 304 && op->succ304))
      op->code2type[i] = SUCCESS;
    else if (i <= 399)
      op->code2type[i] = REDIRECT;
    else
      op->code2type[i] = FAILURE;
  }
  /* swap aliases round */
  for (i = 0; i < ITEM_NUMBER; i++)
    reversealias(&(op->aliashead[i]));
  for (i = 0; i < GENREP_NUMBER; i++)
    reversealias(&(op->outopts.aliashead[i]));
  /* read in language information */
  if (op->outopts.lang.file == NULL)
    selectlang(country[op->outopts.lang.code], &(op->outopts));
  if (op->outopts.domainsfile == NULL)
    configstr(&(op->outopts.domainsfile), NULL, LANGDIR"ukdom"EXTSEP"tab",
	      NULL, -1);
  op->outopts.multibyte = FALSE;
  if (!vblesonly) {  /* persists to end of function */
    if (op->outopts.outstyle == PLAIN)
      op->outopts.outstyle = ASCII; /* The only difference is the langfile */
    if ((tempf = my_fopen(op->outopts.lang.file, "language file")) == NULL)
      error("Can't read language file %s", op->outopts.lang.file);
    op->outopts.lngstr = (char **)xmalloc(LNGSTR_NUMBER * sizeof(char *));
    pos = NULL;
    DEFAULTSTR(op->outopts.lngstr[charset_],
	       nextlngstr(tempf, op->outopts.lang.file, TRUE));
    if (*(op->outopts.lngstr[charset_]) == '*') {
      op->outopts.multibyte = TRUE;
      (op->outopts.lngstr[charset_])++;
      op->outopts.html = FALSE;
    }
    for (i = 0; i < 7; i++)
      DEFAULTSTR(op->outopts.dayname[i],
		 nextlngstr(tempf, op->outopts.lang.file, TRUE));
    for (i = 0; i < 12; i++)
      DEFAULTSTR(op->outopts.monthname[i],
		 nextlngstr(tempf, op->outopts.lang.file, TRUE));
    for (i = 1; i < LNGSTR_NUMBER; i++)
      DEFAULTSTR(op->outopts.lngstr[i],
		 nextlngstr(tempf, op->outopts.lang.file, TRUE));
    (void)nextlngstr(tempf, op->outopts.lang.file, FALSE);
    /* This last nextlngstr checks the language file isn't too long */
    op->outopts.plainmonthlen =
      (unsigned int)arraymaxlen(op->outopts.monthname, 12, ASCII);
    /* plain is longer: by pretending to be ASCII it includes whole strlen */
    op->outopts.monthlen =
      (unsigned int)arraymaxlen(op->outopts.monthname, 12,
				op->outopts.outstyle);
    op->outopts.plaindaylen =
      (unsigned int)arraymaxlen(op->outopts.dayname, 7, ASCII);
    op->outopts.daylen = (unsigned int)arraymaxlen(op->outopts.dayname, 7,
						     op->outopts.outstyle);
    /* Set the convfloor (see do_alias(n|N)). We only do this approximately:
       convert A0-FF for ISO-8859-*, 80-FF o/wise. This may still include some
       non-printable characters! But we can't have a table for every charset.*/
    if (!op->outopts.searchconv ||
	strcaseeq(op->outopts.lngstr[charset_], "US-ASCII"))
      op->outopts.convfloor = 0;
    else if (substrcaseeq(op->outopts.lngstr[charset_], "iso-8859-") ||
	     strcaseeq(op->outopts.lngstr[charset_], "armscii-8"))
      op->outopts.convfloor = 0xA0;
    else
      op->outopts.convfloor = 0x80;
    /* Similarly set the characters we shall consider as unprintable in the
       reports. */
    for (i = 0; i <= 0x1F; i++)
      unprintable[i] = TRUE;
    for (i = 0x20; i <= 0xFF; i++)
      unprintable[i] = FALSE;    
    if (substrcaseeq(op->outopts.lngstr[charset_], "iso-8859-") ||
	strcaseeq(op->outopts.lngstr[charset_], "armscii-8")) {
      for (i = 0x80; i <= 0x9F; i++)
	unprintable[i] = TRUE;
    }
    unprintable[0x7F] = TRUE;
    unprintable[(unsigned char)'\0'] = FALSE;
    unprintable[(unsigned char)'\t'] = FALSE;
    if (op->outopts.baseurl != NULL && STREQ(op->outopts.baseurl, "none"))
      op->outopts.baseurl = NULL;
    if (op->outopts.outstyle == COMPUTER) {
      DEFAULTSTR(op->outopts.lngstr[succreqs_], "SRS7");
      DEFAULTSTR(op->outopts.lngstr[totunknown_], "NCC7");
      DEFAULTSTR(op->outopts.lngstr[totpages_], "PRP7");
      DEFAULTSTR(op->outopts.lngstr[totfails_], "FLF7");
      DEFAULTSTR(op->outopts.lngstr[totredirs_], "RRR7");
      DEFAULTSTR(op->outopts.lngstr[inforeqs_], "NII7");
      DEFAULTSTR(op->outopts.lngstr[distfiles_], "NFN7");
      DEFAULTSTR(op->outopts.lngstr[disthosts_], "NHH7");
      DEFAULTSTR(op->outopts.lngstr[corrupt_], "CL");
      DEFAULTSTR(op->outopts.lngstr[unwanted_], "UL");
      DEFAULTSTR(op->outopts.lngstr[totdata_], "BTB7");
      DEFAULTSTR(op->outopts.lngstr[dayrepfmt_], "%Y%\b%M%\b%D");
      DEFAULTSTR(op->outopts.lngstr[hourrepfmt_], "%Y%\b%M%\b%D%\b%H");
      DEFAULTSTR(op->outopts.lngstr[quarterfmt_], "%Y%\b%M%\b%D%\b%H%\b%n");
      DEFAULTSTR(op->outopts.lngstr[weekfmt_], "%Y%\b%M%\b%D");
      DEFAULTSTR(op->outopts.lngstr[monthfmt_], "%Y%\b%M");
      DEFAULTSTR(op->outopts.lngstr[genrepdate_], "%Y%\b%M%\b%D");
      DEFAULTSTR(op->outopts.lngstr[genreptime_], "%Y%\b%M%\b%D%\b%H%\b%n");
      DEFAULTSTR(op->outopts.lngstr[datefmt1_], "%Y%\b%M%\b%D%\b%H%\b%n");
      DEFAULTSTR(op->outopts.lngstr[datefmt2_], "%Y%\b%M%\b%D%\b%H%\b%n");
    }
    (void)my_fclose(tempf, op->outopts.lang.file, "language file");
    op->miscopts.dirsufflength = strlen(op->miscopts.dirsuffix);
    for (i = 0; i < GENREP_NUMBER; i++)
      allgraft(op->outopts.tree[i]->tree, op->outopts.tree[i]->space);
    tempf = my_fopen(op->outopts.domainsfile, "domains file");
    if (tempf != NULL) {
      process_domainsfile(tempf, op);
      (void)my_fclose(tempf, op->outopts.domainsfile, "domains file");
    }
    for (sp = op->outopts.suborgs; sp != NULL; TO_NEXT(sp))
      confline(op, "SUBORG2", sp->name, NULL, 1);
    op->outopts.alltrees = (choice *)submalloc(xmemman, 6 * sizeof(choice));
    i = 0;
    POSSTREE(REP_DIR);
    POSSTREE(REP_DOM);
    POSSTREE(REP_ORG);
    POSSTREE(REP_TYPE);
    POSSTREE(REP_REFSITE);  /* if adding more, change '6' above */
    op->outopts.alltrees[i] = REP_NUMBER;
    op->outopts.alldervs = (choice *)submalloc(xmemman, 5 * sizeof(choice));
    i = 0;
    POSSDERV(REP_SEARCHREP);
    POSSDERV(REP_SEARCHSUM);
    POSSDERV(REP_BROWSUM);
    POSSDERV(REP_OS);       /* if adding more, change '5' above */
    op->outopts.alldervs[i] = REP_NUMBER;
    confline(op, "DOMOUTPUTALIAS", "\f", op->outopts.lngstr[unresolved_], -1);
    confline(op, "DOMOUTPUTALIAS", "\b", op->outopts.lngstr[nodomain_], -1);
    confline(op, "DOMOUTPUTALIAS", "\v", op->outopts.lngstr[unkdomain_], -1);
    confline(op, "ORGOUTPUTALIAS", "\f", op->outopts.lngstr[unresolved_], -1);
    confline(op, "ORGOUTPUTALIAS", "\b", op->outopts.lngstr[nodomain_], -1);
    confline(op, "ORGOUTPUTALIAS", "\v", op->outopts.lngstr[unkdomain_], -1);
    confline(op, "DIROUTPUTALIAS", "/", op->outopts.lngstr[rootdir_], -1);
    confline(op, "DIROUTPUTALIAS", "//", op->outopts.lngstr[nodir_], -1);
    confline(op, "TYPEOUTPUTALIAS", ".", op->outopts.lngstr[noext_], -1);
    confline(op, "TYPEOUTPUTALIAS", "./", op->outopts.lngstr[brkdirs_], -1);
    confline(op, "OSOUTPUTALIAS", "unkwin", op->outopts.lngstr[unkwin_], -1);
    confline(op, "OSOUTPUTALIAS", "unkmac", op->outopts.lngstr[unkmac_], -1);
    confline(op, "OSOUTPUTALIAS", "unkux", op->outopts.lngstr[unkux_], -1);
    confline(op, "OSOUTPUTALIAS", "unkos", op->outopts.lngstr[unkos_], -1);
#ifndef NODNS
    if (dnslevel != DNS_NONE) {
      dnstable = rehash(NULL, HASHSIZE, NULL);
      if ((tempf = my_fopen(dnsfile, "DNS input file")) != NULL) {
	pos = NULL;
	goodtimec = (starttimeuxc > (timecode_t)dnsgoodhrs * 60)?\
	  (starttimeuxc - (timecode_t)dnsgoodhrs * 60):0;
	badtimec = (starttimeuxc > (timecode_t)dnsbadhrs * 60)?\
	  (starttimeuxc - (timecode_t)dnsbadhrs * 60):0;
	while((rc = nextdnsline(tempf, &timec, &name, &alias)) != EOF) {
	  if (rc == TRUE && ((STREQ(alias, "*") && timec >= badtimec) ||
			     (!STREQ(alias, "*") && timec >= goodtimec)) &&
	      !(timec > starttimeuxc + MINS_IN_WEEK))
	    do_dns(name, alias, dnslevel);
	}
	(void)my_fclose(tempf, dnsfile, "DNS input file");
      }
      if (dnslevel == DNS_WRITE) {
#ifdef NOOPEN
	/* The ANSI, but less preferred, option. There is a race problem. Also
	   if we have got overwrite access but not read, it will go wrong. */
	if ((tempf = FOPENR(dnslockfile)) == NULL) {
	  if ((dnslock = FOPENW(dnslockfile)) == NULL) {
	    warn('F', TRUE, "Failed to create DNS lock file %s: "
		 "backing off to DNS LOOKUP", dnslockfile);
	    dnslevel = DNS_LOOKUP;
	  }
	  else
	    debug('F', "Creating %s as DNS lock file", dnslockfile);
	}
	else {
	  fclose(tempf);
	  warn('F', TRUE,
	       "DNS lock file %s already exists: backing off to DNS LOOKUP",
	       dnslockfile);
	  dnslevel = DNS_LOOKUP;
	}
#else
	/* The following is not the strictly correct procedure on Unix. NFS is
	   broken, so there can still be a race condition. One should really
	   @ create a guaranteed unique file;
	   @ hard link the lock file to the unique file;
	   @ stat the unique file, testing for links == 2.
	   However, this has the disadvantage that it can leave files with
	   weird names lying around. Also it can't then share code with other
	   platforms. I think that the chance of a problem is so small, and the
	   consequences sufficiently non-serious, that this is good enough. */
	if ((i = open(dnslockfile, O_WRONLY | O_CREAT | O_EXCL, OPEN_MODE)) < 0) {
	  if (errno == EEXIST)
	    warn('F', TRUE,
		 "DNS lock file %s already exists: backing off to DNS LOOKUP",
		 dnslockfile);
	  else
	    warn('F', TRUE, "Failed to create DNS lock file %s: "
		 "backing off to DNS LOOKUP", dnslockfile);
	  dnslevel = DNS_LOOKUP;
	}
	else if ((dnslock = fdopen(i, "w")) == NULL) { /* can this happen? */
	  /* We don't actually write to the dnslock. But this is convenient
	     for compatibility with the #ifdef NOOPEN approach. */
	  warn('F', TRUE,
	       "Failed to create DNS lock file %s: backing off to DNS LOOKUP",
	       dnslockfile);
	  dnslevel = DNS_LOOKUP;
	}
	else
	  debug('F', "Creating %s as DNS lock file", dnslockfile);
#endif
	if (dnslevel == DNS_WRITE) {
	  if ((dnsfilep = FOPENA(dnsfile)) == NULL) {
	    warn('F', TRUE, "Failed to open DNS output file %s for writing: "
		 "backing off to DNS LOOKUP", dnsfile);
	    dnslevel = DNS_LOOKUP;
	    fclose(dnslock);
	    dnslock = NULL;
	    if (remove(dnslockfile) != 0)
	      warn('F', TRUE, "Trouble deleting DNS lock file %s",
		   dnslockfile);
	    else
	      debug('F', "Deleting DNS lock file %s", dnslockfile);
	  }
	  else
	    debug('F', "Opening %s as DNS output file", dnsfile);
	}
      }
    }  /* end if dnslevel != DNS_NONE */
#endif
  }  /* end if !vblesonly */
}

/* Now functions for turning strings into log formats */

choice strtoinfmt(Inputformat **ans, char *s, choice *count) {
  extern Memman *xmemman;
  extern Inputfns inpfns[], pnlinpfn, ccinpfn;

  Inputformat *ifp;
  logical done, count_this, typedone[INP_NUMBER];
  char tempchar = '\0';
  char *c;
  int i;

  if (strchr(s, '%') == NULL)
    return(FMT_NOPC);
  for (i = 0; i < ITEM_NUMBER; i++)
    count[i] = 0;
  for (i = 0; i < INP_NUMBER; i++)
    typedone[i] = FALSE;
  *ans = (Inputformat *)submalloc(xmemman, sizeof(Inputformat));
  ifp = *ans;
  /* First a messy special case. If %s occurs but not %S, promote %s to %S */
  for (c = s, done = FALSE; *c != '\0' && !done; c++) {
    if (*c == '%') {
      c++;
      if (*c == '*')
	c++;
      if (*c == 'S')
	done = TRUE;
      c++;
    }
  }
  if (!done) {
    for (c = s; *c != '\0'; c++) {
      if (*c == '%') {
	c++;
	if (*c == '*')
	  c++;
	if (*c == 's')
	  *c = 'S';
	c++;
      }
    }
  }
  /* Now the main routine */
  for (c = s; *c != '\0'; c++) {
    if (*c == '%') {
      c++;
      if (*c == '%') {
	ifp->inpfns = &ccinpfn;
	ifp->sep = *c;
      }
      else {
	if (*c == '*') {
	  count_this = FALSE;
	  c++;
	}
	else
	  count_this = TRUE;
	done = FALSE;
	for (i = 0; !done && inpfns[i].code != '\0'; i++) {
	  if (*c == inpfns[i].code) {
	    if (inpfns[i].type != UNSET) {
	      if (typedone[inpfns[i].type])
		return(FMT_DUP);
	      typedone[inpfns[i].type] = TRUE;
	    }
	    ifp->inpfns = &inpfns[i];
	    if (inpfns[i].fn == &parsestring || inpfns[i].fn == &parseref ||
		inpfns[i].fn == &parsemsbrow || inpfns[i].fn == &parsejunk ||
		inpfns[i].fn == &parsecode) {
	      c++;
	      if (*c == '\0') {
		ifp->sep = '\n';
		c--;
	      }
	      else if (*c == '\\') {
		c++;
		if (*c == '\\')
		  ifp->sep = *c;
		else if (*c == 'n' || *c == 'r')
		  ifp->sep = '\n';
		else if (*c == 't')
		  ifp->sep = '\t';
		else
		  return(FMT_BADCHAR);
	      }
	      else if (*c == '%') {
		c++;
		if (*c == '%')
		  ifp->sep = *c;
		else if (*c == 'w' && inpfns[i].fn != &parseref) {
		  ifp->sep = WHITESPACE;      /* parseref can't take %w */
		  c -= 2;  /* need to parsespace() too */
		}
		else
		  return(FMT_NOTERM);
	      }
	      else
		ifp->sep = *c;
	    }
	    else if (inpfns[i].fn == &parselogfmt) {
	      c++;
	      if (*c < '0' || *c > '6')
		return(FMT_BADBUILTIN);
	      else
		ifp->sep = *c;
	    }
	    else  /* fn != parse(string|ref|msbrow|junk|code|logfmt) */
	      ifp->sep = '\0';
	    if (i < ITEMFNS_NUMBER)
	      count[inpfns[i].type] = count_this?2:1;
	    done = TRUE;
	  }     /* end if *c == inpfns[i].code */
	}       /* end for i */
	if (!done)
	  return(FMT_BADPC);
      }
    }    /* end if *c == '%' */
    else if (*c == '\\') {
      c++;
      if (*c == '\\') {
	ifp->inpfns = &ccinpfn;
	ifp->sep = *c;
      }
      else if (*c == 'n' || *c == 'r') {
	ifp->inpfns = &pnlinpfn;
	ifp->sep = '\n';
      }
      else if (*c == 't') {
	ifp->inpfns = &ccinpfn;
	ifp->sep = '\t';
      }
      else
	return(FMT_BADCHAR);
    }    /* end if *c == '\\' */
    else {
      ifp->inpfns = &ccinpfn;
      ifp->sep = *c;
    }
    ifp->next = (Inputformat *)submalloc(xmemman, sizeof(Inputformat));
    tempchar = ifp->sep;
    TO_NEXT(ifp);
  }
  if (tempchar != '\n') {
    ifp->inpfns = &pnlinpfn;
    ifp->next = (Inputformat *)submalloc(xmemman, sizeof(Inputformat));
    TO_NEXT(ifp);
  }
  ifp->inpfns = NULL;
  if (typedone[INP_YEAR] || typedone[INP_MONTH] || typedone[INP_DATE] ||
      typedone[INP_HOUR] || typedone[INP_MIN] || typedone[INP_AM]) {
    if (typedone[INP_UNIXTIME])
      return(FMT_DUP);
    if (!(typedone[INP_YEAR] && typedone[INP_MONTH] && typedone[INP_DATE] &&
	  typedone[INP_HOUR] && typedone[INP_MIN]))
      return(FMT_PARTTIME);  /* partial time info is corrupt */
    count[INP_DATE] = (count[ITEM_FILE] == 2)?2:1;
    count[INP_AM] = (choice)typedone[INP_AM];
    count[INP_UNIXTIME] = 0;
  }
  else if (typedone[INP_UNIXTIME]) {
    count[INP_DATE] = (count[ITEM_FILE] == 2)?2:1;
    count[INP_AM] = 0;
    count[INP_UNIXTIME] = 1;
  }
  else {
    count[INP_DATE] = 0;
    count[INP_AM] = 0;
    count[INP_UNIXTIME] = 0;
  }
  if (typedone[INP_QUERY] && !typedone[ITEM_FILE])
    return(FMT_QBUTNOR);
  count[INP_BYTES] =
    2 * (choice)(typedone[INP_BYTES] && count[ITEM_FILE] == 2);
  count[INP_PROCTIME] =
    2 * (choice)(typedone[INP_PROCTIME] && count[ITEM_FILE] == 2);
  if (typedone[INP_CODE] && count[ITEM_FILE] == 2)
    count[INP_CODE] = 2;
  else if (typedone[INP_CODE])
    count[INP_CODE] = 1;
  else
    count[INP_CODE] = 0;
  return(FMT_OK);
}

char *apachelogfmt(char *fmt) {
  extern char *workspace;  /* assume large enough, as usual */
  char *p, *q;

  workspace[0] = '\0';
  for (p = fmt; *p != '\0'; p++) {
    if (*p == '\\' && *(p + 1) != '\0')
      p++;
    if (*p == '%' && *(p + 1) != '\0') {
      p++;
      while (ISDIGIT(*p) || *p == ',' || *p == '!' || *p == '<' || *p == '>')
	p++;
      if (*p == 'b')
	strcat(workspace, "%b");
      else if (*p == 'u')
	strcat(workspace, "%u");
      else if (*p == 'v' || *p == 'V')
	strcat(workspace, "%v");
      else if (*p == 'h' || *p == 'a')
	strcat(workspace, "%S");
      else if (*p == 's')
	strcat(workspace, "%c");
      else if (*p == 'U')
	strcat(workspace, "%r");
      else if (*p == 'r')
	strcat(workspace, "%j%w%r%wHTTP%j");
      else if (*p == 't')
	strcat(workspace, "[%d/%M/%Y:%h:%n:%j]");
      else if (*p == 'T')
	strcat(workspace, "%t");
      else if (*p == 'q')
	strcat(workspace, "%q");
      else if (substrcaseeq(p, "{user-agent}") && *(p + 12) == 'i') {
	strcat(workspace, "%B");
	p += 12;
      }
      else if (substrcaseeq(p, "{referer}") && *(p + 9) == 'i') {
	strcat(workspace, "%f");
	p += 9;
      }
      else { /* unknown code */
	if (*p == '{') {
	  while (*p != '}' && *p != '\0')
	    p++;
	  if (*p == '}')
	    p++;
	  if (*p == 't')
	    return(NULL);
	}
	strcat(workspace, "%j");
      }
    }
    else {
      q = strchr(workspace, '\0');
      *(q++) = *p;
      *q = '\0';
    }
  }
  return(workspace);
}

/*** Finally we move on to the command line argument processing. ***/

void clconfline(Options *op, char *s) {
  char *cmd = NULL, *arg1 = NULL, *arg2 = NULL;
  int rc;

  if ((rc = parseconfline(s, &cmd, &arg1, &arg2)) != -1)
    confline(op, cmd, arg1, arg2, rc);
}

void clgenrep(Options *op, choice rep, char *arg) {
  size_t len;
  char c, *d;

  op->outopts.repq[rep] = (arg[0] == '+')?TRUE:FALSE;
  if (arg[0] == '-') {
    if (arg[2] != '\0')
      CLLONGWARN(arg);
  }
  else if (arg[2] != '\0') {   /* parse sort method */
    rep = G(rep);   /* future args are genargs only */
    if (!ISALPHA(arg[2]))
      d = arg + 2;
    else {
      d = arg + 3;
      c = TOLOWER(arg[2]);
      if (c == 'r')
	op->outopts.sortby[rep] = REQUESTS;
      else if (c == 'p')
	op->outopts.sortby[rep] = PAGES;
      else if (c == 'b')
	op->outopts.sortby[rep] = BYTES;
      else if (c == 'a')
	op->outopts.sortby[rep] = ALPHABETICAL;
      else if (c == 'd')
	op->outopts.sortby[rep] = DATESORT;
      else if (c == 'x')
	op->outopts.sortby[rep] = RANDOM;
      else {
	warn('C', TRUE, "Unknown sort method in command line option %s", arg);
	return;
      }
    }
    if (*d != '\0') {   /* parse floor */
      len = strlen(d);
      c = TOLOWER(*(d + len - 1));   /* final character */
      if (c != 'r' && c != 'p' && c != 'b' && c != 'd') {
	if (d == arg + 2) {
	  warn('C', TRUE,
	       "No sort method or floor given in command line option %s", arg);
	  return;
	}     /* else deduce floor method */
	c = arg[2];
	memmove((void *)(arg + 2), (void *)(arg + 3), len + 1);
	if (c == 'a' || c == 'x')
	  *(d + len - 1) = 'r';
	else
	  *(d + len - 1) = c;
	configfloor((void *)&(op->outopts.floor[rep]), arg, arg + 2, NULL, -2);
      }
      else
	configfloor((void *)&(op->outopts.floor[rep]), arg, d, NULL, -2);
    }
  }
}

void cldebug(char **s, char *arg) {
  if (arg[0] == '-') {
    if (arg[2] != '\0')
      CLLONGWARN(arg);
    configdebug(s, arg, "FALSE", NULL, -2);
  }
  else if (arg[2] == '\0')
    configdebug(s, arg, "TRUE", NULL, -2);
  else
    configdebug(s, arg, arg + 2, NULL, -2);
}

void clargs(Options *op, int argc, char *argv[]) {
  extern char repcodes[];
  extern logical vblesonly;
  extern char *debug_args, *warn_args;
  int i;
  choice j;

  for (i = 1; i < argc; i++) {
    if (strlen(argv[i]) > 255) {
      argv[i][70] = '\0';
      warn('C', TRUE, "Ignoring long command line argument starting\n%s", argv[i]);
    }
    else if (!IS_EMPTY_STRING(argv[i])) {
      if (argv[i][0] != '+' && argv[i][0] != '-')
	configlogfile((void *)&(op->miscopts.logfile), argv[i], argv[i],
		      NULL, -2);
      else switch (argv[i][1]) {
      case '\0':
	configlogfile((void *)&(op->miscopts.logfile), argv[i], "stdin",
		      NULL, -2);
	break;
      case '4':
      case '5':
      case 'd':
      case 'D':
      case 'H':
      case 'h':
      case 'm':
      case 'P':
      case 'W':
      case 'x':
      case 'z':
	for (j = 0; repcodes[j] != argv[i][1]; j++)
	  ;
	CLREPTOGGLE(j);
	break;
      case 'b':
      case 'B':
      case 'c':
      case 'E':
      case 'f':
      case 'i':
      case 'I':
      case 'J':
      case 'k':
      case 'K':
      case 'n':
      case 'N':
      case 'p':
      case 'o':
      case 'r':
      case 'S':
      case 't':
      case 'u':
      case 'v':
      case 'Z':
	for (j = 0; repcodes[j] != argv[i][1]; j++)
	  ;
	clgenrep(op, j, argv[i]);
	break;
      case 's':
	if (STREQ(argv[i] + 2, "ettings"))
	  vblesonly = TRUE;
	else
	  clgenrep(op, REP_REFSITE, argv[i]);
	break;
      case 'a':
	CLOUTSTYLE(op->outopts.outstyle);
	break;
      case 'A':
	CLLONGCHECK(configall((void *)(op->outopts.repq), argv[i],
			      (argv[i][0] == '+')?"ON":"OFF", NULL, -2));
	break;
      case 'C':
	CLSHORTCHECK(clconfline(op, argv[i] + 2));
	break;
      case 'F':
	if (argv[i][0] == '-') {
	  CLLONGCHECK(op->dman.fromstr = NULL);
	}
	else
	  CLSHORTCHECK(confline(op, "FROM", argv[i] + 2, NULL, -2));
	break;
      case 'g':
	CLSHORTCHECK((void)config(argv[i] + 2, op));
	break;
      case 'G':  /* mandatory config file: already dealt with */
	break;
      case 'O':
	CLSHORTCHECK(configstr((void *)&(op->outopts.outfile), NULL,
			       argv[i] + 2, NULL, -1));
	break;
      case 'q':
	cldebug(&warn_args, argv[i]);
	break;
      case 'T':
	if (argv[i][0] == '-') {
	  CLLONGCHECK(op->dman.tostr = NULL);
	}
	else
	  CLSHORTCHECK(confline(op, "TO", argv[i] + 2, NULL, -2));
	break;
      case 'U':
	CLSHORTCHECK(confline(op, "CACHEFILE", argv[i] + 2, NULL, -2));
	break;	
      case 'V':
	cldebug(&debug_args, argv[i]);
	break;
      case 'X':
	CLGOTOS(op->outopts.gotos);
	break;
      case '-':
	if (STREQ(argv[i] + 2, "settings"))
	  vblesonly = TRUE;
	else
	  warn('C', TRUE, "Ignoring unknown command line argument %s",
	       argv[i]);  /* --help and --version are detected earlier, */
	break;            /* in settings() */
      default:
	warn('C', TRUE, "Ignoring unknown command line argument %s", argv[i]);
	break;
      }
    }
  }
}
