/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/*
 *   COMPONENT_NAME: austext
 *
 *   FUNCTIONS: add_a_keytype
 *              dumpboth
 *              dumpoef
 *              load_ocf
 *              obsolete_keyword
 *              ocfopen
 *              parse_boolean
 *              read_database
 *              read_filepath
 *              read_keytypes
 *              read_maxhits
 *              read_path
 *              read_rest_of_line
 *              set_boolbit
 *              set_boolint
 *              set_fileio
 *              set_int
 *              set_long
 *
 *   ORIGINS: 27
 *
 *   (C) COPYRIGHT International Business Machines Corp. 1991,1995
 *   All Rights Reserved
 *   US Government Users Restricted Rights - Use, duplication or
 *   disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */
/*************************** OCF.C ***************************
 * $TOG: ocf.c /main/9 1998/04/17 11:26:02 mgreess $
 * August 1991.
 * Load_ocf() reads an engine's "site configuration file" (.ocf),
 * parses its contents, and returns a ptr to a list of
 * DBLKs containing the file's info.
 * Returns TRUE if .ocf file format is acceptable and everything worked.
 * Returns FALSE if file contains fatal errors.  If file is ok, also
 * returns a list of dblks in global usrblk and sets some global variables.
 * The name and path of config file can be specified
 * or generated in a number of convenient ways.
 * The theory is: as many options as possible are in the config file
 * rather than command lines, etc.
 * Note: User configuration files are no longer supported.
 * 
 * $Log$
 * Revision 2.6  1996/03/05  21:12:01  miker
 * Reversed meaning of ascii_charmap test.
 *
 * Revision 2.5  1996/03/05  18:02:46  miker
 * Repl ctype functions with refs to ascii_charmap and COMMENT_CHARS.
 *
 * Revision 2.4  1996/02/05  16:34:57  miker
 * Silently try old file name when looking for site config file.
 *
 * Revision 2.3  1995/10/25  15:39:33  miker
 * Added prolog.
 *
 * Revision 2.2  1995/10/03  21:40:15  miker
 * Cosmetic msg change only.
 *
 * Revision 2.1  1995/09/22  21:30:12  miker
 * Freeze DtSearch 0.1, AusText 2.1.8
 *
 * Revision 1.3  1995/09/05  21:41:59  miker
 * Changed environment variable OCFPATH to DTSROCFPATH.
 *
 * Revision 1.2  1995/09/05  18:46:13  miker
 * Moved all documentation to ocf.doc.
 *
 * Revision 1.1  1995/08/31  21:07:25  miker
 * Initial revision (renamed ocf.c from loadocf.c).
 *
 * *Log: loadocf.c,v *
 * Revision 1.19  1995/07/19  21:12:34  miker
 * 2.1.6c: Cleaned up documentation to match actual current capabilities.
 * Obsoleted MAILFEATURE and PRINTSERVER keywords.
 *
 * Revision 1.18  1995/07/18  22:52:17  miker
 * Added ability to open prespecified OE_sitecnfg_fname.
 */

#include "SearchE.h"
#include <ctype.h>
#include <stdlib.h>
#define X_INCLUDE_STRING_H
#define XOS_USE_NO_LOCKING
#include <X11/Xos_r.h>
#include <errno.h>
#include <unistd.h>	/* for POSIX getcwd() */
#include <sys/stat.h>

char    *strupr (char *s);

/*****#define DEBUG_DUMP******/
/********#define DEBUG_OEF**********/

#define PROGNAME	"OCF"
#define	DELIMITERS	" =,\t\n"
#define NEGLOGHALF	(-.69314718F)	/* negative natural log of .5 */
#define FNAME_SITECONFIG_OLD	"austext.ocf"
#define MS_misc		1
#define MS_loadocf	8

/*-------------------- STATIC GLOBALS ----------------------*/
static DBLK    *db;	/* curr database/dict */
static char    *cfgfname;
static char    *sprintbufp;
static char    *token;
static int      fatal_error;
static int      debug_ocf = FALSE;

/*-------------------- ENGINE GLOBALS ----------------------
 * These are globals which must be accessed by both sides. 
 * Globals unique to the engine are in oe.c.
 */
char           *OE_server_dir = NULL;
char           *OE_inittab_dir = NULL;

/*-------------------- OEF TABLE ----------------------*/
/*** (struct typdef located in oe.h)
	{
	char	*id;			 keyword identifier
	char	**OEFptr;		 addr of variable to change
	char	previously_specified;	 bool ensures only one spec
	} OEFTAB;
********/
OEFTAB          oef_table[] =
{
    { "AUDIT", &OEF_audit, FALSE, },
    { "DISCARD", &OEF_discard, FALSE, },
    { "NEWS", &OEF_news, FALSE, },
    { "NOTESNOT", &OEF_notesnot, FALSE, },
    { "NOTESSEM", &OEF_notessem, FALSE, },
    { "README", &OEF_readme, FALSE, },
    { NULL, NULL, FALSE },	/* end of list */
};


#ifdef DEBUG_DUMP	/* for debugging only */
/****************************************/
/*					*/
/*		 dumpboth		*/
/*					*/
/****************************************/
static void     dumpboth (void)
{
    char            typestring[4 * MAX_KTCOUNT + 2];
    int             i;
    char           *ptr;
    LLIST          *m;
    DBLK           *d;

    printf ("\nDUMPBOTH> fatal_error=%d, msglist=%p, dblist=%p\n",
	fatal_error, ausapi_msglist, usrblk.dblist);
    printf ("%s\n", DtSrGetMessages ());
    DtSrFreeMessages ();
    for (d = usrblk.dblist; d != NULL; d = d->link) {
	ptr = typestring;
	for (i = 0; i < d->ktcount; i++) {
	    *ptr++ = (d->keytypes[i].is_selected) ? '*' : ' ';
	    *ptr++ = d->keytypes[i].ktchar;
	    *ptr++ = ' ';
	}
	*ptr = 0;
	printf ("DBLK at %p link=%p name='%s' max=%d\n"
	    "    keytypes='%s', path='%s'\n",
	    d, d->link, d->name, d->maxhits,
	    typestring, NULLORSTR (d->path));
    }
    printf ("Push any key to continue...");

    *typestring = '\0';
    fgets (typestring, sizeof(typestring), stdin);
    if (strlen(typestring) && typestring[strlen(typestring)-1] == '\n')
      typestring[strlen(typestring)-1] = '\0';

    printf ("\n\n\n");
    return;
}  /* dumpboth() */

#endif

#ifdef DEBUG_OEF	/* for debugging only */
/****************************************/
/*					*/
/*		 dumpoef		*/
/*					*/
/****************************************/
static void     dumpoef (char *before_after)
{
    OEFTAB         *oef;
    fprintf (aa_stderr,
	"\toef_table[] %s (* = changed strings)\n",
	before_after);
    for (oef = oef_table; oef->id != NULL; oef++) {
	fprintf (aa_stderr, "\t%10s = %c'%s'\n",
	    oef->id,
	    (oef->previously_specified) ? '*' : ' ',
	    *(oef->OEFptr));
    }
    fputc ('\n', aa_stderr);
    return;
}  /* dumpoef() */

#endif


/****************************************/
/*					*/
/*	       parse_boolean		*/
/*					*/
/****************************************/
/* Forces token to upper case.
 * Returns +1 for TRUE, -1 for FALSE, and 0 for neither.
 */
static int      parse_boolean (char *token)
{
    /* Completely missing token defaults to affirmative */
    if (token == NULL)
	return +1;

    strupr (token);
    switch (token[0]) {
	case 'O':	/* on, off */
	    if (token[1] == 'N')
		return +1;
	    else if (token[1] == 'F')
		return -1;
	    else
		return 0;

	case 'T':	/* true */
	case 'Y':	/* yes */
	case 'E':	/* enabled */
	case '1':	/* (one) */
	    return +1;

	case 'F':	/* false */
	case 'N':	/* no */
	case 'D':	/* disabled */
	case '0':	/* (zero) */
	    return -1;

	default:
	    return 0;
    }
}  /* parse_boolean() */

/****************************************/
/*					*/
/*	       read_database		*/
/*					*/
/****************************************/
/* Validates next token as database name.
 * If not valid name, returns FALSE.
 * Otherwise chains down through the database list
 * looking for the one whose name matches the token.
 * If a match is found, sets db to it, returns TRUE.
 * If no match is found, creates and initializes
 * new structure, sets db to it, returns TRUE.
 */
static int      read_database (char *keyword, _Xstrtokparams *strtok_buf)
{
    DBLK          **linkptr;

    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 477,
		"%s %s: Missing database name after '%s' keyword.\n"),
	    PROGNAME"477", cfgfname, keyword);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
	return FALSE;
    }

    if (strlen (token) > 8) {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 184,
	    "%s %s: More than 8 characters in Database name '%.16s'.\n"),
	    PROGNAME"184", cfgfname, token);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
	return FALSE;
    }

    /* Chain down the list looking for previously defined database structure */
    linkptr = &usrblk.dblist;
    for (db = usrblk.dblist; db != NULL; db = db->link) {
	if (strcmp (db->name, token) == 0)
	    return TRUE;
	linkptr = &db->link;
    }
    /* Didn't find matching dblk so allocate and initialize new one at
     * end of list (linkptr).  Use system defaults for most values.
     */
    if (debug_ocf)
	fprintf (aa_stderr, PROGNAME "204 "
	    "%s: creating dblk for '%s'\n", cfgfname, token);
    db = austext_malloc (sizeof (DBLK) + 8, PROGNAME "205", NULL);
    memset (db, 0, sizeof (DBLK));
    strcpy (db->name, token);
    db->is_selected = TRUE;
    *linkptr = db;

    return TRUE;
}  /* read_database() */


/****************************************/
/*					*/
/*	      set_boolbit		*/
/*					*/
/****************************************/
static void     set_boolbit (long *flagvar, long mask, char *flagname,
			     _Xstrtokparams *strtok_buf)
{
    int             i;

    token = _XStrtok(NULL, DELIMITERS, *strtok_buf);
    i = parse_boolean (token);
    if (i > 0)
	*flagvar |= mask;	/* switch ON */
    else if (i < 0)
	*flagvar &= ~mask;	/* switch OFF */
    else {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 352,
	    "%s %s: Invalid %s value, line ignored.\n"),
	    PROGNAME"352", cfgfname, flagname);
	DtSearchAddMessage (sprintbufp);
    }
    return;
}  /* set_boolbit() */



/****************************************/
/*					*/
/*		set_int			*/
/*					*/
/****************************************/
static void     set_int (int *intvar, char *keyword, _Xstrtokparams *strtok_buf)
{
    int             myint;

    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	token = CATGETS(dtsearch_catd, MS_loadocf, 140, "(missing)");
ERR_MSG:
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 610,
	    "%s %s: %s value is '%s'. "
	    "Should be positive integer. Line ignored."),
	    PROGNAME"844", cfgfname, keyword, token);
	DtSearchAddMessage (sprintbufp);
	return;
    }
    if ((myint = atoi (token)) <= 0L)
	goto ERR_MSG;
    *intvar = myint;
    return;
}  /* set_int() */


/****************************************/
/*					*/
/*		set_long		*/
/*					*/
/****************************************/
static void     set_long (long *longvar, char *keyword, 
			  _Xstrtokparams *strtok_buf)
{
    long            longi;

    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	token = CATGETS(dtsearch_catd, MS_loadocf, 140, "(missing)");
ERR_MSG:
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 610,
	    "%s %s: %s value is '%s'. "
	    "Should be positive integer. Line ignored."),
	    PROGNAME"610", cfgfname, keyword, token);
	DtSearchAddMessage (sprintbufp);
	return;
    }
    if ((longi = atol (token)) <= 0L)
	goto ERR_MSG;
    *longvar = longi;
    return;
}  /* set_long() */


/****************************************/
/*					*/
/*	       set_boolint		*/
/*					*/
/****************************************/
static void     set_boolint (int *boolint, char *keyword,
			     _Xstrtokparams *strtok_buf)
{
    int             i;

    token = _XStrtok(NULL, DELIMITERS, *strtok_buf);
    i = parse_boolean (token);
    if (i > 0)
	*boolint = TRUE;	/* switch ON */
    else if (i < 0)
	*boolint = FALSE;	/* switch OFF */
    else {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 352,
	    "%s %s: Invalid %s value, line ignored.\n"),
	    PROGNAME"352", cfgfname, keyword);
	DtSearchAddMessage (sprintbufp);
    }
    return;
}  /* set_boolint() */

/****************************************/
/*					*/
/*	      set_fileio		*/
/*					*/
/****************************************/
/* sets OE_fileio string pointer */
static void     set_fileio (_Xstrtokparams *strtok_buf)
{
    /* if a value is missing, presume -ON */
    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	OE_fileio = "-ON";
	return;
    }

    /* check for hardcoded path name (anything that doesn't begin with '-') */
    if (token[0] != '-') {
	OE_fileio = austext_malloc (strlen (token) + 2, PROGNAME "592", NULL);
	strcpy (OE_fileio, token);
	return;
    }

    strupr (token);
    if (strcmp (token, "-ON") == 0)
	OE_fileio = "-ON";
    else if (strcmp (token, "-OFF") == 0)
	OE_fileio = "-OFF";
    else if (strcmp (token, "-HOME") == 0)
	OE_fileio = "-HOME";
    else {
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 607,
	    "%s %s: Invalid FILEIO value.  "
	    "User file reads and writes prohibited."),
	    PROGNAME"607", cfgfname);
	DtSearchAddMessage (sprintbufp);
	OE_fileio = "-OFF";
    }
    return;
}  /* set_fileio() */


/****************************************/
/*					*/
/*	    read_rest_of_line		*/
/*					*/
/****************************************/
/* Malloc a copy of the rest of the input line after
 * the current token and set passed string ptr to it.
 */
static void     read_rest_of_line (char *keyword, char **passed_ptr,
				   _Xstrtokparams *strtok_buf)
{
    if ((token = _XStrtok(NULL, "\n", *strtok_buf)) == NULL) {
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 1007,
	    "%s%s: Empty %s string ignored."),
	    PROGNAME"1007 ", cfgfname, keyword);
	DtSearchAddMessage (sprintbufp);
	return;
    }
    *passed_ptr = austext_malloc (strlen(token) + 4, PROGNAME"1020", NULL);
    strcpy (*passed_ptr, token);
    return;
}  /* read_rest_of_line() */ 


/****************************************/
/*					*/
/*	      read_filepath		*/
/*					*/
/****************************************/
static void     read_filepath (_Xstrtokparams *strtok_buf)
{
    char           *errp;
    char           *newpath;
    int             tabx;
    int             toklen;

    /* Second token on line should be valid id word.
     * Set tabx to matching oef table entry.
     */
    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	token = CATGETS(dtsearch_catd, MS_loadocf, 142, "rest of line");
	errp = CATGETS(dtsearch_catd, MS_loadocf, 143, "is empty");

IGNORE_FILEPATH:
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 903,
	    "%s %s: FILEPATH '%s' %s. Line ignored."),
	    PROGNAME"903", cfgfname, token, errp);
	DtSearchAddMessage (sprintbufp);
	return;
    }
    strupr (token);
    for (tabx = 0; oef_table[tabx].id != NULL; tabx++) {
	if (strcmp (oef_table[tabx].id, token) == 0)
	    break;
    }
    if (oef_table[tabx].id == NULL) {
	errp = CATGETS(dtsearch_catd, MS_loadocf, 165, "unknown id");
	goto IGNORE_FILEPATH;
    }
    if (oef_table[tabx].previously_specified) {
	errp = CATGETS(dtsearch_catd, MS_loadocf, 166, "previously specified");
	goto IGNORE_FILEPATH;
    }

    /* Third token should be either a directory path (ends in slash)
     * or a directory path plus a file name (does not end in slash).
     */
    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	token = oef_table[tabx].id;
	errp = CATGETS(dtsearch_catd, MS_loadocf, 168,
	    "missing path specification");
	goto IGNORE_FILEPATH;
    }
    toklen = strlen (token);

    /* Allocate buffer for new path string and initialize it.
     * Leave enough extra room for the default, dos-format file name,
     * i.e. 8 chars, a period, and a 3 char extension.
     */
    newpath = austext_malloc (toklen + 20, PROGNAME "956", NULL);
    strcpy (newpath, token);
    if (token[toklen - 1] == LOCAL_SLASH) {
	strncpy (newpath + toklen, *(oef_table[tabx].OEFptr), 12);
	newpath[toklen + 12] = 0;
    }

    /* Setting the previously_specified flag not only prevents multiple
     * specifications in a single file, but also permits user files
     * to override site files for specific ids because loadocf is always
     * called by the ui before it is called by oe initialization.
     */
    *(oef_table[tabx].OEFptr) = newpath;
    oef_table[tabx].previously_specified = TRUE;
    return;
}  /* read_filepath() */


/****************************************/
/*					*/
/*	       read_path		*/
/*					*/
/****************************************/
static void     read_path (_Xstrtokparams *strtok_buf)
{
#ifdef DEBUG_DUMP
                    printf (PROGNAME "217> Entering read_path().\n");
#endif

    /* parse the path for this dictionary/database */
    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 221,
	    "%s %s: Database '%s', No PATH specified, statement ignored.\n"),
	    PROGNAME"221", cfgfname, db->name);
	DtSearchAddMessage (sprintbufp);
	return;
    }
    db->path = austext_malloc (strlen (token) + 4, PROGNAME "244", NULL);
    strcpy (db->path, token);
    ensure_end_slash (db->path);
    return;
}  /* read_path() */


/****************************************/
/*					*/
/*	      obsolete_keyword		*/
/*					*/
/****************************************/
/* append warning msg: passed keyword no longer used */
static void     obsolete_keyword (char *keyword)
{
    sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 1,
	"%1$s%2$s: %3$s keyword is obsolete."),
	PROGNAME "001 ", cfgfname, keyword);
    DtSearchAddMessage (sprintbufp);
    return;
}  /* obsolete_keyword() */


/****************************************/
/*					*/
/*	      read_maxhits		*/
/*					*/
/****************************************/
static void     read_maxhits (_Xstrtokparams *strtok_buf)
{
    int             i;

#ifdef DEBUG_DUMP
    printf (PROGNAME "292> Entering read_maxhits().\n");
#endif

    /* parse the max number of hits for this dictionary */
    if ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) == NULL) {
INVALID_MAXHITS:
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 304,
	    "%s %s: Database '%s', invalid MAXHITS value.\n"),
	    PROGNAME"304", cfgfname, db->name);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
	return;
    }
    i = atoi (token);
    if (i <= 0)
	goto INVALID_MAXHITS;
    else
	db->maxhits = i;
    return;
}  /* read_maxhits() */


/****************************************/
/*					*/
/*	      add_a_keytype		*/
/*					*/
/****************************************/
/* Subroutine of read_keytypes().
 * Concatenates a new DtSrKeytype node to end of current
 * db->keytypes array (in effect, remallocs db->keytypes).
 * Initializes ktchar with passed character.
 * Initializes name string with a generic label derived
 * from ktchar, which may later be changed by read_keytypes().
 * Keeps db->ktcount current.
 * If duplicate ktchar already exists, adds warning msg to
 * msglist, but changes nothing and returns index of old node.
 * Returns index of DtSrKeytype node or -1 on error.
 */
static int      add_a_keytype (char ktchar)
{
    DtSrKeytype        *kt;
    int             i, newindex;

    /* Make sure we have not exceeded max number of keytypes */
    if (db->ktcount >= MAX_KTCOUNT) {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 732,
	    "%s %s: Database '%s', No more than %d keytypes allowed.\n"),
	    PROGNAME"732", cfgfname, db->name, MAX_KTCOUNT);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
	return -1;
    }

    /* Make sure ktchar is alphanumeric */
    if ( (ascii_charmap[ktchar] & (CONSONANT | VOWEL | NUMERAL)) == 0) {
	if (!isprint (ktchar))
	    ktchar = '?';
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 684,
	    "%s %s: Database '%s',\n  Invalid KEYTYPE character '%c'."),
	    PROGNAME"684", cfgfname, db->name, ktchar);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
	return -1;
    }

    /* Test for duplicate, preexisting keytype */
    kt = db->keytypes;
    for (i = 0; i < db->ktcount; i++)
	if (ktchar == kt[i].ktchar) {
	    sprintf (sprintbufp,
		CATGETS(dtsearch_catd, MS_loadocf, 1002,
		"%s %s: Database '%s', Duplicate KEYTYPE character '%c'.\n"),
		PROGNAME"1002", cfgfname, db->name, ktchar);
	    DtSearchAddMessage (sprintbufp);
	    return i;
	}

    /* Append valid new keytype to the keytypes array */
    i = sizeof(DtSrKeytype) * db->ktcount;	/* size of current array */
    kt = austext_malloc (i + sizeof(DtSrKeytype) + 4, PROGNAME "699", NULL);
    if (db->keytypes != NULL) {
	memcpy (kt, db->keytypes, i);
	free (db->keytypes);
    }
    newindex = db->ktcount;
    kt[newindex].is_selected = TRUE;
    kt[newindex].ktchar = ktchar;
    sprintf (kt[newindex].name,
	CATGETS(dtsearch_catd, MS_loadocf, 457, "'%c' Records"), ktchar);

    /* Warn about inaccessible lowercase ktchars */
    if ( ((ascii_charmap[ktchar] & 0xff) != ktchar) && OE_uppercase_keys ) {
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 1011,
	    "%s %s: Database '%s': Records with lowercase\n"
	    "  KEYTYPE character '%c' will be inaccessible.\n"
	    "  Set UPPERKEYS = FALSE if lowercase keys are intended."),
	    PROGNAME"1011", cfgfname, db->name, ktchar);
	DtSearchAddMessage (sprintbufp);
    }

    db->keytypes = kt;
    db->ktcount++;
    return newindex;
}  /* add_a_keytype() */


/****************************************/
/*					*/
/*	      read_keytypes		*/
/*					*/
/****************************************/
/* Builds keytypes arrays in current dblk.
 * Parameter 'how_many' limits the number keytype additions.
 * For example if how_many == 1, only 1 keytype will be added
 * and the balance of the line will be ignored.
 * We also quit adding keytypes when the total reaches
 * MAX_KTCOUNT or when we hit end of line or comments.
 */
static void     read_keytypes (int how_many, _Xstrtokparams *strtok_buf)
{
    /* 'last_ktchar' is index to db->keytypes.  if last_ktchar < 0 
     * then last token was not a ktchar, or it was an invalid ktchar.
     */
    int             last_ktchar = -1;
    DtSrKeytype        *kt;
    char           *charptr;

#ifdef DEBUG_DUMP
    printf (PROGNAME "361> Entering read_keytypes().\n");
#endif

    /* parsing loop for each keytypes token */
    while ((token = _XStrtok(NULL, DELIMITERS, *strtok_buf)) != NULL) {
	/* quit when comments begin */
	if (*token == '*' || *token == ':')
	    break;

	/* Test for ktchar token (beginning with a single quote) */
	if (*token == '\'') {
	    if (how_many <= 0)
		break;	/* don't exceed our quota */
	    last_ktchar = add_a_keytype (token[1]);
	    if (last_ktchar >= 0)
		how_many--;
	    continue;
	}

	/*
	 * Token is a name string.  If the last token was NOT a ktchar in
	 * single quotes, or it was an invalid ktchar, then create a new
	 * keytypes entry with the ktchar equal to the first char of this
	 * label. If any err, don't update the name string. 
	 */
	if (last_ktchar < 0) {
	    if (how_many <= 0)
		break;	/* don't exceed our quota */
	    last_ktchar = add_a_keytype (token[0]);
	    if (last_ktchar < 0)
		continue;
	    else
		how_many--;
	}

	/*
	 * Update the appropriate table entry with the label string.
	 * Convert underscores to spaces. 
	 */
	kt = &db->keytypes[last_ktchar];
	strncpy (kt->name, token, DtSrMAX_KTNAME);
	kt->name[DtSrMAX_KTNAME] = 0;
	for (charptr = kt->name; *charptr != 0; charptr++)
	    if (*charptr == '_')
		*charptr = ' ';

	last_ktchar = -1;
    }	/* end token parsing loop */

    return;
}  /* read_keytypes() */


/****************************************/
/*					*/
/*		 ocfopen		*/
/*					*/
/****************************************/
/* Subroutine of load_ocf().
 * Attempts to open config file created by concatenating
 * file name to passed pathname prefix.
 * If both fields are NULL, tries to open fully qualified
 * fname prespecified in OE_sitecnfg_fname.
 * Messages may be appended if some information, like an
 * environment variable, was found but still couldn't open file.
 * Loads OE_sitecnfg... fields after successful sitecnfg open.
 * Returns TRUE if fopen() successful, else FALSE.
 */

static int      ocfopen (char *prefix, char *fname, FILE ** stream)
{
    char            fullname[1024];
    FILE           *fptr;
    struct stat     statbuf;
    int             is_prespecified_fname = (prefix == NULL && fname == NULL);

    /*
     * Build a complete path/file name from passed args into fullname
     * buffer. 
     */
    if (is_prespecified_fname) {
	strncpy (fullname, OE_sitecnfg_fname, sizeof (fullname));
	fullname[sizeof (fullname) - 1] = 0;
	goto GOT_FULLNAME;
    }

    if (prefix == NULL) {
	if (debug_ocf)
	    fprintf (aa_stderr, " --> open not attempted: null prefix.\n");
	return FALSE;
    }

    strncpy (fullname, prefix, sizeof (fullname));
    fullname[sizeof (fullname) - 2] = 0;
    strcpy (ensure_end_slash (fullname), fname);


GOT_FULLNAME:
    fptr = fopen (fullname, "rt");
    if (debug_ocf) {
	if (fptr == NULL)
	    fprintf (aa_stderr, " --> %s\n", strerror (errno));
	else
	    fprintf (aa_stderr, " --> open succeeded!\n");
    }

    /*
     * If open failed: (1) Append information message if error is
     * something other than 'cant find file'. (Always retn msg if user
     * prespecified file name). (2) Return now. 
     */
    if (fptr == NULL) {
	if (errno != ENOENT || is_prespecified_fname) {
	    sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 414,
		"%s Unable to open configuration file '%s':\n  %s."),
		PROGNAME"414", fullname, strerror(errno));
	    DtSearchAddMessage (sprintbufp);
	}
	return FALSE;
    }

    /* Load OE_... fields associated with site config */
    if (!is_prespecified_fname) {
	OE_sitecnfg_fname = austext_malloc (strlen (fullname) + 2,
	    PROGNAME "941", NULL);
	strcpy (OE_sitecnfg_fname, fullname);
    }
    if (fstat (fileno (fptr), &statbuf) == -1) {
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 1404,
	    "%s Status unavailable for configuration file '%s': %s"),
	    PROGNAME"1404", fullname, strerror(errno));
	DtSearchAddMessage (sprintbufp);
	fclose(fptr);
	return FALSE;
    }
    OE_sitecnfg_mtime = statbuf.st_mtime;

    /* Save file name globally for error msgs */
    strcpy (cfgfname, fullname);
    *stream = fptr;
    return TRUE;
}  /* ocfopen() */


/****************************************/
/*					*/
/*	        load_ocf		*/
/*					*/
/****************************************/
/* This is the only function in this module seen by the outside world.
 * Returns TRUE if .ocf file format is acceptable and everything worked.
 * Returns FALSE if file contains fatal errors.  If file is ok, also
 * returns a list of dblks in global usrblk and sets some global variables.
 */
int             load_ocf (void)
{
    char           *p, *q;
    char            sprintbuf[1024];
    char            cfgfnamebuf[1024];
    char            inbuf[1024];
    char            uprtoken[128];
    FILE           *stream;
    _Xstrtokparams  strtok_buf;

    /* Initialize program globals */
    usrblk.dblist = NULL;	/* just to be sure */
    fatal_error = FALSE;
    sprintbufp = sprintbuf;
    cfgfname = cfgfnamebuf;

    debug_ocf = (usrblk.debug & USRDBG_RARE);

#ifdef DEBUG_DUMP
    puts ("\nentering load_ocf");
    dumpboth ();
#endif

#ifdef DEBUG_OEF
    dumpoef ("BEFORE");
#endif

    /* Construct the full pathname/filename and open it.
     * Try various optional path prefixes depending on
     * existence of certain environment and global variables.
     * Also silently try the old filename in each directory.
     * If the site config file is never found, return FALSE,
     * meaning 'fatal error', and an error msg.
     *
     * (1) The first place to look is a prespecified, fully qualified
     * name passed by the initialization function.  That occurs when
     * the global OE_sitecnfg_fname, which otherwise would be NULL,
     * points to the passed arg.  It's a fatal error if this name
     * was specified but cannot be opened.
     */
    if (OE_sitecnfg_fname) {
	if (debug_ocf)
	    fprintf (aa_stderr,
		PROGNAME "1446 ocfopen '%s', prespecified file name.\n",
		OE_sitecnfg_fname);
	if (ocfopen (NULL, NULL, &stream))
	    goto OCFOPEN_OK;
	else
	    return FALSE;
    }

    /* (2) try to find file in DTSROCFPATH directory */
    p = getenv ("DTSROCFPATH");
    if (debug_ocf)
	fprintf (aa_stderr, PROGNAME "1753 ocfopen '%s'. DTSROCFPATH='%s'.\n",
	    FNAME_SITECONFIG, NULLORSTR (p));
    if (ocfopen (p, FNAME_SITECONFIG, &stream))
	goto OCFOPEN_OK;
    if (ocfopen (p, FNAME_SITECONFIG_OLD, &stream))
	goto OCFOPEN_OK;

    /* (3) try to find file in current working directory */
    if (getcwd (inbuf, sizeof (inbuf)) == NULL)
	strcpy (inbuf, "./");	/* default to 'local' dir */
    else if (inbuf[0] == 0)
	strcpy (inbuf, "/");	/* presume 'root' dir */
    if (debug_ocf)
	fprintf (aa_stderr, PROGNAME "1771 ocfopen '%s'. cwd='%s'.\n",
	    FNAME_SITECONFIG, inbuf);
    if (ocfopen (inbuf, FNAME_SITECONFIG, &stream))
	goto OCFOPEN_OK;
    if (ocfopen (inbuf, FNAME_SITECONFIG_OLD, &stream))
	goto OCFOPEN_OK;

    /* (4) try to find file in HOME directory */
    q = getenv ("HOME");
    if (debug_ocf)
	fprintf (aa_stderr, "ocfopen '%s'. HOME='%s'.\n",
	    FNAME_SITECONFIG, NULLORSTR (q));
    if (ocfopen (q, FNAME_SITECONFIG, &stream))
	goto OCFOPEN_OK;
    if (ocfopen (q, FNAME_SITECONFIG_OLD, &stream))
	goto OCFOPEN_OK;

    /* (5) try to find file in server daemon's inittab directory */
    if (debug_ocf)
	fprintf (aa_stderr, "ocfopen '%s'. OE_inittab_dir='%s'.\n",
	    FNAME_SITECONFIG, NULLORSTR (OE_inittab_dir));
    if (ocfopen (OE_inittab_dir, FNAME_SITECONFIG, &stream))
	goto OCFOPEN_OK;
    if (ocfopen (OE_inittab_dir, FNAME_SITECONFIG_OLD, &stream))
	goto OCFOPEN_OK;

    /* (6) try to find file in server daemon's inetd.conf directory */
    if (debug_ocf)
	fprintf (aa_stderr, "ocfopen '%s'. OE_server_dir='%s'.\n",
	    FNAME_SITECONFIG, NULLORSTR (OE_server_dir));
    if (ocfopen (OE_server_dir, FNAME_SITECONFIG, &stream))
	goto OCFOPEN_OK;
    if (ocfopen (OE_server_dir, FNAME_SITECONFIG_OLD, &stream))
	goto OCFOPEN_OK;

    /* If we can't find site config on OE side we must quit.  */
    sprintf (sprintbuf, CATGETS(dtsearch_catd, MS_loadocf, 1643,
	    "%1$sCannot find or open '%2$s'\n"
	    "  configuration file in any of the following locations:\n"
	    "    DTSROCFPATH directory =     %3$s,\n"
	    "    current working directory = %4$s,\n"
	    "    HOME directory =            %5$s,\n"
	    "    /etc/inittab directory =    %6$s\n"
	    "    /etc/inetd.conf directory = %7$s\n"
	)
	,PROGNAME "1643 "
	,FNAME_SITECONFIG
	,(p) ? p : CATGETS(dtsearch_catd, MS_misc, 16,
	    "<no environment variable>")
	,inbuf
	,(q) ? q : CATGETS(dtsearch_catd, MS_misc, 16,
	    "<no environment variable>")
	,(OE_inittab_dir) ? OE_inittab_dir :
	    CATGETS(dtsearch_catd, MS_misc, 17,
	    "<no server daemon>")
	,(OE_server_dir) ? OE_server_dir :
	    CATGETS(dtsearch_catd, MS_misc, 17,
	    "<no server daemon>")
	);
    DtSearchAddMessage (sprintbuf);
    return FALSE;

OCFOPEN_OK:

    /* MAIN LOOP ---- Read each line from file */
    *inbuf = '\0';
    while (fgets (inbuf, sizeof (inbuf) - 1, stream) != NULL) {
	/* terminate string just to be sure */
        if (strlen(inbuf) && inbuf[strlen(inbuf)-1] == '\n')
          inbuf[strlen(inbuf)-1] = '\0';

	/* test for comment line */
	if (strchr (COMMENT_CHARS, inbuf[0]))
	    continue;

	/* parse KEYWORD */
	if ((token = _XStrtok(inbuf, DELIMITERS, strtok_buf)) == NULL)
	    continue;
	
	snprintf(uprtoken, sizeof(uprtoken), "%s", token);
	strupr (uprtoken);

	if (strcmp (uprtoken, "PATH") == 0) {
	    if (read_database (uprtoken, &strtok_buf))
		read_path (&strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "KEYTYPES") == 0) {
	    if (read_database (uprtoken, &strtok_buf))
		read_keytypes (MAX_KTCOUNT, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "KEYTYPE") == 0) {
	    if (read_database (uprtoken, &strtok_buf))
		read_keytypes (1, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "FILEIO") == 0) {
	    set_fileio (&strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "USERNOTES") == 0) {
	    set_boolint (&OE_enable_usernotes, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "MARKDEL") == 0) {
	    set_boolint (&OE_enable_markdel, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "FASTDECODE") == 0) {
	    set_boolint (&OE_fastdecode, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "NOITERATE") == 0) {
	    set_boolbit (&OE_flags, OE_NO_ITERATE, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "UPPERKEYS") == 0) {
	    set_boolint (&OE_uppercase_keys, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "AUDIT") == 0) {
	    set_boolbit (&OE_flags, OE_AUDIT, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "FILEPATH") == 0) {
	    read_filepath (&strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "WHITLIM") == 0) {
	    set_long (&OE_words_hitlimit, uprtoken, &strtok_buf);
	    continue;
	}
	if (strcmp (token, "d3bug") == 0) {
	    usrblk.debug |= atol (token + strlen (token) + 1);
	    printf (PROGNAME "1630 %s: usrblk.debug = %ld, x%08lx.\n",
		cfgfname, usrblk.debug, usrblk.debug);
	    continue;
	}
	if (strcmp (uprtoken, "MAXHITS") == 0) {
	    if (read_database (uprtoken, &strtok_buf))
		read_maxhits (&strtok_buf);
	    continue;
	}
	if (strcmp (uprtoken, "LABEL") == 0) {
	    if (read_database (uprtoken, &strtok_buf))
		read_rest_of_line (uprtoken, &db->label, &strtok_buf);
	    continue;
	}

	/* if we made it this far, the first token is unknown */
	sprintf (sprintbufp,
	    CATGETS(dtsearch_catd, MS_loadocf, 495,
	    "%s %s: '%.16s' is invalid keyword.\n"),
	    PROGNAME"495", cfgfname, token);
	DtSearchAddMessage (sprintbufp);

#ifdef DEBUG_DUMP
	dumpboth ();
#endif
    }	/* ...keep reading next line in file until eof */

    fclose (stream);

    /* In a site config file, SOME database has to have been specified.
     * Also for each database, KEYTYPES must be present and perfect.
     * If no keytypes survived the KEYTYPES parse for each database,
     * or if there was no KEYTYPES line for a database
     * in site config file, remove the dblk and mark fatal error.
     */
    if (usrblk.dblist == NULL) {
	sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 1869,
	    "%s %s: Configuration file must specify at least one database."),
	    PROGNAME"1869", cfgfname);
	DtSearchAddMessage (sprintbufp);
	fatal_error = TRUE;
    }
    else
	for (db = usrblk.dblist; db != NULL; db = db->link) {
	    if (db->ktcount <= 0) {
		sprintf (sprintbufp, CATGETS(dtsearch_catd, MS_loadocf, 986,
		    "%s %s: KEYTYPES missing for database '%s'."),
		    PROGNAME"986", cfgfname, db->name);
		DtSearchAddMessage (sprintbufp);
		cutnode_llist ((LLIST *)db, (LLIST **)&usrblk.dblist);
		free (db);
		fatal_error = TRUE;
	    }
	}

#ifdef DEBUG_OEF
    dumpoef ("AFTER");
#endif

#ifdef DEBUG_DUMP
    printf (PROGNAME "516 leaving load_ocf().\n");
    dumpboth ();
#endif

    if (fatal_error)
	return FALSE;
    usrblk.dblk = usrblk.dblist;	/* init usrblk.dblk */
    return TRUE;
}  /* load_ocf() */

/*************************** OCF.C ****************************/
