Revision 337 (by dpavlin, 2004/06/10 19:22:40) new trunk for webpac v2
/*
	openisis - an open implementation of the CDS/ISIS database
	Version 0.8.x (patchlevel see file Version)
	Copyright (C) 2001-2003 by Erik Grziwotz, erik@openisis.org

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it 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 this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	see README for more information
EOH */

/*
	$Id: openisis.c,v 1.67 2003/06/03 11:25:02 kripke Exp $
	main file of openisis executable.
*/

#ifdef HAVE_PTHREAD
#define	HAVE_THREADS
#endif

#include <assert.h>
#include <stdlib.h> /* free */
#include <string.h> /* strcmp */
#ifdef WIN32
#	include <sys/timeb.h>
#	include <sys/types.h>
#	define timeval _timeb
#else
#	include <unistd.h> /* gettimeofday */
#endif
#include <sys/time.h> /* gettimeofday */
#ifdef	HAVE_PTHREAD
#include <pthread.h> /* threaded crashtest */
#endif

#include "openisis.h"

/*
	temporary library includes for testing of functions
	that should later be accessible via openisis.h
*/
#include "lio.h"
extern Db* ldb_getdb (int dbid);


/* ************************************************************
	private types
*/
typedef enum { /* what to do */
	DO_DUMP,
	DO_SCAN, /* simple full scan */
	DO_SEARCH, /* basic index based searching */
	DO_TERMS, /* list terms */
	DO_CHECK, /* check db */
	DO_PERF, /* do random reads for performance checking */
	DO_CRASH, /* do multi-threaded crashtest */
	DO_SPLIT, /* split a field value */
	DO_STREAM, /* stream in records */
	DO_MFNLIST, /* fetch records by mfn list */
	DO_IFLOAD, /* read a lk2-style file from stdin */
	DO_SWLOAD, /* load stopwords */
	DO_IFDUMP, /* dump a lk2-style file to stdout */
	DO_FDT, /* print fdt */
	DO_VUTF /* validate UTF-8 input */
} todo;

typedef enum {
	FMT_MFN,  /* rowid only */
	FMT_MFNF, /* rowid, 1st field */
	FMT_PROP, /* property style */
	FMT_TXT,  /* plaintext masterfile style */
	FMT_TXTW  /* plaintext masterfile style with W lines */
} format;

typedef enum {
	IFM_DUMP,
	IFM_TAB,
	IFM_OLD,  /* dump old index */
	IFM_COPY, /* copy old index */
	IFM_CHK   /* check (new oxi) index */
}	ifmode;


/* ************************************************************
	private data
*/
static const char *pft;
static const char **term, **val;
static OpenIsisSet *post;
int nterm, nval;
static int db = -1, wdb = -1, append = 0, idxall = 0;

/* ************************************************************
	private functions
*/
static int argchk ( const char *param, const char *n, const char *v )
{
	return strcmp( param, n ) ? 0 : v ? 1 :
		(openIsisSMsg( OPENISIS_ERR_INVAL, "no value for param '%s'", param ), 0);
}	/* argchk */


static int print ( OpenIsisRec *r, int freeit, format f )
{
	union { OpenIsisRec r; char buf[10000]; } x;
	int i, ret;
	if ( ! r ) {
		if ( FMT_TXT == f )
			openIsisSMsg( 1, "\n" );
		return -1;
	}
	ret = r->rowid;
	if ( pft ) {
		OpenIsisRec *q;
		OPENISIS_INITBUF(x);
		q = openIsisFmt( &x.r, pft, r );
		if ( freeit )
			free( r );
		freeit = q != &x.r;
		r = q;
	}
	if ( FMT_MFN == f )
		openIsisSMsg( 1, "%d\n", r->rowid );
	else if ( FMT_MFNF == f )
		openIsisSMsg( 1, "%d %.*s\n", r->rowid,
			0 == r->len ? 1 : (int)r->field[0].len,
			0 == r->len ? "-" : r->field[0].val );
	else if ( FMT_TXT <= f ) {
		openIsisSMsg( 1, "\n" ); /* blank line */
		if ( FMT_TXTW == f )
			openIsisSMsg( 1, "W\t%d\n", r->rowid );
		for ( i=0; i<r->len; i++ ) {
			if ( r->field[i].val )
				openIsisSMsg( 1, "%d\t%.*s\n", r->field[i].tag,
					(int)r->field[i].len, r->field[i].val );
			else
				openIsisSMsg( 1, "%d\t%d\n",  r->field[i].tag, r->field[i].len );
		}
	} else for ( i=0; i<r->len; i++ ) {
		if ( ! r->field[i].val ) { /* shouldn't happen -- numeric ? */
			openIsisSMsg( 1, "%d.?=%d\n", r->rowid, r->field[i].len );
			continue;
		}
		openIsisSMsg( 1, "%d.%d=%.*s\n", r->rowid, r->field[i].tag,
			(int)r->field[i].len, r->field[i].val );
		if ( r->field[i].len && '^' == *r->field[i].val ) { /* split subfields */
			OpenIsisRec *rf = openIsisReadField( 0, r->field+i );
			if ( rf ) {
				int j;
				for ( j=0; j<rf->len; j++ )
					openIsisSMsg( 1, "%d.%d.%c=%.*s\n",
						r->rowid, r->field[i].tag,
						(0x60 & (int)rf->field[j].tag ) ?
							(int)rf->field[j].tag : ' ',
						(int)rf->field[j].len, rf->field[j].val );
				free( rf );
			}
		}
	}
	if ( 0 <= wdb ) {
		OpenIsisRec *q = 0;
		int ok;
		if ( append )
			r->rowid = 0;
		if ( idxall && r != &x.r ) { /* add index entries for all fields */
			OPENISIS_INITBUF(x);
			q = &x.r;
			for ( i=0; i<r->len; i++ ) {
				char hit[64];
				OpenIsisField *fld = r->field + i;
				sprintf( hit, "%d.%d.%d.1	", r->rowid, fld->tag, i );
				OPENISIS_RADDS( q, OPENISIS_XHIT, hit, q != &x.r );
				OPENISIS_RCAT( q, fld->val, fld->len, q != &x.r );
			}
		}
		ok = openIsisDWritex( wdb, r, q );
		openIsisSMsg( 1, "wrote mfn %d (%d)\n", r->rowid, ok );
	}
	if ( freeit )
		free( r );
	return ret;
}	/* print */


static void printid ( int id, format f )
{
	if ( FMT_MFN == f && 0 > wdb )
		openIsisSMsg( 1, "%d\n", id );
	else 
		print( openIsisReadRow( db, id ), !0, f );
}	/* printid */


static int printlk2 ( void *me, OpenIsisKey *key, OpenIsisHit *hit )
{
	(void)me;
	if ( key && hit )
		/* 30 key BLANK 7 mfn BLANK 5 tag BLANK 4 occ BLANK 4 pos*/
		openIsisSMsg( 1, "%-30.*s %7u %5u %4u %4u\n",
			key->len, key->byt, hit->mfn, hit->tag, hit->occ, hit->pos );
	return 0;
}	/* printlk2 */


static int printtab ( void *me, OpenIsisKey *key, OpenIsisHit *hit )
{
	(void)me;
	if ( key && hit )
		/* 30 key BLANK 7 mfn BLANK 5 tag BLANK 4 occ BLANK 4 pos*/
		openIsisSMsg( 1, "%.*s\t%u\t%u\t%u\t%u\n",
			key->len, key->byt, hit->mfn, hit->tag, hit->occ, hit->pos );
	return 0;
}	/* printtab */


/* timing utility. set the timeval, return milliseconds since last call. */
#ifdef WIN32
static int millis ( struct _timeb *tb )
{
	struct _timeb otb = *tb;
	_ftime( tb );
	return (tb->time - otb.time)*1000 + (tb->millitm - otb.millitm);
}	/* millis */
#else
static int millis ( struct timeval *tv )
{
	struct timeval otv = *tv;
	gettimeofday( tv, 0 );
	return (tv->tv_sec - otv.tv_sec)*1000 + (tv->tv_usec - otv.tv_usec)/1000;
}	/* millis */
#endif

#ifdef	HAVE_PTHREAD
int myOpenIsisLockFunc ( int lock )
{
	static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; /* the "fast" kind */
	static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
	/*
	LOG_DBG( LOG_ERROR, "thread %d op 0x%08x", (int)pthread_self(), lock );
	*/
	switch ( OPENISIS_WAIT & lock ) {
	case OPENISIS_RELE: return pthread_mutex_unlock( &mut );
	case OPENISIS_LOCK: return pthread_mutex_lock( &mut );
	case OPENISIS_WAKE: return pthread_cond_broadcast( &cond );
	case OPENISIS_WAIT: return pthread_cond_wait( &cond, &mut );
	}
	return -1;
}
#else
#	define myOpenIsisLockFunc 0
#endif

typedef struct {
	int             start;
}	threadarg;

/* (p)thread routine */
static void * run ( void *arg )
{
	struct timeval tv;
	int m;
	threadarg *my = (threadarg *)arg;
	int start = my->start;
	int i;

	millis( &tv );
	for ( i=start+1; i++!=start; ) {
		int j;
		if ( i >= nterm )
			i = 0;
		for ( j=0; j<post[i].len; j++ ) {
			int mfn = post[i].id[j];
			OpenIsisRec *r = openIsisReadRow( db, mfn );
			if ( ! r ) {
				openIsisSMsg( 2, "no rec %d\n", mfn );
				continue;
			}
			if ( 0 >= r->len )
				openIsisSMsg( 2, "no fields for %d\n", mfn );
			else if ( r->field[0].len != (int)strlen(val[mfn])
				|| memcmp( r->field[0].val, val[mfn], r->field[0].len )
			)
				openIsisSMsg( 2, "mismatch on %d\n", mfn );
			free( r );
		}
	}
	m = millis(&tv);
	openIsisSMsg( 1, "thread %d@%d terminated after %.3f seconds\n",
#ifdef HAVE_THREADS
		(int)pthread_self()
#else
		0L
#endif
		, start, m/1000. );
	return (void *)m;
}	/* run */


/* multithreaded crashtest */
static int crash ( const char *pre )
{
	struct timeval tv;
	union { OpenIsisRec r; char buf[10000]; } x;
	int l;
	int p = 0;
	int i, pass;
	int fd = 2;
	threadarg def;

	millis( &tv );
	x.r.len   = 0;
	x.r.bytes = sizeof(x);
	nterm = 0;
	while ( openIsisTerm( &x.r, db, pre ) && x.r.len )
		nterm += x.r.len;
	openIsisSMsg( fd, "%d terms\n", nterm );
	openIsisSMsg( fd, "%.3f sec\n", millis(&tv)/1000. );

	term = (const char **)malloc( nterm * sizeof(*term) );
	post = (OpenIsisSet *)malloc( nterm * sizeof(*post) );
	nterm = 0;
	nval = 0;
	while ( openIsisTerm( &x.r, db, pre ) && x.r.len ) {
		for ( i=0; i<x.r.len; i++, nterm++ ) {
			int cnt;
			OpenIsisSet *set = post+nterm;
			char *c = malloc( x.r.field[i].len + 1 );

			memcpy( c, x.r.field[i].val, x.r.field[i].len );
			c[ x.r.field[i].len ] = 0;
			term[ nterm ] = c;

			set->len = 0;
			openIsisQuery( set, db, c, OPENISIS_QRY_KEYEQ, 0 );
			if ( 0 >= set->len ) {
				openIsisSMsg( 2, "no results for '%s'\n", c );
				return 1;
			}
			for ( cnt = 0; cnt < set->len; cnt++ )
				if ( nval < set->id[cnt] )
					nval = set->id[cnt];
			p += set->len;
		}
	}
	openIsisSMsg( fd, "%d postings max mfn %d\n", p, nval );
	openIsisSMsg( fd, "%.3f sec\n", millis(&tv)/1000. );

	val = (const char **)malloc( (1+nval) * sizeof(*val) );
	for ( i=1; i<=nval; i++ ) {
		OpenIsisRec *r = openIsisReadRow( db, i );
		val[i] = 0;
		if ( ! r ) {
			openIsisSMsg( 2, "no rec %d\n", i );
			continue;
		}
		if ( 0 < r->len ) {
			char *c = malloc( r->field[0].len + 1 );

			memcpy( c, r->field[0].val, r->field[0].len );
			c[ r->field[0].len ] = 0;
			val[i] = c;
		}
		free( r );
	}
	l = millis(&tv);
	openIsisSMsg( fd, "sequential read %d rows in %.3f seconds %d rows per sec\n",
		nval, l/1000., nval*1000/(l?l:1) );

	def.start = 0;
	i = 0;
	run( &def );
	l = millis(&tv);
	run( &def );
	l = millis(&tv);
	run( &def );
	l = millis(&tv);
	openIsisSMsg( fd, "in-thread run in %.3f seconds %d rows per sec\n",
		l/1000., p*1000/(l?l:1) );
#ifdef HAVE_THREADS
#define passes 3
	{
	static int nThreads[] = { 8, 2, 1, 4 };
	int res[ sizeof(nThreads)/sizeof(nThreads[0]) ];
	pthread_t th[ 8 /* max. nThreads */ ];
	threadarg arg[ sizeof(th)/sizeof(th[0]) ];
	int j;


	for ( i=0; i<(int)(sizeof(nThreads)/sizeof(nThreads[0])); i++ )
		res[i] = 0;
	for ( j=0; j<(int)(sizeof(th)/sizeof(th[0])); j++ )
#if 0
		if ( ! (arg[j].ses = openIsisSesGet( -1, 0 )) ) {
			openIsisSMsg( fd, "could not get %dth session\n", j );
			return 1;
		}
#endif
	for ( pass=0; pass<passes; pass++ )
		for ( i=0; i<(int)(sizeof(nThreads)/sizeof(nThreads[0])); i++ ) {
			int rps, avg;
			millis(&tv);
			assert( nThreads[i] <= (int)(sizeof(th)/sizeof(th[0])) );
			for ( j=0; j<nThreads[i]; j++ ) {
				arg[j].start = j*nterm/nThreads[i];
				if ( pthread_create( th+j, 0, run, arg+j ) )
					th[j] = (pthread_t)0;
			}
			openIsisSMsg( fd, "started %d threads\n", nThreads[i] );
			avg = 0;
			for ( j=0; j<nThreads[i]; j++ ) {
				int t;
				pthread_join( th[j], (void**)&t );
				avg += t;
			}
			avg /= nThreads[i];
			l = millis(&tv);
			rps = (int)(nThreads[i] * p * 1000 / avg);
			openIsisSMsg( fd, "joined %d threads avg %.3f after %.3f seconds %d rows per sec\n",
				nThreads[i], avg/1000., l/1000., rps );
			res[i] += rps;
		}
	for ( i=0; i<(int)(sizeof(nThreads)/sizeof(nThreads[0])); i++ )
		openIsisSMsg( fd, "%d threads  %d rows per sec\n", nThreads[i], res[i]/passes );
	}
#else
	(void)pass; /* avoid compiler warning */
#endif	/* HAVE_THREADS */
	return 0;
}	/* crash */


/* ************************************************************
	package functions
*/
/* ************************************************************
	public functions
*/
int main ( int argc, const char **argv )
{
	int ret = 0;
	int i,intarg=0;
	todo what = DO_DUMP;
	format fmt = FMT_TXT;
	int check = OPENISIS_CHK_FIX;
	int searchmode = OPENISIS_QRY_KEYAT;
	int idxmode = OPENISIS_IDXPF;
	const char *search = 0;
	const char *idxto = 0;
	int needdb = !0;
	const char *dowrite = 0;
	ifmode ifm = IFM_DUMP;
	OpenIsisRec *argr;
	OpenIsisDb *odb;

	/* initialize minimal env */
	openIsisCOpen(0);
	argr = openIsisRSet( 0,
		OPENISIS_RFDT|OPENISIS_RARGV|OPENISIS_RIGN | (argc-1),
		openIsisFdtSyspar, argv+1 );

	if ( 2 == argc && ! strcmp("-version",argv[1]) ) {
		openIsisSMsg( 0, "%s\n", OPENISIS_VERSION );
		goto bye;
	}
	/* check options ... */
	for ( i=0; i < argc; ) {
		const char *n = argv[i], *v = 0;
		assert( n );
		if ( '-' == n[0] )
			n++;
		if ( 1 == argc - i || '-' == argv[i+1][0] ) { /* no value */
			i++;
		} else {
			v = argv[i+1];
			assert( v );
			i += 2;
		}

		if ( argchk("logfile",n,v) )
			openIsisLog( '=', v );
		else if ( argchk("v",n,v) )
			openIsisLog( *v, 0 );
		else if ( argchk("scan",n,v) ) {
			what = DO_SCAN;
			search = v;
		}
		else if ( argchk("search",n,v) ) {
			what = DO_SEARCH;
			search = v;
		}
		else if ( argchk("upto",n,v) ) {
			idxmode = OPENISIS_IDXUPTO;
			idxto = v;
		}
		else if ( argchk("incl",n,v) ) {
			idxmode = OPENISIS_IDXINCL;
			idxto = v;
		}
		else if ( argchk("query",n,v) ) {
			what = DO_SEARCH;
			searchmode = OPENISIS_QRY_SIMPLE;
			search = v;
		}
		else if ( argchk("terms",n,v) ) {
			what = DO_TERMS;
			search = v;
		}
		else if ( argchk("perf",n,v) ) {
			what = DO_PERF;
			intarg = atoi(v);
		}
		else if ( argchk("crash",n,v) ) {
			what = DO_CRASH;
			search = v;
		}
		else if ( argchk("split",n,v) ) {
			what = DO_SPLIT;
			search = v;
			needdb = 0;
		}
		else if ( argchk("fmt",n,v) ) {
			if ( ! strcmp("mfn",v) )
				fmt =FMT_MFN;
			else if ( ! strcmp("mfnf",v) )
				fmt =FMT_MFNF;
			else if ( ! strcmp("prop",v) )
				fmt =FMT_PROP;
			else if ( ! strcmp("txt",v) )
				fmt =FMT_TXT;
			else if ( ! strcmp("txtw",v) )
				fmt =FMT_TXTW;
		}
		else if ( ! strcmp("check",n) )
			what = DO_CHECK;
		else if ( ! strcmp("vutf",n) ) {
			what = DO_VUTF;
			needdb = 0;
		} else if ( argchk("pft",n,v) )
			pft = v;
		else if ( ! strcmp("stream",n) ) {
			what = DO_STREAM;
			needdb = 0;
		} else if ( argchk("write",n,v) )
			dowrite = v;
		else if ( argchk("append",n,v) ) {
			dowrite = v;
			append = !0;
		} else if ( ! strcmp("idxall",n) )
			idxall = !0;
		else if ( ! strcmp("mfnlist",n) )
			what = DO_MFNLIST;
		else if ( argchk("ifload",n,v) ) {
			what = DO_IFLOAD;
			intarg = atoi(v);
		} else if ( ! strcmp("swload",n) ) {
			what = DO_SWLOAD;
		} else if ( ! strcmp("ifadd",n) ) {
			what = DO_IFLOAD;
			intarg = -1;
		} else if ( ! strcmp("ifdel",n) ) {
			what = DO_IFLOAD;
			intarg = -2;
		} else if ( ! strcmp("ifcopy",n) ) {
			what = DO_IFDUMP;
			ifm = IFM_COPY;
		} else if ( ! strcmp("ifchk",n) ) {
			what = DO_IFDUMP;
			ifm = IFM_CHK;
		} else if ( ! strcmp("iftab",n) ) {
			what = DO_IFDUMP;
			ifm = IFM_TAB;
		} else if ( ! strcmp("ifdump",n) )
			what = DO_IFDUMP;
		else if ( ! strcmp("noxi",n) )
			ifm = IFM_OLD;
		else if ( argchk("out",n,v) ) {
			char buf[256] = ">";
			int l = strlen(v);
			if ( l < 254 ) {
				memcpy( buf+1, v, l );
				buf[l+2] = 0;
				openIsisSOpen( buf, 0, 0 );
			}
		}
		else if ( argchk("fdtdump",n,v) ) {
			what = DO_FDT;
			intarg = atoi(v);
		}
	}	/* while argc */


	if ( needdb && 0 > (db = openIsisOpen( 0, argv + 1, argc - 1 )) ) {
		openIsisSMsg( 2,
			"openisis " OPENISIS_VERSION "\n\n"
			"please specify a valid database with -db, e.g.\n"
			"-db /winisis/data/cds\n"
			"\n"
			"other options are:\n"
			"-search term      search for term\n"
			"-query \"query\"    run a query like \"water * plant\"\n"
			"-terms term       list terms matching term (e.g. plant$)\n"
		);
		/* warning: string length `580' is greater than the minimum length
		 * `509' ISO C89 is required to support
		*/
		openIsisSMsg( 2,
			"-fmt mfn          for a search or query, list only the mfn\n"
			"-fmt mfnf         for a search or query, list the mfn and 1st field\n"
			"-pft \"pft\"        use printformat (currently very limited)\n"
			"-write dbpath     specify a db where records are written to\n"
			"-mfnlist          read mfns from stdin\n"
			"-ifload pctfree   read .lk2-index from stdin\n"
			"\n"
			"default output format is one field per line like tag<TAB>value\n"
		);
		ret = 1;
		goto bye;
	}

	if ( dowrite && 0 > (wdb = openIsisOpen( dowrite, 0, 0 )) ) {
		openIsisSMsg( 2, "could not open write target db '%s'\n", dowrite );
		ret = 2;
		goto bye;
	}
		

	switch ( what ) {
	case DO_DUMP: {
		int max = openIsisMaxRowid( db );
		int rowid;
		for ( rowid = 1; rowid <= max; rowid++ )
			printid( rowid, fmt );
	} break; /* DO_DUMP */
	case DO_MFNLIST: {
		char *buf = 0;
		int l;
		while ( 0 <= (l = openIsisSReadln( &buf )) ) {
			int id = 0;
			while ( l-- )
				id = 10*id + *buf++ - '0';
			if ( id )
				printid( id, fmt );
		}
	} break; /* DO_MFNLIST */
	case DO_SCAN: {
		int max = openIsisMaxRowid( db );
		int rowid;
		for ( rowid = 1; 0 < rowid && rowid <= max; rowid++ )
			rowid = print( openIsisScan( db, rowid, 0, search ), !0, fmt );
	} break; /* DO_SCAN */
	case DO_SEARCH: {
		int cnt;
		OpenIsisSet set;
		set.len = 0;
		openIsisQuery( &set, db, search, searchmode, 0 );
		if ( 0 >= set.len ) {
			openIsisSMsg( 2, "no results for '%s'\n", search );
			ret = 1;
			goto bye;
		}
		/* openIsisSMsg( 2, "%d\trows for\t%s\n", set.len, search ); */
		for ( cnt = 0; cnt < set.len; cnt++ )
			printid( set.id[cnt], fmt );
	} break; /* DO_SEARCH */
	case DO_TERMS: {
		union { OpenIsisRec r; char buf[10000]; } x;
		x.r.len   = 0;
		x.r.bytes = sizeof(x);
		while ( openIsisTerm( &x.r, db, search ) && x.r.len ) {
			/* openIsisSMsg( 1, "%d terms\n", x.r.len ); */
			for ( i=0; i<x.r.len; i++ )
				openIsisSMsg( 1, "%.*s\n", (int)x.r.field[i].len, x.r.field[i].val );
		}
	} break; /* DO_TERMS */
	case DO_PERF: {
		int max = openIsisMaxRowid( db );
		while ( 0 < intarg-- ) {
			OpenIsisRec *r = openIsisReadRow( db, 1+((int)rand() % max) );
			free( r );
		}
	} break; /* DO_PERF */
	case DO_CHECK:	
		ret = openIsisCheck( db, check );
		goto bye;
	case DO_CRASH:	
		ret = crash( search );
		goto bye;
	case DO_SPLIT: {
		OpenIsisField f;
		OpenIsisRec *r;
		f.tag = 24; f.val = search; f.len = strlen(search);
		r = openIsisReadField( 0, &f );
		if ( r )
			for ( i=0; i<r->len; i++ )
				openIsisSMsg( 1, "%c=%.*s\n", (int)r->field[i].tag,
					(int)r->field[i].len, r->field[i].val );
	}	break;
	case DO_STREAM: {
		OpenIsisIos ios;
		OpenIsisRecStream rs = { 0, OPENISIS_STOPONEMPTY, 0, 0, 0 };
		LIO_SINIT( &ios, lio_stdio, "stdin", LIO_IN );
		rs.in = &ios; /* some gcc versions need it this way */
		while ( 0 < (i = openIsisSGetr( &rs )) )
			print( rs.rec, 0, fmt );
	}	break;
	case DO_IFLOAD:
	case DO_SWLOAD: {
		OpenIsisKey key;
		OpenIsisHit hit;
		OpenIsisIndex idx = openIsisIdxOpen( db, intarg );
		char *buf = 0;
		int l, lines = 0;

		memset( &hit, 0, sizeof(hit) );
		hit.dbn = (-2 == intarg) ? 0xffff : 0; /* secret key for ifdel */
		while ( 0 <= (l = openIsisSReadln( &buf )) && buf ) {
			char *t = memchr( buf, '\t', l );
			if ( DO_SWLOAD == what ) {
				memcpy( key.byt, buf, key.len = (unsigned char)l );
			} else if ( t ) { /* tab delimited */
				key.len = (unsigned char)(t - buf);
				memcpy( key.byt, buf, key.len );
				if ( 0 >= (l -= t-buf+1) || !(t = memchr( buf=t+1, '\t', l )) )
					continue;
				hit.mfn = (unsigned) openIsisA2i( buf, t-buf );
				if ( 0 >= (l -= t-buf+1) || !(t = memchr( buf=t+1, '\t', l )) )
					continue;
				hit.tag = (unsigned short) openIsisA2i( buf, t-buf );
				if ( 0 >= (l -= t-buf+1) || !(t = memchr( buf=t+1, '\t', l )) )
					continue;
				hit.occ = (unsigned short) openIsisA2i( buf, t-buf );
				if ( 0 >= (l -= t-buf+1) )
					continue;
				hit.pos = (unsigned short) openIsisA2i( t+1, l );
			} else {
				/* 10/30 key BLANK 7 mfn BLANK 5 tag BLANK 4 occ BLANK 4 pos*/
				int eok = l - 24; /* pos of blank after key, 10 or 30 */
				if ( 54 != l && 34 != l ) {
					openIsisSMsg( OPENISIS_ERR_INVAL,
						"bad ifload input len %d, want 34 or54 bytes + newline\n", l );
					break;
				}
				for ( i=eok-1; ' ' == buf[i] && i--; )
					;
				key.len = (unsigned char) (++i);
				memcpy( key.byt, buf, key.len );
				log_msg( LOG_VERBOSE, "'%.*s'", 7, buf+eok+1 );
				hit.mfn = (unsigned) openIsisA2i( buf+eok+1, 7 );
				hit.tag = (unsigned short)openIsisA2i( buf+eok+9, 5 );
				hit.occ = (unsigned short)openIsisA2i( buf+eok+15, 4 );
				hit.pos = (unsigned short)openIsisA2i( buf+eok+20, 4 );
			}
			log_msg( LOG_VERBOSE, "'%.*s' %d %d %d %d",
				key.len, key.byt, hit.mfn, hit.tag, hit.occ, hit.pos );
			if ( openIsisIdxAdd( idx, &key, &hit ) )
				break;
			if ( !(++lines & 0x3ff) )
				log_msg( LOG_INFO, "%dK lines", lines >> 10 );
		}
		openIsisIdxDone( idx );
	}	break;
	case DO_IFDUMP: {
		OpenIsisIdxLoop l;
		memset( &l, 0, sizeof(l) );
		l.flg = idxmode;
		switch ( ifm ) {
		case IFM_OLD:
			l.flg |= OPENISIS_IDXTRAD;
		case IFM_DUMP:
			l.cb = (OpenIsisIdxCb*)printlk2;
			break;
		case IFM_TAB:
			l.cb = (OpenIsisIdxCb*)printtab;
			break;
		case IFM_COPY:
			l.flg |= OPENISIS_IDXTRAD;
			l.me = openIsisIdxOpen( 0 <= wdb ? wdb : db, 0 );
			l.cb = (OpenIsisIdxCb*)openIsisIdxAdd;
			break;
		case IFM_CHK:
			/* nuttin */
			break;
		}
		if ( search )
			memcpy( l.key.byt, search,
				l.key.len = (unsigned char)strlen( search ) );
		if ( idxto )
			memcpy( l.to.byt, idxto,
				l.to.len = (unsigned char)strlen( idxto ) );
		openIsisIdxLoop( db, &l );
		if ( IFM_COPY == ifm )
			openIsisIdxDone( (OpenIsisIndex)l.me );
	}	break;
	case DO_FDT:
		odb = ldb_getdb( db );
		if ( odb && odb->fdt )
			print( openIsisFFdt2Rec( odb->fdt, 0, intarg ), 0, fmt );
		break;
	case DO_VUTF: {
		char buf[1024];
		int t = 0, f = 0, g;
		while ( 0 < (g = lio_read( &lio_in, buf, sizeof(buf) )) ) {
			int l = openIsisValidUTF8( buf, g, &f );
			if ( l ) {
				openIsisSMsg( OPENISIS_ERR_INVAL,
					"at total %d = %d+%d\n", l-1+t, l-1, t );
				ret = 1;
				goto bye;
			}
			t += g;
		}
	}
	}	/* switch ( what ) */
bye:
	if ( 0 <= db )
		openIsisClose( db );
	if ( 0 <= wdb )
		openIsisClose( wdb );

	/* at least with WINE,
		atexit cleanup is not performed
		unless we explicitly call exit :(
	*/
	exit( ret );
	return ret;
}	/* openisis main */