static char rcsid[] = "index.c,v 1.5 1996/01/04 04:07:07 duane Exp";
/* 
 *  index.c -- Broker indexing/search support using PLS, Inc.'s PLWeb 2.0
 *
 *  Darren Hardy, U. Colorado - Boulder
 *
 *  DEBUG: section 101, level 1         Broker PLWeb indexing engine
 *
 *  You can define the following values in the broker.conf file:
 *
 *      PLS-Root                Directory in which PLWeb is installed
 *      PLS-DBgroup             Name of PLWeb database group
 *      PLS-DBname              Name of PLWeb database
 *      PLS-Num-Reorg           Number of deletions before plreorg is run
 *
 *  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
 */
#include "broker.h"
#include "log.h"
#include "PLWeb/index.h"

#ifndef USE_PARENS_FOR_BOOLEAN
#undef USE_PARENS_FOR_BOOLEAN
#endif

#define BADQ_STR \
        "103 - ERROR: PLWeb cannot support your query.\n"

#define PLS_RESULT_TAG	"PLWeb Results: "

/* Global variables */
extern char *DIRpath;
extern char *brk_obj_url;
extern int qsock;
extern int IndexType;
extern int QM_opaqueflag;
extern int QM_gotphrase;	/* got a quoted phrase or not */
extern int IndexServer_pid;
extern reg_t *Registry;

extern char *SM_Get_Obj_Filename();	/* only UNIX filesystem SM */

/* Local variables */
#define LOCAL static
LOCAL char *PLWeb_plsroot = NULL;
LOCAL char *PLWeb_dbgroup = NULL;
LOCAL char *PLWeb_dbname = NULL;
LOCAL int PLWeb_nreorg = 0;
LOCAL int PLWeb_NewObj = 0;
LOCAL int PLWeb_maxresults;
LOCAL int PLWeb_illegal_query = 0;
LOCAL int PLWeb_ncalled = 0;	/* current number of queries against server */
LOCAL int PLWeb_lifetime = 15 * 60;
LOCAL int PLWeb_max_lifetime = 15 * 60;		/* 15 minutes */

/* Local functions */
LOCAL int PLWeb_bulk_query();
LOCAL int PLWeb_del_query();
LOCAL int PLWeb_user_query();
LOCAL char *PLWeb_do_qlist();
LOCAL char *PLWeb_build_select();
LOCAL fd_t PLWeb_getfd();

#define BIG_BUFSIZ	(8*BUFSIZ)	/* for very long lines */

/* ----------------------------------------------------------------- *
 * PLWeb_bulk_query - do bulk transfer of all objects that match the query
 * ----------------------------------------------------------------- */
LOCAL int PLWeb_bulk_query(rsock, indexfp, ptime)
     int rsock;
     FILE *indexfp;
     time_t ptime;
{
	char ret[BIG_BUFSIZ];
	fd_t qfd, oldfd = -1;
	int cnt = 0;
	reg_t *bentry;
	FILE *fp;

	if ((fp = fdopen(rsock, "w")) == NULL) {
		log_errno("fdopen");
		QM_send_bulk_err(rsock);
		return ERROR;
	}
	QM_send_bulk_begin(rsock);
	while (fgets(ret, BIG_BUFSIZ, indexfp) != NULL) {
		if (((qfd = PLWeb_getfd(ret)) != ERROR) &&
		    (qfd != oldfd) &&
		    ((bentry = RG_Get_Entry(qfd)) != NULL) &&
		    (bentry->update_time >= ptime) &&
		    (QM_send_bulk_fd(qfd, fp, bentry) == SUCCESS)) {
			cnt++;
		}
	}
	fflush(fp);		/* critical, must flush before termination */
	QM_send_bulk_end(rsock);

	fclose(fp);
	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * PLWeb_del_query -- delete all objects that match the query. 
 * ----------------------------------------------------------------- */
LOCAL int PLWeb_del_query(rsock, indexfp)
     int rsock;
     FILE *indexfp;
{
	char ret[BIG_BUFSIZ];
	fd_t qfd, oldfd = -1;
	int cnt = 0;
	reg_t *rme;

	while (fgets(ret, BIG_BUFSIZ, indexfp) != NULL) {
		if (((qfd = PLWeb_getfd(ret)) != ERROR) &&
		    (qfd != oldfd) &&
		    ((rme = RG_Get_Entry(qfd)) != NULL)) {
			COL_DEL_Obj(rme);
			cnt++;
		}
	}
	Log("Deleted %d objects based on query.\n", cnt);
	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * PLWeb_user_query -- Read the output of the PLWeb query on indexfp, then
 * send to rsock via protocol.
 * ----------------------------------------------------------------- */
LOCAL int PLWeb_user_query(rsock, indexfp)
     int rsock;
     FILE *indexfp;
{
	fd_t fd1, fd2 = (fd_t) (-1);
	char inb[BIG_BUFSIZ], opb[BUFSIZ], *opdata[BIG_BUFSIZ], *tmp, *s;
	int opsize = 0, obcnt = 0, i, rank, sumsize;

	/* If the query was illegal, give up quickly */
	if (PLWeb_illegal_query) {
		SWRITE(rsock, BADQ_STR, strlen(BADQ_STR));
		return ERROR;
	}
	/*
	 *  Before we return the query results, we perform 2 write's on
	 *  the socket to the client to test whether or not the client
	 *  will be able to receive the query results.
	 *  We have to do two writes because the first will complete 
	 *  even though the other side is gone.
	 */
	(void) write(rsock, PIPECHK, strlen(PIPECHK));
	if (write(rsock, PIPECHK, strlen(PIPECHK)) == -1) {
		errorlog("Client is gone -- aborting user query results.\n");
		close(rsock);
		return ERROR;
	}
	memset(opdata, '\0', BIG_BUFSIZ * sizeof(char *));	/* zero out opdata */
	while (fgets(inb, BIG_BUFSIZ, indexfp) != NULL) {
		if ((fd1 = PLWeb_getfd(inb)) == ERROR) {
			continue;
		}
		(void) strtok(inb, "\t");	/* tag + URL */
		rank = atoi(strtok(NULL, "\t"));	/* rank info */
		sumsize = atoi(strtok(NULL, "\t"));	/* summary size */
		sprintf(inb, "Rank: %d   Summary Size: %d bytes\n", rank, sumsize);
		opdata[0] = xstrdup(inb);
		opdata[1] = NULL;
		opsize = 1;
		if ((fd1 != fd2) && (fd2 != (fd_t) (-1))) {
			/* return the previous object */
			if (QM_user_object(rsock, fd2, opsize, opdata)
			    == SUCCESS)
				obcnt++;

			/* free the opaque data */
			for (i = 0; i < BUFSIZ; i++) {
				if (opdata[i] != NULL) {
					xfree(opdata[i]);
					opdata[i] = NULL;
				}
			}
			opsize = 0;
		}
		fd2 = fd1;
	}

	/* Get the last object */
	if (fd2 != (fd_t) (-1)) {
		if (QM_user_object(rsock, fd2, opsize, opdata) == SUCCESS)
			obcnt++;
	}
	QM_user_done(rsock, obcnt);

	/* Free memory */
	for (i = 0; i < BUFSIZ; i++)
		if (opdata[i] != NULL)
			xfree(opdata[i]);

	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * PLWeb_do_qlist -- Recursive function to build a query from the list.
 * ----------------------------------------------------------------- */
LOCAL char *PLWeb_do_qlist(ql)
     qlist_t *ql;
{
#ifdef USE_PARENS_FOR_BOOLEAN
	char *ll, *rl;
	static char *nl;

	if (ql->type == LOGICAL) {
		if (ql->op == NOT) {
			return NULL;
		}
		if ((ll = PLWeb_do_qlist((qlist_t *) ql->llist)) == NULL) {
			return NULL;
		}
		if ((rl = PLWeb_do_qlist((qlist_t *) ql->rlist)) == NULL) {
			xfree(ll);
			return NULL;
		}
		nl = (char *) xmalloc(BUFSIZ);
		nl[0] = '(';
		nl[1] = '\0';
		strcat(nl, ll);

		switch (ql->op) {
		case AND:
			strncat(nl, " AND ", 5);
			break;
		case OR:
			strncat(nl, " OR ", 4);
			break;
		default:
			xfree(nl);
			xfree(rl);
			xfree(ll);
			return NULL;
		}
		strcat(nl, rl);
		strcat(nl, ")");

		xfree(ll);
		xfree(rl);

		return (nl);
	}
	return (PLWeb_build_select(ql));
#else
	char *ll, *rl;

	if (ql->type == LOGICAL) {
		if (ql->op == NOT) {
			return NULL;
		}
		if ((ll = PLWeb_do_qlist((qlist_t *) ql->llist)) == NULL) {
			return NULL;
		}
		if ((rl = PLWeb_do_qlist((qlist_t *) ql->rlist)) == NULL) {
			xfree(ll);
			return NULL;
		}
		switch (ql->op) {
		case AND:
			strncat(ll, " AND ", 5);
			break;
		case OR:
			strncat(ll, " OR ", 4);
			break;
		default:
			xfree(rl);
			xfree(ll);
			return NULL;
		}
		strcat(ll, rl);
		xfree(rl);
		return (ll);
	}
	return (PLWeb_build_select(ql));
#endif
}

/* ----------------------------------------------------------------- *
 * PLWeb_build_select -- Build the basic PLWeb query. 
 * ----------------------------------------------------------------- */
LOCAL char *PLWeb_build_select(ql)
     qlist_t *ql;
{
	static char *tmp;

	if (ql->op == EXACT) {
		tmp = (char *) xmalloc(BUFSIZ);
		tmp[0] = '\0';
		if (ql->llist) {
			PLWeb_illegal_query = 1;
			sprintf(tmp, "%s:%s", ql->llist, ql->rlist);
			xfree(ql->rlist);
			xfree(ql->llist);
			return (tmp);
		}
		sprintf(tmp, "%s", ql->rlist);
		xfree(ql->llist);
		return (tmp);
	}
	if (ql->op == REGEX) {
		tmp = (char *) xmalloc(BUFSIZ);
		tmp[0] = '\0';
		if (ql->llist) {
			PLWeb_illegal_query = 1;
			sprintf(tmp, "%s:%s", ql->llist, ql->rlist);
			xfree(ql->rlist);
			xfree(ql->llist);
			return (tmp);
		}
		sprintf(tmp, "%s", ql->rlist);
		xfree(ql->llist);
		return (tmp);
	}
	return NULL;
}

/* ----------------------------------------------------------------- *
 * PLWeb_getfd -- Get the fd of the PLWeb return.
 * ----------------------------------------------------------------- */
LOCAL fd_t PLWeb_getfd(instr)
     char *instr;
{
	char *buf, *s;
	reg_t *r;

	if (!strncmp(instr, PLS_RESULT_TAG, strlen(PLS_RESULT_TAG)))
		return ERROR;

	/* grab the URL from the first tab delimited field */
	buf = xstrdup(instr + strlen(PLS_RESULT_TAG) - 1);
	for (s = buf; *s; s++) {
		if (*s == '\t') {
			*s = '\0';
			break;
		}
	}

	/* do an approx search by URL to get the OID */
	if ((r = RG_Object_Search_ByURL(buf)) == NULL) {
		Debug(101, 1, ("PLWeb_getfd: no match for URL '%s'\n", buf));
		xfree(buf);
		return ERROR;
	}
	xfree(buf);
	return (r->FD);
}


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 
 * PUBLIC FUNCTIONS
 * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */

/* ----------------------------------------------------------------- *
 * IND_New_Object -- index a new object
 * ----------------------------------------------------------------- */
int PLWeb_IND_New_Object(entry)
     reg_t *entry;
{
	int ret = SUCCESS;

	if (IndexType == I_PER_OBJ)
		ret = ERROR;	/*PLWeb_Index_Object(entry); */

	if (ret == SUCCESS)
		PLWeb_NewObj++;

	return (ret);
}

/* ----------------------------------------------------------------- *
 * IND_Index_Full() -- perform a complete index of all objects.
 * ----------------------------------------------------------------- */
int PLWeb_IND_Index_Full()
{
	char *tfn, *fn, cmd[4 * BUFSIZ], surfn[BUFSIZ];
	FILE *fp;
	reg_t *tmp;

	Log("Begin PLWeb Full Indexing...\n");

	if ((tfn = tempnam(NULL, "plweb")) == NULL) {
		log_errno("tempnam");
		errorlog("Cannot find tmp filename?\n");
		return ERROR;
	}
	if ((fp = fopen(tfn, "w")) == NULL) {
		log_errno(tfn);
		errorlog("Cannot write tempfile?\n");
		return ERROR;
	}
	/* Walk the entire Registry to generate cheat file for pladdsur.pl */
	for (tmp = Registry; tmp != NULL; tmp = tmp->next) {
		fn = SM_Get_Obj_Filename(tmp->FD);
		sprintf(surfn, "%s.sur", fn);
		(void) unlink(surfn);
		if (tmp->desc == NULL) {
			fprintf(fp, "%s.sur %s %s \"<URL:%s>\"\n",
			    fn, fn, tmp->url, tmp->url);
		} else {
			fprintf(fp, "%s.sur %s %s \"%s\"\n",
			    fn, fn, tmp->url, tmp->desc);
		}
		xfree(fn);
	}
	(void) fclose(fp);
	sprintf(cmd, "pls_newdbgroup %s %s; pls_newdb %s %s %s; pls_index %s %s %s %s", PLWeb_dbgroup, PLWeb_plsroot, PLWeb_dbname, PLWeb_dbgroup, PLWeb_plsroot, PLWeb_dbname, PLWeb_dbgroup, PLWeb_plsroot, tfn);

	Debug(101, 1, ("RUNNING PLWeb command: %s\n", cmd));
	do_system(cmd);
	(void) unlink(tfn);
	xfree(tfn);
	Log("Finished PLWeb Full Indexing.\n");
	return SUCCESS;
}


/* ----------------------------------------------------------------- *
 * IND_Index_Incremental -- perform an incremental index
 * ----------------------------------------------------------------- */
int PLWeb_IND_Index_Incremental()
{
	Log("Sorry, PLWeb Incremental Indexing is UNSUPPORTED!\n");
	return ERROR;
}

/* ----------------------------------------------------------------- *
 * IND_Index_Start -- prepare for indexing a stream of objects.
 * ----------------------------------------------------------------- */
int PLWeb_IND_Index_Start()
{
	PLWeb_NewObj = 0;
	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * IND_Index_Flush -- finish indexing a stream of objects.
 * ----------------------------------------------------------------- */
int PLWeb_IND_Index_Flush()
{
	if (PLWeb_NewObj > 0) {
		/* Do the default indexing operation */
		switch (IndexType) {
		case I_FULL:
			return (PLWeb_IND_Index_Full());
		case I_INCR:
			return (PLWeb_IND_Index_Incremental());
		case I_PER_OBJ:
			break;
		default:
			fatal("PLWeb_IND_Index_Flush: Internal error.\n");
		}
	}
	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * IND_Destroy_Obj -- remove an object from the indexer.
 * ----------------------------------------------------------------- */
int PLWeb_IND_Destroy_Obj(entry)
     reg_t *entry;
{
	char *fn, *cmd;
	/* this is slow since you need 1 fork per delete */
	if (SM_Exist_Obj(entry->FD) == TRUE) {
		Log("Removing PLWeb object %d from index.\n", entry->FD);
		fn = SM_Get_Obj_Filename(entry->FD);
		sprintf(cmd, "pls_delete %s %s %s %d %s",
		    PLWeb_dbname, PLWeb_dbgroup, PLWeb_plsroot,
		    PLWeb_nreorg, fn);
		do_system(cmd);
		xfree(fn);
	}
	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * IND_initialize -- initialize interface to indexer
 * ----------------------------------------------------------------- */
/*
 * ** PURIFY: complains that all these strdup()'s are memory leaks.
 */
int PLWeb_IND_initialize()
{
	IndexType = I_FULL;
	PLWeb_plsroot = xstrdup("/usr/local/pls");
	PLWeb_dbgroup = xstrdup("Harvest");
	PLWeb_dbname = xstrdup("MyBroker");
	PLWeb_max_lifetime = 15 * 60;
	PLWeb_nreorg = 64;
	PLWeb_ncalled = 0;
	return SUCCESS;
}


/* ----------------------------------------------------------------- *
 * IND_do_query -- process a query string 
 * ----------------------------------------------------------------- */
int PLWeb_IND_do_query(ql, rsock, qflag, ptime)
     qlist_t *ql;
     int rsock, qflag;
     time_t ptime;
{
	FILE *indexfp;
	char commandstr[BUFSIZ], xbuf[BUFSIZ], *patstr, *tfn = NULL;
	int err = SUCCESS;

	PLWeb_ncalled++;

	sprintf(commandstr, "pls_search %s %s %s ",
	    PLWeb_dbname, PLWeb_dbgroup, PLWeb_plsroot);

	/* Generate PLWeb pattern to search */
	patstr = PLWeb_do_qlist(ql);

	if (patstr != NULL) {
		sprintf(commandstr + strlen(commandstr), " \'%s\' ", patstr);
		xfree(patstr);

		/* Need a tmpfile for PLWeb output */
		if ((tfn = tempnam(NULL, "query")) != NULL) {
			strcat(commandstr, " > ");
			strcat(commandstr, tfn);
		} else {
			SWRITE(rsock, IND_FAIL, IND_FAIL_S);
			return ERROR;	/* shouldn't really happen */
		}

		Debug(101, 1, ("PLWeb search command: %s\n", commandstr));

		/* Run the user query, give only PLWeb_lifetime seconds */
		do_system_lifetime(commandstr, PLWeb_lifetime);

		/* Now process the tempfile that contains the results */
		if ((indexfp = fopen(tfn, "r")) == NULL) {
			log_errno(tfn);
			(void) unlink(tfn);
			xfree(tfn);	/* PURIFY */
			if (qflag == UQUERY) {
				SWRITE(rsock, IND_FAIL, IND_FAIL_S);
			} else {
				QM_send_bulk_err(rsock);
			}
			(void) close(rsock);
			return ERROR;
		}
		/* Process the PLWeb results based on this query type */
		switch (qflag) {
		case QBULK:
#ifdef FORK_ON_BULK
			if (fork() == 0) {	/* child */
				close(qsock);
				(void) PLWeb_bulk_query(rsock, indexfp, ptime);
				(void) fclose(indexfp);
				(void) unlink(tfn);
				(void) close(rsock);
				_exit(0);
			}
			err = SUCCESS;
#else
			err = PLWeb_bulk_query(rsock, indexfp, ptime);
#endif
			break;
		case UQUERY:
			err = PLWeb_user_query(rsock, indexfp);
			break;
		case QDELETE:
			err = PLWeb_del_query(rsock, indexfp);
			break;
		default:
			break;
		}

		/* Clean up */
		(void) fclose(indexfp);
		(void) unlink(tfn);
		xfree(tfn);	/* PURIFY */
	} else if (qflag == QBULK) {
		QM_send_bulk_err(rsock);
		err = ERROR;
	} else {
		(void) write(rsock, ERR_MSG, strlen(ERR_MSG));
		log(ERR_MSG);
		err = ERROR;
	}
	(void) close(rsock);	/* close so that results are sent */
	return err;
}

/* ----------------------------------------------------------------- *
 * IND_Init_Flags -- intialize query parser flags 
 * ----------------------------------------------------------------- */
void PLWeb_IND_Init_Flags()
{
	PLWeb_lifetime = PLWeb_max_lifetime;	/* reset on each query */
	PLWeb_maxresults = 0;	/* Max number of hits in the result set */
	PLWeb_illegal_query = 0;	/* Is PLWeb capable of this query */
}

/* ----------------------------------------------------------------- *
 * IND_Set_Flags -- set query parser flag
 * ----------------------------------------------------------------- */
void PLWeb_IND_Set_Flags(flag, val)
     char *flag, *val;
{
	if (flag == NULL)
		return;

	if ((strcasecmp(flag, "maxresult") == 0) && val != NULL) {
		PLWeb_maxresults = atoi(val);
		if (PLWeb_maxresults < 1)
			PLWeb_maxresults = 0;
	} else if (strcasecmp(flag, "timeout") == 0) {
		if (val != NULL)
			PLWeb_lifetime = atoi(val);
		if (PLWeb_lifetime < 10)
			PLWeb_lifetime = 10;	/* at least 10 seconds */
		if (PLWeb_lifetime > PLWeb_max_lifetime)
			PLWeb_lifetime = PLWeb_max_lifetime;
	} else {
		Log("WARNING: unknown flag/val %s %s\n", flag ? flag :
		    "null", val ? val : "null");
		PLWeb_illegal_query = 0 /*1 */ ;
	}
}

/* ----------------------------------------------------------------- *
 * IND_config -- configure indexer specific variables 
 * ----------------------------------------------------------------- */
int PLWeb_IND_config(value, tag)
     char *value;
     char *tag;
{
	if (tag == NULL || value == NULL)
		return ERROR;
	Debug(101, 1, ("PLWeb Configuration: %s %s\n", value, tag));

	if (strcasecmp(tag, PLS_ROOT) == 0) {
		PLWeb_plsroot = xstrdup(value);
	} else if (strcasecmp(tag, PLS_DBGROUP) == 0) {
		PLWeb_dbgroup = xstrdup(value);
	} else if (strcasecmp(tag, PLS_DBNAME) == 0) {
		PLWeb_dbname = xstrdup(value);
	} else if (strcasecmp(tag, PLS_NREORG) == 0) {
		if (sscanf(value, "%d", &PLWeb_nreorg) != 1)
			fatal("sscanf PLWeb_nreorg failed");
	} else if (strcasecmp(tag, PLS_MAXLIFE) == 0) {
		if (sscanf(value, "%d", &PLWeb_max_lifetime) != 1)
			fatal("sscanf PLWeb_max_lifetime failed");
	}
	return SUCCESS;
}
