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: ldb.c,v 1.95 2003/06/10 11:00:34 kripke Exp $
	implementation of general db access functions.
*/

#include <stdlib.h>
#include <string.h>
#include <limits.h> /* PATH_MAX */
#include <errno.h>


/* special */
#if defined( __GNUC__ ) && defined ( alloca )
#include <alloca.h>
#endif

/* gcc defines always a cpu type - this we use for byteorder checking */
#if defined( sparc ) || defined( __ppc__ )
#	define LDB_BIG_ENDIAN
/* TODO: figure out fastest "htonl" on those boxes that usually don't swap */
static int rvi ( int i ) {
	int r;
	((char*)&r)[0] = ((char*)&i)[3];
	((char*)&r)[1] = ((char*)&i)[2];
	((char*)&r)[2] = ((char*)&i)[1];
	((char*)&r)[3] = ((char*)&i)[0];
	return r;
}
static short rvs ( short i ) {
	short r;
	((char*)&r)[0] = ((char*)&i)[1];
	((char*)&r)[1] = ((char*)&i)[0];
	return r;
}
#define SWI( i ) i = rvi( i )
#define SWS( s ) s = rvs( s )
#else
# define rvi(i) i
# define rvs(s) s
#define SWI( i )
#define SWS( s )
#endif
#if defined( sparc )
#	define LDB_NEEDALIGN
#endif
#if defined( LDB_NEEDALIGN )
static unsigned GETINT ( const void *m )
{
	unsigned l;
	memcpy( &l, m, 4 );
	return l;
}
static unsigned short GETSHORT ( const void *m )
{
	unsigned short s;
	memcpy( &s, m, 2 );
	return s;
}
#else
#define GETINT( m ) (*(unsigned*)(m))
#define GETSHORT( m ) (*(unsigned short*)(m))
#endif

#include "lstr.h"
#include "lio.h"
#include "lbt.h"
#include "lcs.h"
#include "ldb.h"
#include "lfdt.h"
#include "luti.h"

#ifdef WIN32
#define IsAbsPath(p) \
	((p) && *(p) && ( \
		'/' == *(p) || '\\' == *(p) || ( \
		':' == (p)[1] && ( \
			'/' == (p)[2] || '\\' == (p)[2] \
		))))
#else
#define IsAbsPath(p) \
	((p) && '/' == *(p))
#endif


#define LF 10 /* LineFeed a.k.a. newline - '\n' isn't really well defined */
#define TAB 9 /* horizontal, that is */
#define VT 11 /* vertical, used as newline replacement */

/* ************************************************************
	private types
*/

/** extension of master file proper. */
static const char EXT_MST_MST[] = ".mst";
/** extension of master file xref. */
static const char EXT_MST_XRF[] = ".xrf";

typedef enum {
	MST_MST,
	MST_XRF,
	MST_FILES
} mst_file;

static const char * const EXT_MST[MST_FILES] = {
	EXT_MST_MST,
	EXT_MST_XRF
};

/** extension of inverted file short term nodes. */
static const char EXT_INV_N01[] = ".n01";
/** extension of inverted file short term leaves. */
static const char EXT_INV_L01[] = ".l01";
/** extension of inverted file long term nodes. */
static const char EXT_INV_N02[] = ".n02";
/** extension of inverted file long term leaves. */
static const char EXT_INV_L02[] = ".l02";
/** extension of inverted file control records. */
static const char EXT_INV_CNT[] = ".cnt";
/** extension of inverted file postings. */
static const char EXT_INV_IFP[] = ".ifp";

typedef enum {
	INV_N01,
	INV_L01,
	INV_N02,
	INV_L02,
	INV_CNT,
	INV_IFP,
	INV_FILES
} inv_file;

static const char * const EXT_INV[INV_FILES] = {
	EXT_INV_N01,
	EXT_INV_L01,
	EXT_INV_N02,
	EXT_INV_L02,
	EXT_INV_CNT,
	EXT_INV_IFP
};

/** extension of lbt B-Link-Tree.
	It's named oxi because that is nicer than oix for OpenIsis indeX.
	however, see http://www.oxicenter.com.br/
*/
static const char EXT_LBT_OXI[] = ".oxi";
static const char * const EXT_LBT[] = {
	EXT_LBT_OXI
};


/** plaintext master file
*/
static const char EXT_TXT_TXT[] = ".txt";
static const char EXT_TXT_PTR[] = ".ptr";
static const char EXT_TXT_OPT[] = ".opt";
typedef enum {
	TXT_TXT,
	TXT_PTR,
	TXT_FILES
} txt_file;
static const char * const EXT_TXT[] = {
	EXT_TXT_TXT,
	EXT_TXT_PTR
};

static const char ISIX[] = "ISIX"; /* ptr magic */


/** extension of supporting file alpha character table. */
static const char EXT_SUP_ACT[] = ".act";
/** extension of supporting file uppercase table. */
static const char EXT_SUP_UCT[] = ".uct";

typedef enum {
	SUP_ACT,
	SUP_UCT,
	SUP_FILES
} sup_file;

static const char * const EXT_SUP[SUP_FILES] = {
	EXT_SUP_ACT,
	EXT_SUP_UCT
};


typedef int lblk[128];


typedef struct {
	Db          head;
	int         flags;
	const char *path;
	int         mst[MST_FILES]; /* master file */
	int         inv[INV_FILES]; /* primary inverted file */
	int         mfc[LMFC__FL]; /* master file control record */
	unsigned    mflen; /* master file length */
	int         xrf[129]; /* last used xrf block : THREAD THREAT */
	int         xrlen; /* length of xrf (in blocks) */
	unsigned short ptr; /* type of pointer file (new style xrf) */
	unsigned short ptrl; /* pointer bytes, 512 for old xrf */
	char       *mmap; /* memory map of xrf/ptr */
	int         mmlen; /* length of map (in ptrl) */
	int         cnt[LDB_INDEXES][LCNT__FL]; /* two cnt records */
	short       tlen[LDB_INDEXES]; /* max term length for each index */
	LcsTab      ctab[LCS__TABS];
	Idx         oxi;
} LDb;


typedef union {
	lll bar;
	char r[16];
} Ptr;


/* db flags */
#define DB_OPEN     0x010000
#define DB_INVOPEN  0x020000
#define DB_LBTOPEN  0x040000
#define DB_TXTOPEN  0x080000
#define DB_WRITABLE 0x100000
#define DB_MODIFIED 0x200000

#define DB_TXTMODE  0x20
#define DB_MMAP  0x10
#define DB_VARI 0xf /* mask for variant */


/* get xstr for record rec in set */
#define DB_XSTR( db, set, rec ) \
	lstrlib[ set ].desc[ DB_VARI & (db)->flags ][ rec ]
/* get record names for record rec in set */
#define DB_RNAM( db, set, rec ) \
	lstrlib[ set ].name[ rec ]


/** packed little endian masterfile control structure.
*/
typedef struct Mfc {
	int   ctlm;
	int   nmfn;
	int   nmfb;
	short nmfp;
	short type;
	int   rcnt;
	int   mfx1;
	int   mfx2;
	int   mfx3;
} Mfc;


/** packed little endian masterfile record.
*/
typedef struct Dict {
	short tag;
	short pos;
	short len;
} Dict;

/** packed little endian masterfile record.
*/
typedef struct Mfr {
	int   mfn;
	short recl; /* a.k.a. mfrl */
	short bwbl; /* low part of int */
	short bwbh; /* high part of int */
	short bwp;
	/* it is believed, that this first five fields up to here (12 bytes packed)
	are to be in one 512-byte block; the manual mentiones even 14 bytes ... ???
	*/
	short base;
	short nvf;
	short stat;
	Dict  dict[1];
} Mfr;



/* ************************************************************
	private data
*/

static LDb defdbspace[32];
/* array of open dbs. should expand dynamically. */
static LDb *dbs = defdbspace;
static int dbs_len = sizeof(defdbspace)/sizeof(defdbspace[0]);

static int init;


/* ************************************************************
	private functions
*/
static LDb *getDb ( int id )
{
	if ( 0 <= id && id < dbs_len && dbs[id].flags ) {
		return &dbs[id];
	}
	log_msg( LOG_ERROR, "attempt to access bad db id %d", id );
	return 0;
}	/* getDb */


/* ************************************************************
	start of io section
*/
enum {
	/* additional flags in the LIO_FD range */
	OPEN_TRY = 1, /* try writable, open readonly else */
	OPEN_UC  = 2, /* use uppercase ext */
	/* commonly used combinations */
	/* 1) open as is, do not complain about any failure, do not create */
	OPEN_ASIS  = LIO_SEEK|LIO_RDWR|OPEN_TRY,
	/* 2) open readonly, do not complain about any failure, do not create */
	OPEN_RDIF  = LIO_SEEK|LIO_RD|LIO_TRY,
	/* 3) open readonly, complain about any failure */
	OPEN_RD    = LIO_SEEK|LIO_RD,
	/* 4) open or create writable, complain on failure */
	OPEN_NEW   = LIO_SEEK|LIO_RDWR|LIO_CREAT,
	OPEN_BLANK = LIO_SEEK|LIO_RDWR|LIO_CREAT|LIO_TRUNC
};

/* figure out wether to use uppercase extension on path.
	if last path component (everything after the last / and \)
	does contain an uppercase ascii and does not contain a lowercase ascii,
	return OPEN_UC, else 0.
*/
static int autocase ( const char *path )
{
	int ret = 0;
	const char *e = path + strlen( path );
	while ( e-- > path )
		if ( 'A'<=*e && *e<= 'Z' )
			ret = OPEN_UC;
		else if ( 'a'<=*e && *e<= 'z' )
			return 0;
		else if ( '/'==*e || '\\' == *e )
			break;
	return ret;
}

/* set extension. fname MUST already end with .xxx.
	if how has OPEN_UC set, use uppercase extension
*/
static char *setext ( char *fname, const char *ext, int how )
{
	int l = strlen( fname ) - 4;
	memcpy( fname+l, ext, 4 );
	if ( OPEN_UC & how ) {
		char *p = fname+l;
		for ( ;*p; p++ ) /* use uppercase extensions */
			if ( 'a' <= *p && *p <= 'z' )
				*p -= 'a'-'A';
	}
	return fname;
}

/**
	try to open all files according to how.
	ldb is only interested in seekable readable true files.
	@return
		1 if all files could be opened writable
		0 if all files could be opened readonly,
			and that was requested by a RD mode or try write
		something negative else
*/
static int openfiles ( int *fid, char *path,
	const char * const *ext, int nfiles, int how )
{
	int i;
	int wr = LIO_WR&how ? 1 : 0, mode = LIO_WANT & how;

	for ( i=0; i<nfiles; i++ ) {
		setext( path, ext[i], how );
		fid[i] = lio_open( path, mode & LIO_WANT );
		log_msg( LOG_INFO, "opening file '%s' %c 0x%x",
			path, wr ? 'w' : 'r', fid[i] );
		if ( 0 < fid[i] ) { /* ok */
			mode &= ~LIO_FLOCK; /* lock only leading file */
			continue;
		}
		fid[i] = 0;
		while ( i-- ) /* close others */
			lio_close( &fid[i], LIO_INOUT );
		if ( OPEN_TRY & how )
			return openfiles( fid, path, ext, nfiles,
				(how & ~(OPEN_TRY|LIO_WR)) | LIO_TRY );
		return LIO_TRY&how ? -ERR_BADF /* silent */
			: log_msg( LOG_SYSERR, "could not open file '%s' for %sing",
			path, wr ? "writ" : "read" );
	}
	return wr; /* good */
}	/* openfiles */


static int closefiles ( int *fid, int nfiles )
{
	int ret = 0, i;
	for ( i=0; i<nfiles; i++ )
		if ( 0 < fid[i] && LIO_INOUT & fid[i] )
			lio_close( &fid[i], LIO_INOUT );
	return ret;
}	/* closefiles */


static int readblk ( void *dst, int siz, int fid, int where )
{
	int got;
	got = lio_pread( &fid, dst, siz, where );
	if ( 0 > got )
		return got;
#ifndef NDEBUG
	if ( LOG_DO( LOG_ALL ) )
		LOG_HEX( dst, got );
#endif
	if ( siz == (int)got )
		return 0;
	log_msg( LOG_WARN, "got %u bytes wanted %d at %d in 0x%x",
		got, siz, where, fid );
	return 1+(int)got;
}	/* readblk */


/* ************************************************************
	end of io section
*/

static int *nrec ( int *xstr )
{
	int *dst = (int*)mAlloc( xstr[LSTR_ILEN] );
	if ( dst )
		*dst = *xstr;
	return dst;
}	/* nrec */



typedef struct b8 { char x[8]; } b8;
typedef struct b4 { char x[4]; } b4;
typedef struct b2 { char x[2]; } b2;


static int convert ( int *dst, char *src, int *xstr )
{
	static int pow2[] = { 1, 2, 4, 8 };
	int occ = -1;
	int *xmbrs = xstr+LSTR_XMBR;
	int nmbrs  = LSTRFIX(*xstr);
	int *mbr;
	char *buf, *part = src, *srcend = src + xstr[LSTR_XLEN];

	if ( ! dst )
		return log_msg( ERR_NOMEM, "out of memory (no dst) in convert" );
	if ( LSTRLEN(*xstr) > xstr[LSTR_ILEN] )
		return log_msg( ERR_INVAL, "bad ilen %d need %d",
			xstr[LSTR_ILEN], (int)LSTRLEN(*xstr) );
	if ( *dst != *xstr )
		return log_msg( ERR_INVAL, "bad *dst 0x%08x need 0x%08x", *dst, *xstr );
	/* clean and re-init */
	memset( dst, 0, xstr[LSTR_ILEN] );
	*dst = *xstr;
	mbr  = dst+1;
	buf  = ((char*)dst) + LSTRLEN(*dst);

	/* cvt the fixed part (occ==-1) and each occurrence of repeated part. */
	for ( ;/* occ < LSTROCC(*dst) */; ) { /* cvt one part */
		int i;
		for ( i=0; i<nmbrs; i++, mbr++ ) { /* assign one xmbr */
			int xmbr = xmbrs[i];
			char *s = part + LONG2OFF(xmbr);
			int sbytes = srcend - s;
			int len,j;
			union {
				char buf[8];
				b8   x8;
				b4   x4;
				b2   x2;
				short s;
				int  i;
				lll  ll;
			} num;
			if ( LMBRISNUM( xmbr ) ) {
				/* numeric data */
#ifdef LDB_BIG_ENDIAN
#	define NEEDSWAP(xmbr) ( ! ( LMBR_FHIE & (xmbr) ) )
#else
#	define NEEDSWAP(xmbr) (LMBR_FHIE & (xmbr))
#endif
				int ld = LMBRLD(xmbr);
#ifndef LDB_NEEDALIGN
				if ( ! NEEDSWAP( xmbr ) ) {
					/* much faster this way ... */
					/* TODO: len and bit checks; actually not needed yet ... */
					switch ( ld ) {
					case 3: *mbr = *(lll*)s; break;
					case 2: *mbr = *(int*)s; break;
					case 1: *mbr = *(short*)s; break;
					case 0: *mbr = *s; break;
					}
					continue;
				}
#endif
				len = pow2[ LMBRLD(xmbr) ];
				if ( len > sbytes )
					return log_msg( ERR_INVAL,
						"srcbuf too short %d have %d need %d occ %d mbr %d",
						xstr[LSTR_XLEN], sbytes, len, occ, i );
				if ( !NEEDSWAP( xmbr ) )
					/* for ( j = len; j--; ) num.buf[j] = s[j]; */
					switch ( ld ) {
					case 3: num.x8 = *(b8*)s; break;
					case 2: num.x4 = *(b4*)s; break;
					case 1: num.x2 = *(b2*)s; break;
					case 0: num.buf[0] = *s; break;
					}
				else /* swap bytes */
					for ( j = len; j--; )
						num.buf[j] = s[len - 1 - j];
				switch ( len ) {
				case 8: *mbr = num.ll; break;
				/* TODO: defines for 16 and 64 bit compilers */
				case 4: *mbr = num.i; break;
				case 2: *mbr = num.s; break;
				case 1: *mbr = num.buf[0]; break;
				}
				if ( LMBRISBITS( xmbr ) ) { /* apply bit shift and mask */
					*mbr >>= LMBRBITOFF( xmbr );
					*mbr &= ~(-1L << LMBRBITLEN( xmbr ));
				}
				continue;
			} 
			/* else raw data -- that's easy :) */
			{
				int offset = buf - (char*)dst;
				int need;
				len = LONG2LEN(xmbr);
				need = offset + len;
				if ( need > xstr[LSTR_ILEN] )
					return log_msg( ERR_INVAL,
						"bad buflen %d need %d+%d in occ %d mbr %d",
						xstr[LSTR_ILEN], offset, len, occ, i );
				if ( len > sbytes )
					return log_msg( ERR_INVAL,
						"srcbuf too short %d have %d need %d occ %d mbr %d",
						xstr[LSTR_XLEN], sbytes, len, occ, i );
				memcpy( buf, s, len );
				*mbr = buf - (char*)dst;
				buf += len;
			}
		}	/* for mbrs */

		if ( ++occ >= LSTROCC(*dst) )
			break;
		if ( occ )
			part += (short)xstr[LSTR_XRLO]; /* adv. rep. part len */
		else { /* was the fixed part, setup for repeated */
			nmbrs = LSTRREP(*xstr);
			part  += (short)(xstr[LSTR_XRLO]>>16); /* adv. rep. part off */
			xmbrs += i;
		}
	}
	return 0;
}	/* convert */


static int readrec ( int *dst, int fid, int where, int *xstr )
{
	char *buf = (char *)
#ifdef alloca
		alloca( xstr[LSTR_XLEN] )
#else
		mAlloc( xstr[LSTR_XLEN] )
#endif
	;
	int got = 0;
	int ret = 0;

	if ( ! buf )
		return -ERR_NOMEM;
	if ( 0 > where )
		where = -where * xstr[LSTR_XLEN];
	got = readblk( buf, xstr[LSTR_XLEN], fid, where );
	ret = got ? got : convert( dst, buf, xstr );
#ifndef alloca
	mFree( buf );
#endif

	return ret;
}	/* readrec */


/* read and log */
static int readlog ( int *dst, int fid, int where, LDb *db, int set, int rec )
{
	int ret = readrec( dst, fid, where, DB_XSTR( db, set, rec ) );
	if ( !ret && LOG_DO( LOG_TRACE ) )
		LOG_STR( dst, lstrlib[ set ].name[ rec ] );
	return ret;
}	/* readlog */



/* ************************************************************
	access functions for the record types
*/

static int getOff ( LDb *db, int rowid, int xr )
{
	int rowix = rowid - 1; /* mfns count from 1 */
	int xrf_block = rowix / 127;
	int blkix = xrf_block + 1; /* ... so do xrf blocks */
	int off;
	if ( xrf_block < db->mmlen ) {
		if ( xr )
			((lblk*)db->mmap)[xrf_block][1+(int)(rowix % 127)] = rvi( xr );
		else
			xr = rvi( ((lblk*)db->mmap)[xrf_block][1+(int)(rowix % 127)] );
	} else {
		int err = 0;
		/* if ( LIO_LOCK() ) return -3; */
		if ( xr ) { /* write */
			if ( blkix <= db->xrlen ) {
				SWI( xr );
				if ( 4 != lio_pwrite( &db->mst[MST_XRF], &xr, 4,
					xrf_block*512 + 4*(1 + (rowix % 127))) )
					return 0;
			} else { /* extent */
				lblk extend;
				memset( extend, 0, sizeof(extend) );
				while ( db->xrlen < blkix ) { /* extend */
					extend[0] = db->xrlen + 1; /* set blk id */
					if ( blkix == extend[0] )
						extend[1+(int)(rowix % 127)] = rvi( xr );
					SWI( extend[0] );
					if ( 512 != lio_pwrite( &db->mst[MST_XRF], extend, 512,
						db->xrlen*512) )
						return 0;
					db->xrlen++;
				}
			}
			if ( blkix == db->xrf[LXRF_XPOS] )
				db->xrf[ LXRF_XREC + (int)(rowix % 127) ] = xr;
			/* and go on read it back, just to check ... */
		}
		if ( blkix != db->xrf[LXRF_XPOS] ) {
			int ret;
			LOG_DBG( LOG_VERBOSE, "fetching xrf block %d had %d",
				blkix, ! db->xrf ? -1 : db->xrf[LXRF_XPOS] );
			ret = readlog( db->xrf, db->mst[MST_XRF],
				-xrf_block, db, LSET_MST, LSTR_XRF );
			if ( ret ) {
				log_msg( LOG_ERROR, "\twhen fetching xrf block %d", blkix );
				err = -1;
			} else if ( blkix == -db->xrf[LXRF_XPOS] ) {
				LOG_DBG( LOG_DEBUG, "hmmm ... negative" );
				db->xrf[LXRF_XPOS] = blkix;
			} else if ( blkix != db->xrf[LXRF_XPOS] ) {
				log_msg( LOG_WARN, "bad xrf %d wanted %d",
					db->xrf[LXRF_XPOS], blkix );
				err = -2;
			}
		}
		xr = db->xrf[ LXRF_XREC + (int)(rowix % 127) ];
		/* LIO_RELE(); */
		if ( err )
			return err;
	}
	/*
	21 bits (<<11) signed for the (512 byte) block ("xrmfb")
		1 for the first block (offset 0)
		0 means, never had such a record
		-1 and xrmfp=0: record removed from MST
		(there is no record at pos 0 in 1st block,
		since there resides the MST header)
		other negative value -x or pos!=0:
		record logically deleted, was at +x
	1 bit (1<<10): this record is new and not yet inverted
	1 bit (1<<9): this record is changed and not yet re-inverted
	9 bits for the block-relative position ("xrmfp")
	*/
	off = (((xr & 0xfffff800) >> 2) - 0x200) | (0x1ff & xr);
	if ( 0 < (xr & ~0x600) ) {
		LOG_DBG( LOG_DEBUG,
			"offset for rowid %d is %d (blk %d pos %d) flg 0x%08x at %d[%d]",
			rowid, off, (xr>>11)&0xfffff, xr&0x1ff, xr&0x80000600, blkix, rowix%127 );
		return off;
	}
	/* deleted */
	log_msg( LOG_INFO,
		"offset for rowid %d is %d (blk %d pos %d) flg 0x%08x at %d[%d]",
		rowid, off, (xr>>11)&0xfffff, xr&0x1ff, xr&0x80000600, blkix, rowix%127 );
	return 0;
}	/* getOff */


static int* getMfr ( LDb *db, int off, int *nxtoff )
{
	struct mfcxstr {
		int xstr[LSTR_LONGS(7+3)];
	} my = *(struct mfcxstr *)DB_XSTR( db, LSET_MST, LSTR_MFR ); 
	int head[1+7+3];
	int len = 0, base;
	int *rec = 0;
	char *buf = 0;
#ifdef alloca
	int notalloca = 0;
#endif

	LOG_DBG( LOG_VERBOSE, "getting MFR at off %d", off );
	if ( 498 < off % 512 )
		log_msg( LOG_WARN, "blk pos > 498 in offset 0x%08x", off );
	*head = *my.xstr;
	if ( readrec( head, db->mst[MST_MST], off, my.xstr ) ) {
		log_msg( LOG_ERROR, "\twhen reading MFR head at %d", off );
		return 0;
	}
	/* log_str( LOG_VERBOSE, head, lstrlib[LSET_MST].name[LSTR_MFR] ); */
	len = head[LMFR_RECL];
	LOG_DBG( LOG_VERBOSE, "got MFR %d reclen %d", head[LMFR_MFN], len );
	if ( nxtoff ) {
		*nxtoff = off + (0 < len ? len : -len); /* an odditiy */
		if ( 1 & *nxtoff ) /* an odditiy */
			(*nxtoff)++; /* round up to even */
		if ( 498 < *nxtoff % 512 ) {
			*nxtoff += 512;
			*nxtoff &= ~0x1ff;
		}
		if ( *nxtoff > db->mfc[LMFC_NMFB]*512 + db->mfc[LMFC_NMFP] ) {
			LOG_DBG( LOG_VERBOSE, "at end of db: %d > %d*512+%hd",
				*nxtoff, db->mfc[LMFC_NMFB], db->mfc[LMFC_NMFP] );
			*nxtoff = -1;
		}
	}
	if ( len < 0 ) {
		log_msg( LOG_INFO, "found deleted rec len %hd at offset %d", len, off );
		return 0;
	}

	/* check external base length */
	base = LONG2OFF(my.xstr[LSTR_XRLO])
		+ head[LMFR_NVF]*LONG2LEN(my.xstr[LSTR_XRLO]);
	if ( 0 > head[LMFR_BASE] || 0 > head[LMFR_NVF]
		|| 0x8fff < head[LMFR_NVF]
		|| len < head[LMFR_BASE] || len < base
		|| (head[LMFR_NVF] && head[LMFR_BASE] < base)
	) {
		log_msg( LOG_ERROR,
			"bad len %d base %d nvf %d need base %d at offset %d",
			len, head[LMFR_BASE], head[LMFR_NVF], base, off );
		/* check alignment problem */
		base = LONG2OFF(my.xstr[LSTR_XRLO])
			+ head[LMFR_STAT]*LONG2LEN(my.xstr[LSTR_XRLO]);
		if ( 0 > head[LMFR_NVF] || 0 > head[LMFR_STAT]
			|| 0x8fff < head[LMFR_STAT]
			|| len < head[LMFR_NVF] || len < base
			|| (head[LMFR_STAT] && head[LMFR_NVF] < base)
		) 
			;
		else
			log_msg( LOG_ERROR, "probably alignment problem, try -format aligned" );
		goto cleanup;
	}

	/* set up external structure for this rec */
	my.xstr[LSTR_SIZE] |= head[LMFR_NVF] << 16; /* occ of rep. part */
	my.xstr[LSTR_XLEN] = len;
	/* internal base length */
	base = LSTRLEN( *my.xstr );
	/* internal len adjusted for the slightly longer base */
	my.xstr[LSTR_ILEN] = len + base - head[LMFR_BASE]; /* the buffer */

	rec = nrec( my.xstr );
	if ( ! rec ) {
		log_msg( LOG_SYSERR, "could not alloc MFR of len %hd", my.xstr[LSTR_ILEN] );
		goto cleanup;
	}
	buf = (char *)
#ifdef alloca
		alloca( len );
	/* first try faster alloca, but stack may be too limited for large records */
	notalloca = ! buf;
	if ( notalloca )
		buf =
#endif
		mAlloc( len );

	if ( ! buf ) {
		log_msg( LOG_SYSERR, "could not alloc MFR of len %hd", len );
		goto cleanup;
	}
	if ( readblk( buf, len, db->mst[MST_MST], off ) ) {
		log_msg( LOG_ERROR, "\twhen reading MFR" );
		goto cleanup;
	}
#ifndef LDB_BIG_ENDIAN
	if ( LVAR_PAC != (DB_VARI & db->flags) ) {
#endif
		if ( convert( rec, buf, my.xstr ) ) {
			log_msg( LOG_ERROR, "\twhen converting MFR" );
			goto cleanup;
		}
#ifndef LDB_BIG_ENDIAN
	} else { /* 10% faster */
		Mfr *mfr = (Mfr*)buf;
		short *s = &mfr->dict->tag;
		int *f = rec + LMFR__FL;
		int *e = f + 3*head[LMFR_NVF];
		rec[LMFR_MFN]  = mfr->mfn;
		rec[LMFR_RECL] = mfr->recl;
		/*
		rec[LMFR_BWB]  = mfr->bwbh<<16 | mfr->bwbl;
		rec[LMFR_BWP]  = mfr->bwp;
		*/
		rec[LMFR_BASE] = mfr->base;
		rec[LMFR_NVF]  = mfr->nvf;
		rec[LMFR_STAT] = mfr->stat;
		while ( f < e ) {
			*f++ = *s++;
			*f++ = *s++;
			*f++ = *s++;
		}
	}
#endif

	if ( rec[LMFR_STAT] ) {
		log_msg( LOG_WARN, "found status %hd", rec[LMFR_STAT] );
		goto cleanok;
	}

	/* do a consistency check */
	if ( rec[LMFR_NVF] < 0 || rec[LMFR_BASE] < 0 ) {
		log_msg( LOG_ERROR, "found neg. field nvf %hd base %hd",
			rec[LMFR_NVF], rec[LMFR_BASE] );
		goto cleanup;
	}

	/* now care for the field values */
	{
		char *valsrc = buf+rec[LMFR_BASE];
		char *recsta = ((char*)rec);
		char *valdst = recsta + base;
		int xbufl   = rec[LMFR_RECL] - rec[LMFR_BASE];
		int sumlens = 0;
		int i;
		for ( i=0; i < rec[LMFR_NVF]; i++ ) {
			int *d = &rec[LMFR__FL + i*LMFR__RL];
			if ( d[LMFR_POS] < 0 || d[LMFR_LEN] < 0 ) {
				log_msg( LOG_ERROR,
					"bad field %d at off %d: negativ pos %hd or len %hd",
					i, off,  d[LMFR_POS], d[LMFR_LEN] );
				goto cleanup;
			}
			if ( d[LMFR_POS] + d[LMFR_LEN] > xbufl ) {
				log_msg( LOG_ERROR,
					"bad field %d at off %d: pos %hd + len %hd > buf %d",
					i, off,  d[LMFR_POS], d[LMFR_LEN], xbufl );
				goto cleanup;
			}
			sumlens += d[LMFR_LEN];
			if ( sumlens > xbufl ) {
				log_msg( LOG_ERROR,
					"bad fields at off %d: sum of lengths %d > buf %d",
					off,  sumlens, xbufl );
				goto cleanup;
			}
			memcpy( valdst, valsrc+d[LMFR_POS], d[LMFR_LEN] );
			d[LMFR_POS] = valdst - recsta;
			valdst += d[LMFR_LEN];
		}
	}	/* consistency check */
	rec[LMFR_BWB] = /* "used" bytes */
	rec[LMFR_RECL] = my.xstr[LSTR_ILEN];
	rec[LMFR_BWP] = rec[LMFR_NVF]; /* avail fields = used fields */
	rec[LMFR_BASE] = base;

	if ( LOG_TRACE <= log_lev )
		LOG_STR( rec, lstrlib[LSET_MST].name[LSTR_MFR] );
	goto done;

cleanup:
	if ( nxtoff )
		*nxtoff = -1;
cleanok:
	if ( rec ) {
		mFree( rec );
		rec = 0;
	}
done:
	if ( buf
#ifdef alloca
		&& notalloca
#endif
	)
		mFree( buf );
	if ( rec )
		*rec = db->head.dbid;
	return rec;
}	/* getMfr */


/** write the record.
	If it doesn't yet have a mfn, assign one.
	NOTE: on a BIG_ENDIAN, anything but the mfn and recl will be frobbed
	after this call
*/
static int putMfr ( LDb *db, Mfr *mfr )
{
	int oldpos, newpos;
	int ret;

	if ( !(db->flags & DB_WRITABLE) ) {
		log_msg( LOG_ERROR, "db is not writable" );
		return -1;
	}
	/* minimalist sanity check */
	if ( mfr->nvf < 0
		|| mfr->base != 18 + 6*mfr->nvf
		|| mfr->base > mfr->recl
	) {
		log_msg( LOG_ERROR, "bad nvf/base/recl %d/%d/%d ",
			mfr->nvf, mfr->base, mfr->recl );
		return -2;
	}
	db->flags |= DB_MODIFIED;
	if ( mfr->mfn ) {
		int block;
		if ( db->mfc[LMFC_NMFN] > mfr->mfn )
			oldpos = getOff( db, mfr->mfn, 0 );
		else {
			db->mfc[LMFC_NMFN] = mfr->mfn+1;
			oldpos = 0;
		}
		mfr->bwp = 511 & oldpos;
		block = 1 + (oldpos >> 9); /* blockno counting from 1 */
		mfr->bwbl = 0xffff & block;
		mfr->bwbh = block >> 16;
		if ( db->mfc[LMFC_NMFN] <= mfr->mfn )
			db->mfc[LMFC_NMFN] = mfr->mfn+1;
	} else {
		mfr->mfn = db->mfc[LMFC_NMFN]++; /* assign new mfn */
		oldpos = 0;
		mfr->bwbl = mfr->bwbh = mfr->bwp = 0;
	}
	mfr->stat = 0;
	newpos = db->mflen;
	if ( 498 < (newpos & 511) ) /* round up to next block boundary */
		newpos = ~511 & (newpos + 14);
	if ( 1 & newpos )
		newpos++;
	db->mflen = newpos + mfr->recl;
#ifdef LDB_BIG_ENDIAN
{ /* swap swap swap */
	Dict *d = mfr->dict;
	short nvf = mfr->nvf;
	SWI( mfr->mfn ); SWS( mfr->recl ); SWS( mfr->bwbl ); SWS( mfr->bwbh );
	SWS( mfr->bwp ); SWS( mfr->base ); SWS( mfr->nvf  ); SWS( mfr->stat );
	for ( ; nvf--; d++ ) {
		SWS( d->tag );
		SWS( d->pos );
		SWS( d->len );
	}
}
#endif
	ret = lio_pwrite( &db->mst[MST_MST], (char*)mfr, rvs(mfr->recl), newpos );
#ifdef LDB_BIG_ENDIAN
	/* restore mnf, recl */
	SWI( mfr->mfn );
	SWS( mfr->recl );
#endif
	if ( ret != mfr->recl )
		return log_msg( ERR_TRASH, "could not write Mfr %d bytes got %d",
			mfr->recl, ret );
	getOff( db, mfr->mfn, (1 << (oldpos ? 9 : 10))
		| (((newpos & 0xfffffe00) + 0x200) << 2) | (0x1ff & newpos) );

	return 0;
}	/* putMfr */


static int putRec ( LDb *db, Rec *rec )
{
	int ret = 0, i;
	Mfr *mfr = 0;
	int buflen = 0;
	int reclen = 0;
	int contig = 1;
#ifdef alloca
	int notalloca = 0;
#endif
	const char *rbase = ((char *)rec) + rec->base;
	Dict *d;
	Field *f = rec->field;
	/* TODO: if not rec->len, delete ? */
	for ( i = rec->len; i--; f++ ) {
		if ( ! f->len )
			continue;
		if ( ! f->val )
			LOG_OTO( cleanup, ( ERR_FAULT, "bad rec NULL val" ) );
		contig = contig && (f->val == rbase + buflen);
		buflen += f->len;
	}
	reclen = 18 + 6*rec->len + buflen;
	if ( 1 & reclen )
		reclen++;
	mfr = (Mfr*)
#ifdef alloca
		alloca( reclen );
	notalloca = ! mfr;
	if ( notalloca )
		mfr = (Mfr*)
#endif
		mAlloc( reclen );
	if ( ! mfr )
		LOG_OTO( cleanup,
			( ERR_NOMEM, "could not alloc MFR of len %hd", reclen ) );
	mfr->mfn  = rec->rowid;
	mfr->recl = reclen;
	mfr->bwbl = mfr->bwbh = mfr->bwp = 0;
	mfr->base = 18 + 6*rec->len;
	mfr->nvf  = rec->len;
	mfr->stat = 0;
	d = mfr->dict;
	f = rec->field;
	buflen = 0;
	for ( i = rec->len; i--; d++, f++ ) {
		d->tag = f->tag;
		d->pos = buflen;
		buflen += (d->len = f->len);
	}
	if ( contig )
		memcpy( ((char*)mfr)+mfr->base, rbase, buflen );
	else {
		char *mbase = ((char*)mfr)+mfr->base;
		d = mfr->dict;
		f = rec->field;
		for ( i = rec->len; i--; d++, f++ )
			if ( d->len )
				memcpy( mbase + d->pos, f->val, d->len );
	}
	ret = putMfr( db, mfr );
	if ( !ret && !rec->rowid )
		rec->rowid = mfr->mfn;

cleanup:
	if ( mfr
#ifdef alloca
		&& notalloca
#endif
	)
		mFree( mfr );
	return ret;
}	/* putRec */



/* ************************************************************
	access functions for plaintext db
*/

/*
	create a pointer from the least significant bytes of pos, len, fld
	buf must have db->ptrl bytes (up to 16 = 8+4+4)
	and the most strict alignment (i.e. 4 or 8) possible for db->ptrl
	returns buf
*/
static char *mkptr ( char *buf, LDb *db,
	unsigned pos, unsigned len, unsigned fld )
{
	switch ( db->ptr ) {
	case 0x0134: /* '4' */
		((unsigned*)buf)[0] = pos;
		if ( ~0xff & fld ) fld = 0;
#ifdef LDB_BIG_ENDIAN /* the first = high order 3 bytes are len */
		((unsigned*)buf)[1] = (0xff&fld) | len<<8;
#else /* the first = low order 3 bytes are len */
		((unsigned*)buf)[1] = (0xffffff&len) | fld<<24;
#endif
		return buf;
	case 0x0044: /* 'D' */
		((unsigned*)buf)[0] = pos;
		((unsigned*)buf)[1] = len;
		return buf;
	case 0x0035: /* '5' */
#ifdef LDB_BIG_ENDIAN /* the first = high order 5 bytes are pos */
		*(lll*)buf = (0xffffff&len) | ((lll)pos)<<24;
#else /* the first = low order 5 bytes are pos */
		*(lll*)buf = pos | ((lll)len)<<40;
#endif
		return buf;
	}
	/* TODO */
	(void)fld;
	assert( 0 );
	return 0;
}


/*
	read pointer, return len
	if 0x0f00 & db->ptr, fld must not be 0, else *fld is untouched
*/
static unsigned rdptr ( unsigned *pos, unsigned *fld, LDb *db, char *buf )
{
	switch ( db->ptr ) {
	case 0x0134:
		*pos = *(unsigned*)buf;
		*fld = ((unsigned char *)buf)[7];
#ifdef LDB_BIG_ENDIAN
		return ((unsigned*)buf)[1] >> 8;
#else
		return 0xffffff & ((unsigned*)buf)[1];
#endif
	case 0x0044:
		*pos = *(unsigned*)buf;
		return ((unsigned*)buf)[1];
	case 0x0035:
#ifdef LDB_BIG_ENDIAN
		/* *pos = (*(unsigned*)(buf+1)); would bus error on sparc */
		*pos = (unsigned) (*(lll*)buf >> 24);
		return 0xffffff & (unsigned)*(lll*)buf; /* last 3 bytes */
#else /* guess there is no little endian that needs alignment ? */
		*pos = *(unsigned*)buf; /* use low order = first 4 of first 5 bytes */
		return 0xffffff & (*(unsigned*)(buf+5));
#endif
	}
	/* TODO */
	(void)fld;
	assert( 0 );
	return 0;
}


static void setPtr ( LDb *db, int mfn,
	unsigned pos, unsigned len, unsigned fld )
{
	Ptr pt;
	if ( mfn < db->mmlen ) {
		mkptr( db->mmap + mfn*db->ptrl, db, pos, len, fld );
		return;
	}
	lio_pwrite( &db->mst[MST_XRF], 
		mkptr( pt.r, db, pos, len, fld), db->ptrl, mfn*db->ptrl );
}	/* setPtr */


static unsigned getPtr ( unsigned *pos, unsigned *fld, LDb *db, int mfn )
{
	Ptr pt;
	return mfn < db->mmlen
		? rdptr( pos, fld, db, db->mmap + mfn*db->ptrl )
		: db->ptrl == lio_pread( &db->mst[MST_XRF],
			pt.r, db->ptrl, mfn*db->ptrl )
		? rdptr( pos, fld, db, pt.r )
		: 0;
}	/* getPtr */


#if 0
static int putPlain ( LDb *db, Rec *rec )
{
	return 0;
}	/* putPlain */
#endif

/**
	get text
	the original text is read contigously at base.
	the record is then cooked as requested:
	0	well done: do full fixup,
		apply conversions and create fields.
	1 english: do not create fields (rec->fields is 0), apply no conversions,
		but set rec->len to actual number of fields (counting if necessary).
	2 raw:
		set len only if it's known from the pointer
*/
static Rec *dText ( LDb *db, int mfn, int raw )
{
	unsigned base, sz, pos, len, fld; /* #fields actually used */
	unsigned n = 0; /* known #fields */
	Rec *r, *x;
	Field *f, *fe;
	char *p, *q, *e;

	len = getPtr( &pos, &n, db, mfn );
	LOG_DBG( LOG_TRACE, "dText %d pos %d len %d fld %d", mfn, pos, len, n );
	if ( !len )
		return 0;
	if ( raw )
		fld = 0;
	else if ( !(fld = n) ) {
		fld = len / 36; /* assume one (costing 12 bytes) per 36 bytes data */
		if ( fld < 8 ) /* small record is likely to have some short fields */
			fld = 8;
	}
	base = BASESZ(fld);
	sz = base + len;
	r = (Rec*)mAlloc( sz );
	p = ((char*)r) + base;
	if ( (int)len != lio_pread( &db->mst[MST_MST], p, len, pos ) ) {
		mFree( r );
		return 0;
	}
	LOG_DBG( LOG_TRACE, "'%.*s'", len, p );
	r->dbid = db->head.dbid;
	r->rowid = mfn;
	r->used = r->bytes = sz;
	r->fields = fld;
	r->base = base;
	r->len = n;
	if ( raw && (n || 1 != raw) )
		return r;
	e = p + len;
	f = r->field; /* next field to assign */
	fe = f + fld; /* end of assignable fields */
	/*
		loop through buffer lines to count a/o assign
		count them in n
		while f < fe, also fix and assign them
	*/
	for ( n=0;;) { /* possibly 2 passes needed */
		for ( ;p < e; p = q+1 ) {
			if ( !(q = memchr( p, LF, e-p )) )
				q = e; /* > p */
			if ( TAB != *p || !n ) {
				if ( f < fe ) {
					p += a2il( p, q-p, &f->tag );
					if ( p < q && TAB == *p )
						p++;
					f->len = q - (f->val = p);
					f++; /* f == r->field+n, as long as we don't hit fe */
				}
				n++;
				continue;
			}
			/* continuation line */
			if ( f != r->field+n )
				continue;
			/* we ARE assigning & didn't loose sync at fe */
			/* append to previous */ {
				char *dest = (char*)f[-1].val + f[-1].len;
				int dist = p - dest, l = q-p;
				*p = LF;
				memmove( dest, p, l );
				memset( q-dist, ' ', dist ); /* cleanup */
				f[-1].len += l;
			}
		}
		/* now n != 0, since initially p < e, since len != 0 */
		if ( r->len && r->len != (int)n ) {
			log_msg( LOG_WARN, "rec %d len %d != ptr %d", mfn, n, r->len );
			break;
		}
		if ( raw || (int)n <= r->fields ) /* all counted/assigned */
			break;
		/* extend the record to n fields */
		log_msg( LOG_INFO, "extending rec %d %d -> %d fields", mfn, fld, n );
		fld = n;
		base = BASESZ(fld);
		sz = base + len;
		x = (Rec*)mAlloc( sz );
		x->dbid = db->head.dbid;
		x->rowid = mfn;
		x->used = x->bytes = sz;
		x->fields = fld;
		x->base = base;
		x->len = n;
		p = ((char*)x) + base;
		e = p + len;
		q = ((char*)r) + r->base;
		memcpy( p, q, len );
		memcpy( x->field, r->field, r->fields*sizeof(Field) );
		for ( f=x->field, n=r->fields; n--; )
			(f++)->val += p-q;
		n = r->fields;
		mFree( r );
		r = x;
		f = r->field + n;
		fe = r->field + fld;
		p = (char*)f[-1].val + f[-1].len;
		/* seek behind the LF that delimited the last field */
		while (LF != *p++)
			;
	}
	r->len = n;
	if ( !raw && (DB_TXTMODE & db->flags) )
		for ( f = r->field, fe = r->field + r->fields; f < fe; f++ )
			for ( p = (char*)f->val, e = p+f->len; (p = memchr(p,VT,e-p)); )
				*p++ = LF;
	return r;
}	/* dText */


static int pText ( LDb *db, Rec *r, const char *mark )
{
	char buf[128 + 65536];
	unsigned pos = 0, len = 0, fld, off;
	char *p, *b;
	int ret;

	if ( !(db->flags & DB_WRITABLE) ) {
		log_msg( LOG_ERROR, "db is not writable" );
		return -1;
	}
	if ( ! r->rowid )
		r->rowid = db->mfc[LMFC_NMFN]++; /* assign new mfn */
	else if ( db->mfc[LMFC_NMFN] <= r->rowid )
		db->mfc[LMFC_NMFN] = r->rowid + 1;
	else {
		fld = 0;
		len = getPtr( &pos, &fld, db, r->rowid );
	}
	p = b = 32768 >= r->used ? buf : mAlloc(128+2*r->used);
	*p++ = 'W';
	*p++ = TAB;
	p += u2a( p, r->rowid );
	*p++ = TAB;
	if ( pos ) {
		p += u2a( p, pos );
		*p++ = '.';
		p += u2a( p, len );
		if ( fld ) {
			*p++ = '.';
			p += u2a( p, fld );
		}
	}
	*p++ = TAB;
	if ( mark ) {
		int l = strlen(mark);
		if ( l > 31 ) {
			log_msg( LOG_WARN, "mark '%.48s'%s has length %d",
				mark, l<48 ? "" : "...", l );
			l = 31;
		}
		memcpy( p, mark, l );
		p += l;
	} else {
		timeGtfm( p, 0 );
		p += 17;
	}
	*p++ = LF;
	off = p - b;
	p += len = rSerB( p, r );
	if ( len > 1 ) /* don't count 2 trailing LFs */
		len -= 2;
	db->flags |= DB_MODIFIED;
	pos = db->mflen;
	db->mflen += p - b;
	ret = lio_pwrite( &db->mst[MST_MST], b, p - b, pos );
	if ( ret == p - b ) {
		setPtr( db, r->rowid, pos+off, len, r->len );
		ret = 0;
	}
	if ( buf != b )
		mFree( b );
	return ret;
}	/* pText */


/* ************************************************************
	utilities
*/

static int search ( LDb *db, const char *key, LdbPost *post,
	Rec *rec, DXLoop *lp )
{
	int i, j, prefix, idx, ret, ock;
	int pos;
	int *leaf, *entry;
	char *term;
	int *xstr;
	struct { /* terms cursor */
		char  key[LDB_MAX_KEYLEN+1]; /* key or key prefix */
		short klen; /* key length to compare */
		char  imin; /* minimum index to search */
		char  imax; /* maximum index to search */
		int   leaf[LDB_INDEXES][LDB_TERMBUF]; /* one leaf buffer per index */
		short lpos[LDB_INDEXES]; /* next position in leaf, -1 if done */
	} crs;
	short klen; /* length for initial locate */
	int block[128]; /* buffer to read one block */
	int blockpos = 0;
	LdbP *p = 0;

	if ( ! key )
		key = "$";
	/* prepare cursor struct */
	memset( &crs, 0, sizeof(crs) ); /* tabula rasa */
	crs.klen = strlen( key );
	/* check for prefix match */
	if ( post )
		prefix = LDB_PFX & post->mode;
	else if ( (prefix = crs.klen && '$' == key[crs.klen - 1]) )
		crs.klen--;
	/* check out minimum index to search */
	for ( crs.imin=0; crs.klen > db->tlen[(int)crs.imin]; )
		if ( LDB_INDEXES == ++(crs.imin) )
			return log_msg( ERR_INVAL, "bad keylen %d key '%.64s'", crs.klen, key );
	/* prepare key */
	memset( crs.key, ' ', sizeof(crs.key)-1 );
	{
		unsigned char *uc = (unsigned char*)crs.key;
		unsigned char *uk = (unsigned char*)key;
		for ( i=crs.klen; i--; )
			uc[i] = db->ctab[LCS_UCASE].c[ uk[i] ];
	}
	if ( prefix )
		crs.imax = LDB_INDEXES-1;
	else {
		crs.imax = crs.imin;
		crs.klen = db->tlen[(int)crs.imin];
	}
	log_msg( LOG_INFO, "search for '%.*s'%c", crs.klen, crs.key, prefix?'$':' ' );
	key = crs.key;
	klen = crs.klen;
	if ( rec && rec->len ) {
		/* use last key from record to locate starting position */
		key = rec->field[rec->len-1].val;
		klen = rec->field[rec->len-1].len;
		rec->len = 0;
	}

	for ( i=crs.imin; i<=crs.imax; i++ ) { /* find leaf positions */
		int nFile = INV_N01 + 2*i; /* node file index */
		int nStr = LSTR_N01 + 2*i; /* node struct index */
		int *nstr = DB_XSTR( db, LSET_INV, nStr );
		int lvl;
		short cmplen = klen <= db->tlen[i] ? klen : db->tlen[i];
		pos = db->cnt[i][LCNT_POSR]; /* pos of root record */
		j = 0;
		for ( lvl = 0; 0<pos; lvl++ ) { /* traverse node levels */
			int node[102];
			LOG_DBG( LOG_DEBUG, "node %d at %d lvl %d", pos, j, lvl );
			assert( (int)sizeof(node) >= nstr[LSTR_ILEN] );
			*node = *nstr;
			ret = readlog( node, db->inv[nFile], 1-pos, db, LSET_INV, nStr );
			if ( pos != node[LN0X_POS] /* wrong address */
				|| i+1 != node[LN0X_TYPE] /* wrong type */
				|| node[LN0X_OCK] < 1 /* no keys */
				|| 2*db->cnt[i][LCNT_ORDN] < node[LN0X_OCK] /* too many keys */
			)
				return log_msg( ERR_TRASH, "bad node pos %d type %d keys %d",
					node[LN0X_POS], node[LN0X_TYPE], node[LN0X_OCK]
				);
			ock = node[LN0X_OCK];
			for ( j=1;
				j<ock && 0 < (ret = memcmp( key,
					((char*)node)+node[j*LN0X__RL+LN0X__FL+LN0X_KEY], cmplen ));
				j++ )
				;
			/* now j is at end or on next index not less */
			if ( j==ock /* end */
				|| ret /* index is greater than key */
				|| prefix /* backtrack even on exact match */
			)
				j--; /* step into last ock with lower key */
			pos = node[LN0X__FL + j*LN0X__RL + LN0X_REF];
		} /* for lvl */
		/* got some negative ref to leaf; set leaf pos */
		crs.leaf[i][LL0X_PS] = -pos;
		/*
		since the lpos and LL0X_OCK are both 0 by the memset above,
		we will initially load the leaves
		*/
	} /* for indexes */
	/* done preparing cursor */

	if ( post ) /* prepare for postings */
		p = post->p;
	xstr = DB_XSTR( db, LSET_INV, LSTR_IFP ); 

	for (;;) { /* loop terms in prefix mode */
		/* vars for postings: */
		int infb, infp; /* block and pos where to read postings */
		int added; /* postings added or marked per term */
		int blkno; /* postings block number */
		int remain = 0; /* postings to fetch from next block of segment */
		int ifp[LIFP__FL]; /* postings header */

		idx = -1; /* index to use */
		/* compare index terms, load leafes if needed */
		for ( i = crs.imin; i <= crs.imax; i++ ) {
			short cmplen = klen <= db->tlen[i] ? klen : db->tlen[i];
			leaf = crs.leaf[i];
			if ( leaf[LL0X_OCK] <= crs.lpos[i] ) { /* load */
				int lFile = INV_L01 + 2*i; /* leaf file index */
				int lStr = LSTR_L01 + 2*i; /* leaf struct index */
				int *lstr = DB_XSTR( db, LSET_INV, lStr );

				crs.lpos[i] = -1;
			reread:
				if ( ! (pos = leaf[LL0X_PS]) )
					continue;
				LOG_DBG( LOG_DEBUG, "leaf %d", pos );
				assert( (int)sizeof(crs.leaf[i]) >= lstr[LSTR_ILEN] );
				*leaf = *lstr;
				ret = readlog( leaf, db->inv[lFile], 1-pos, db, LSET_INV, lStr );
				if ( pos != leaf[LL0X_POS] /* wrong address */
					|| i+1 != leaf[LL0X_TYPE] /* wrong type */
					|| leaf[LL0X_OCK] < 1 /* no keys */
					|| 2*db->cnt[i][LCNT_ORDN] < leaf[LL0X_OCK] /* too many keys */
				)
					return log_msg( ERR_TRASH, "bad leaf pos %d type %d keys %d",
						leaf[LL0X_POS], leaf[LL0X_TYPE], leaf[LL0X_OCK] );
				ock = leaf[LL0X_OCK];
				/* advance to first term which is not too small
					(should be needed only for first leaf of an index)
				*/
				for ( j=0;
					j<ock && (0 < (ret = memcmp( key,
						((char*)leaf)+leaf[LL0X__FL + j*LL0X__RL + LL0X_KEY], cmplen ))
						|| (!ret && key!=crs.key) ); /* skip exact while locating */
					j++ )
					;
				if ( ock == j )
					goto reread; /* start over w/ next leaf of same index */
				if ( 0 <= ret
					|| (key!=crs.key && !memcmp( crs.key,
						((char*)leaf)+leaf[LL0X__FL + j*LL0X__RL + LL0X_KEY], crs.klen ))
				)
					crs.lpos[i] = j;
				/* else let -1 */
			} /* if reload */
			if ( 0 > crs.lpos[i] )
				continue;
			if ( 0 > idx ) {
				idx = i;
				continue;
			}
			/* compare this index next term to that of index idx */
			/* assume that index w/ lower number has shorter keys */
			ret = memcmp(
				((char*)leaf)+leaf[LL0X__FL + crs.lpos[i]*LL0X__RL + LL0X_KEY],
				((char*)crs.leaf[idx])+
				crs.leaf[idx][LL0X__FL + crs.lpos[idx]*LL0X__RL + LL0X_KEY],
				db->tlen[idx] );
			if ( 0 > ret )
				idx = i;
		}
		if ( 0 > idx )
			goto done;
		j = crs.lpos[idx];
		leaf = crs.leaf[idx];
		entry = leaf + LL0X__FL + j*LL0X__RL;
		term = ((char*)leaf) + entry[LL0X_KEY];
		if ( memcmp( crs.key, term, crs.klen ) )
			goto done;
		crs.lpos[idx]++;

		if ( rec ) { /* record the term */
			/* field to assign */
			Field *f = rec->field + rec->len;
			short tlen = db->tlen[idx];
			/* end of available buffer */
			char *b = rec->len
				? (char*)f[-1].val /* before previously assigned field */
				: ((char*)rec + rec->bytes); /* end of record */
			while ( tlen && ' ' == term[tlen-1] )
				tlen--;
			b -= tlen;
			if ( b < (char*)(f+1) ) /* no space left on device */
				goto done;
			/* probably we're nuking the locator now: */
			memcpy( b, term, tlen );
			f->tag = 0;
			f->val = b;
			f->len = tlen;
			rec->len++;
			/* reset key from locator to prefix */
			key = crs.key;
			klen = crs.klen;
		}

		if ( ! post && ! lp )
			continue;
		/* collect postings */
		infb = entry[LL0X_INFB];
		infp = entry[LL0X_INFP];
		/* the IFP file is organized in blocks of 128 longs.
			1st int is block number followed by 127 data.
			postings are organized in chained segments so that each segment
			fits within one such block. a segment has five longs header,
			giving number of postings and pointer to next segment.
		*/
		added = 0;
		for ( blkno=0; infb; blkno++ ) { /* segments */
			LdbP merge[127/2]; /* buffer to collect new postings */
			int *base; /* start of data */
			int *b; /* start of postings */
			int n; /* max postings in this seg's 1st block */
			int xlen; /* external length to read */
			int f = post ? post->fil - 1 : 0; /* highest pos to consider in given postings */
			int m = 0; /* fill merge buffer */
			int k; /* loop segment */

			if ( infp > 127-2-5 ) {
				return log_msg( ERR_TRASH, "found bad IFP pos %d blk %d for %.*s",
					infp, blkno, klen, key );
			}
			if ( remain ) { /* consecutive block of same segment */
				n = remain;
				if ( n > 127/2 )
					n = 127/2;
				xlen = 8*n;
			} else {
				n = (127 - 5 - infp)/2;
				xlen = 20 + 8*n;
			}
			pos = (infb - 1) * 512 + (infp + 1) * 4;
			if ( blockpos
				&& !((pos-blockpos) >> 9) /* 0 <= (pos-blockpos) < 512 */
				&& pos+xlen <= blockpos+ 1 + (0x1ff & ~blockpos)
			)
				base = block + (pos - blockpos)/sizeof(int);
			else {
				int blklen = 1 + (0x1ff & ~pos);
				assert( xlen <= blklen );
				assert( blklen <= (int)sizeof(block) );
				assert( 0 == (0x1ff & (pos + blklen)) );
				ret = readblk( block, blklen, db->inv[INV_IFP], pos );
				if ( ret )
					return log_msg( ERR_IO, "\twhen reading IFP" );
				blockpos = pos;
				base = block;
			}
			if ( remain ) { /* no header to convert */
				remain -= n;
				b = base; /* no header */
			} else  {
				assert( (int)sizeof(ifp) >= xstr[LSTR_ILEN] );
				*ifp = *xstr;
				ret = convert( ifp, (char *)base, xstr );
				if ( ret )
					return log_msg( ERR_TRASH, "\twhen converting IFP header" );
				if ( n > ifp[LIFP_SEGP] )
					n = ifp[LIFP_SEGP];
				remain = ifp[LIFP_SEGP] - n;
				b = base+5; /* after header */
			}
			LOG_DBG( LOG_VERBOSE,
				"key %d.%d '%.*s' blk %d post %d/%d r %d xlen %d at b/p %d.%d=%d",
				leaf[LL0X_PS], j, db->tlen[idx], term, blkno,
				n, ifp[LIFP_TOTP], remain, xlen, infb, infp, pos );
			if ( LOG_DO( LOG_TRACE ) )
				LOG_STR( ifp, lstrlib[ LSET_INV ].name[ LSTR_IFP ] );
			assert( (size_t)n <= sizeof(merge)/sizeof(merge[0]) );
			if ( lp ) {
				Key kbf;
				Hit hit;
				unsigned char tlen = (unsigned char) db->tlen[idx];
				while ( tlen && ' ' == term[tlen-1] )
					tlen--;
				memcpy( kbf.byt, term, kbf.len = tlen );
				for ( k=0; k<n; k++ ) { /* callback needs 'em sorted */
					int ppos;
					unsigned char *c = (unsigned char *)&b[k*2];
					LdbP e; /* the entry */
#ifdef LDB_BIG_ENDIAN
					memcpy(e.bytes,c,8);
#else
					e.bytes[0] = c[7];	e.bytes[1] = c[6];
					e.bytes[2] = c[5];	e.bytes[3] = c[4];
					e.bytes[4] = c[3];	e.bytes[5] = c[2];
					e.bytes[6] = c[1];	e.bytes[7] = c[0];
#endif
					ppos = LDBP_POS( &e );
					hit.mfn = (unsigned)LDBP_ROW( &e );
					hit.tag = (unsigned short)LDBP_TAG( &e );
					hit.occ = (unsigned short)(ppos >> 16);
					hit.pos = (unsigned short)ppos;
					if ( lp->cb( lp->me, &kbf, &hit ) )
						goto done;
				}
			}
			if ( post ) for ( k=n; k--; ) {
				/* loop backwards (for the fun of it) postings in segment */
				int prow, ptag, ppos;
				unsigned char *c = (unsigned char *)&b[k*2];
				LdbP e; /* the entry */
				LdbP samerow; /* highest possible entry w/ same row as e */
#ifdef LDB_BIG_ENDIAN
				/* the 8 bytes of a posting are BIG ENDIAN ! */
				memcpy(e.bytes,c,8);
#else
				e.bytes[0] = c[7];	e.bytes[1] = c[6];
				e.bytes[2] = c[5];	e.bytes[3] = c[4];
				e.bytes[4] = c[3];	e.bytes[5] = c[2];
				e.bytes[6] = c[1];	e.bytes[7] = c[0];
#endif
				prow = LDBP_ROW( &e );
				ptag = LDBP_TAG( &e );
				ppos = LDBP_POS( &e );
				LOG_DBG( LOG_VERBOSE, "post %d.%hd pos %06x key '%.*s'",
					prow, ptag, ppos, db->tlen[idx], term );
				if ( 0 >= ptag /* bad tag */
					|| !prow || prow >= db->mfc[LMFC_NMFN] /* bad mfn */
				)
					continue;
				if ( ! post
					|| (post->cut && prow >= post->cut)
					|| (post->tag && post->tag != ptag)
				)
					continue;
				if ( prow < post->skp ) /* quickly bail out on skip condition */
					break;
				LDBP_SETROWTOP( &samerow, &e ); /* for mfn comparison */
				/* sweep down to postings for the same row as e ... */
				while ( f >= 0 && LDBP_GT( p+f, &samerow ) )
					f--;
				if ( LDB_AND & post->mode ) {
					int l;
					/* loop postings for same row, mark all (that are near enough) */
					LDBP_SETROWBOT( &samerow, &e ); /* for mfn comparison */
					/* NOTE: postings for row are GT than bottom even if marked */
					for ( l = f; l>=0 && LDBP_GT( p+l, &samerow ); l-- ) {
						if ( post->near ) {
							int dist;
							if ( ptag != LDBP_TAG( p+l ) ) continue;
							if ( LDB_NEAR_G != post->near ) {
								dist = LDBP_POS( p+l ) - LDBP_POS( &e );
								if ( dist < 0 ) dist = -dist;
								if ( 0 < post->near
									? post->near < dist
									: -post->near != dist /* exact $$$$ */
								) continue;
							}
						}
						LDBP_SETMARK( p+l );
						added++;
					}
				} else {	/* OR mode */
					int add;
					if ( ! post->near ) /* add if row not found: ignore details */
						add = 0 > f || prow > LDBP_ROW( p+f );
					else {	/* add if no exact match */
						int l;
						/* NOTE: we don't use mark bit in OR mode, do we ? */
						for ( l = f; l>=0 && LDBP_GT( p+l, &e ); l-- )
							;
						add = 0 > l || LDBP_GT( &e, p+l );
					}
					if ( add )
						merge[ m++ ] = e;
				}
			}	/* for postings in segment */
			if ( m ) { /* merge in the merge buffer */
				LdbP *mm = merge;
				added += m;
				for ( k = post->fil += m; m && k--; ) {
					LdbP src;
					if ( k < m || LDBP_GT( mm, &p[k-m] ) ) {
						src = *mm++;
						m--;
						LOG_DBG( LOG_DEBUG, "merging %d at %d", LDBP_ROW(&src), k );
					} else
						src = p[k-m];
					if ( k < post->len )
						p[k] = src;
					else { /* set cut */
						int row = LDBP_ROW( &src );
						if ( row < post->cut || !post->cut )
							post->cut = row;
					}
				}
				if ( post->fil > post->len )
					post->fil = post->len;
				if ( post->cut ) /* postings for cut row are unreliable */
					while ( post->fil && post->cut <= LDBP_ROW(p+post->fil-1) )
						post->fil--;
			}
			if ( remain ) { /* advance to start of next block */
				infb++;
				infp = 0;
			} else {
				infb = ifp[LIFP_NXTB];
				infp = ifp[LIFP_NXTP];
			}
		} /* for segments */
		LOG_DBG( LOG_VERBOSE, "added %d postings for key '%.*s'",
			added, db->tlen[idx], term );
	}	/* for terms in prefix/postings mode */
done:
	if ( post /* fixup */
		&& LDB_AND & post->mode && !(LDB_KEEPMARKS & post->mode)
	) {
		int mark = LDB_NOT & post->mode ? 0 : 0x8000;
		j=0;
		for ( i=0; i<post->fil; i++ )
			if ( mark == LDBP_MARK(p+i) ) {
				LDBP_CLRMARK(p+i);
				p[j++] = p[i];
			}
		post->fil = j;
	}
	return ! rec ? 0 : rec->len;
}	/* search */


static int ldb_last_path_sep (const char *path) {
	char *p2;
	int   i2;
#ifdef WIN32
	char *p3;
	int   i3;
#endif
	if (! path) {
		return -1;
	}
	p2 = strrchr (path, '/');
	i2 = p2 ? p2 - path : -1;
#ifdef WIN32
	p3 = strrchr (path, '\\');
	i3 = p3 ? p3 - path : -1;
	if (i3 > i2) {
		i2 = i3;
	}
#endif
	return i2;
} /* ldb_last_path_sep */


static int ldb_open (const char *dbname, Rec *dbpar, Rec *syspar, Fdt *fdt)
{
	LDb ndb, *db;
	int i, plen, sz, dbid, lck = LIO_TLOCK; /* WLOCK only on special demand */
	int ret = 0, invret = -1, lbtret = 0, autoformat = 1, writable = -1;
	int uc = -1, gotopt = 0, txtfd = 0, copyidx = 0;
	char *autoenc = 0;
	char *p, *q;
	char buf[65536+1]; /* need 64K buf for copying DO NOT SHRINK !!! */
	char path[ PATH_MAX ];

	memset( &ndb, 0, sizeof(ndb) );
	/* these should be 0 by memsetting to 0 anyway ... */
	ndb.path = 0; ndb.mmap = 0;
	ndb.flags |= DB_MMAP; /* it mean's: we'll try */

	/* loglevel */
	if ( 0 <= (i = rInt2(dbpar, syspar, OPENISIS_SLOGV, -1)) )
		cLog( i, 0 );

	/* prepare name ... */
	if (! dbname) {
		if (! dbpar)
			return log_msg( ERR_FAULT, "ldb_open: dbname not given");
		dbname = rString (dbpar, OPENISIS_DNAME, 0, buf, sizeof(buf));
		if (! dbname)
			return log_msg( ERR_FAULT, "ldb_open: no dbname parameter");
	}
	plen = strlen (dbname);
	if (0 >= plen)
		return log_msg( ERR_FAULT, "ldb_open: empty dbname");
	if ( 4 < plen ) {
		if ( !memcmp( ".mst", dbname+plen-4, 4 ) ) {
			uc = 0;
			plen -= 4;
		} else if ( !memcmp( ".MST", dbname+plen-4, 4 ) ) {
			uc = OPEN_UC;
			plen -= 4;
		}
	}
	if ( sizeof(buf) <= (unsigned)plen
		|| sizeof(path) <= (unsigned)(plen + 4 + 1)
	)
		return log_msg( ERR_FAULT, "ldb_open: dbname too long '%s'", dbname);
	if ('/' == dbname[plen - 1] 
#ifdef WIN32
		|| '\\' == dbname[plen - 1]
#endif
	)
		return log_msg( ERR_FAULT,
			"ldb_open: must not specify directory as dbname '%s'", dbname);
	if (DBNLEN > plen)
		strcpy(ndb.head.name, dbname);
	else {
		int i1 = 1 + plen - DBNLEN ;
		int i2 = ldb_last_path_sep (dbname);
		if (0 <= i2 && plen > ++i2 && i2 > i1) {
			i1 = i2;
		}
		strncpy(ndb.head.name, dbname + i1, DBNLEN - 1) [DBNLEN - 1] = 0;
		log_msg( LOG_WARN, "ldb_open: truncating dbname '%s' to '%s'",
			dbname, ndb.head.name);
	}
	/* ... and path */
	strcpy(path, dbname);
	if (! IsAbsPath (path)) {
		int plen2;
		if ( (dbpar || syspar)
			&& (p = rString2 (dbpar, syspar, OPENISIS_DPATH, buf, sizeof(buf)))
		) {
			plen2 = strlen (p);
			if (sizeof(path) <= (unsigned)(plen + plen2 + 4 + 1 + 1))
				return log_msg( ERR_FAULT,
					"ldb_open: dbname or dbpath too long: %d %d '%s'",
					plen, plen2, path);
			memmove (path + 1 + plen2, path, 1 + plen);
			path[plen2] = '/';
			memcpy (path, p, plen2);
			plen += 1 + plen2;
		}
		if ( !IsAbsPath(path)
			&& syspar
			&& (p = rString(syspar, OPENISIS_SPATH, 0, buf, sizeof(buf)))
		) {
			plen2 = strlen(p);
			if (sizeof(path) <= (unsigned)(plen + plen2 + 4 + 1 + 1))
				return log_msg( ERR_FAULT,
					"ldb_open: dbname or syspath too long: %d %d '%s'",
					plen, plen2, path);
			memmove(path + 1 + plen2, path, 1 + plen);
			path[plen2] = '/';
			memcpy(path, p, plen2);
			plen += 1 + plen2;
		}
	} /* name and path */

	/* more init AFTER honoring verbosity */
	if ( ! init ) {
		lstr_auto(0);
		init = !0;
	}

	for ( dbid=0; dbid<dbs_len; dbid++ ) {
		if ( dbs[dbid].flags &&
			!strcmp( ndb.head.name, dbs[dbid].head.name ) ) {
			log_msg( LOG_INFO, "reopening %d '%s'", dbid, ndb.head.name );
			return dbid;
		}
	}
	/* go for slot */
	if ( dbid == dbs_len )
		for ( dbid=0; dbid<dbs_len && dbs[dbid].flags; dbid++ )
			;
	if ( dbid == dbs_len )
		return -1;
	db = &dbs[dbid];
	/* got slot */
	*db = ndb;
	db->head.dbid = dbid;

	/* preset record sizes */
	db->mfc[0] = *DB_XSTR( db, LSET_MST, LSTR_MFC );
	db->xrf[0] = *DB_XSTR( db, LSET_MST, LSTR_XRF );
	db->cnt[0][0] =
	db->cnt[1][0] = *DB_XSTR( db, LSET_INV, LSTR_CNT );
	/* isis-1 index term lengths */
	db->tlen[0] = 10;
	db->tlen[1] = 30;

	/* only the packed little endian ("DOS") format is writable
	test later ...
	if ( LVAR_PAC != (DB_VARI & db->flags) )
		writable = 0;
	*/

	db->path = mDup( path, plen+1 ); /* save path */
	memcpy( path+plen, ".???", 5 );

	if ( dbpar )
		dbpar = rDup(dbpar, 0, 0);
	/* check options file and extension case */
	if ( 0 <= uc ) /* use case from dbname */
		i = lio_open( setext(path,EXT_TXT_OPT,uc), OPEN_RDIF );
	else if ( 0 > (i = lio_open( setext(path,EXT_TXT_OPT,uc=0), OPEN_RDIF ))
		&& 0 > (i = lio_open( setext(path,EXT_TXT_OPT,uc=OPEN_UC), OPEN_RDIF ))
	)
		uc = autocase( db->path );
	if ( 0 < i ) {
		if ( 0 < (sz = lio_size(i)) ) {
			p = sz < (int)sizeof(buf) ? buf : mAlloc(sz);
			if ( (gotopt = (sz == lio_read( &i, p, sz ))) )
				rDeser( &dbpar, p, sz, 0 );
			log_msg( LOG_INFO, "reading %d bytes options from '%s' %s",
				sz, path, gotopt ? "ok" : "nok" );
			if ( buf != p )
				mFree( p );
		}
		lio_close( &i, LIO_INOUT );
	}
	lck |= uc;

	if ( (dbpar || syspar) && 0 <= (i = rInt2(dbpar, syspar, OPENISIS_DRO, -1)))
		writable = !i; /* explicit 0/1 */

	/* open files */
	/* trad. index is never openend writable. */
	invret = openfiles( db->inv, path, EXT_INV, INV_FILES, uc|OPEN_RDIF );
	if (dbpar || syspar) {
		char fmtstr[32];
		if (rString2 (dbpar, syspar, OPENISIS_DTYPE, fmtstr, sizeof(fmtstr))) {
			if (! strcmp ("aligned", fmtstr)) {
				db->flags |= LVAR_ALI;
				autoformat = 0;
			} else if (! strcmp ("naligned", fmtstr))
				autoformat = 0;
		}
	}
	if ( autoformat ) {
		if ( invret )
			log_msg( LOG_WARN, "cannot guess format -- no inverted file" );
		else {
			unsigned len = lio_size( db->inv[INV_CNT] );
			if ( 56L == len ) {
				db->flags |= LVAR_ALI;
				autoenc = "iso8859-1";
				/* writable = 0; we do not write aligned format */
			} else if ( 52L == len )
				autoenc = "cp850";
			else
				log_msg( LOG_WARN, "cannot guess format -- bad .cnt len %d", len );
			log_msg( LOG_INFO, "using autoformat %saligned for .cnt len %d",
				(db->flags & LVAR_ALI) ? "":"un", len );
		}
	}

	/* data */
#ifdef NOTXTDB
	if ( !(ret = openfiles( db->mst, path, EXT_MST, MST_FILES,
		lck|OPEN_ASIS|LIO_CREAT ))
	)
		writable = 0;
	else if (0 > ret)
#else
	if ( 0 <= (ret = openfiles( &txtfd, path, EXT_TXT, 1,
		lck|LIO_SYNC|(writable?OPEN_ASIS:OPEN_RDIF) ))
	) { /* .txt exists: use it */
		if ( ret )
			writable = 1;
		else if (1 == writable) {
			log_msg( LOG_ERROR, "file '%s' is readonly", path );
			goto cleanup;
		} else
			writable = 0;
	} else if (
		0 <= (ret = openfiles( db->mst, path, EXT_MST, MST_FILES,
			lck|((writable && !(db->flags & LVAR_ALI))?OPEN_ASIS:OPEN_RDIF) ))
		&& (ret || 1!=writable)
	) { /* trad. files are ok */
		if ( !ret )
			writable = 0;
	} else if ( 1 != (ret = openfiles( &txtfd, path, EXT_TXT, 1,
		lck|(ret ? LIO_SYNC : 0)|OPEN_NEW )) ) /* don't sync on autoconv */
#endif
		goto cleanup;

	/* MW: creation mode? KR: ugo+rw & ~umask */
	if ( 1 == (lbtret = openfiles( &db->oxi.fd, path, EXT_LBT, 1,
		lck|(writable?OPEN_ASIS:OPEN_RDIF) ))
	)
		lbtret = 0;
	else if ( !writable )
		;/* no problem */
	else if ( !lbtret ) { /* exists ro */
		log_msg( LOG_ERROR, "file '%s' is readonly", path );
		goto cleanup;
	} else { /* create and copy to oxi */
		if ( 1 != openfiles( &db->oxi.fd, path, EXT_LBT, 1, lck|OPEN_NEW ) )
			goto cleanup;
		lbtret = 0;
		copyidx = 1;
	}

	if ( db->mst[MST_MST] ) { /* care for the traditionals */
		if ( (ret = readlog(
			db->mfc, db->mst[MST_MST], 0, db, LSET_MST, LSTR_MFC ))
		) {
			/* NEW goto cleanup; */
			memset( db->mfc, 0, sizeof(db->mfc) );
			db->mfc[LMFC_NMFN] = 1;
			db->mfc[LMFC_NMFB] = 1;
			db->mfc[LMFC_NMFP] = 64;
			db->mflen = 64;
		} else {
			/*
			int lastblock = (db->mflen = lio_size( db->mst[MST_MST] ))/512;
			if ( 511 & db->mflen ) lastblock++;
			counting from 1
			the next record's block should be either the last one we have
			or the next one to follow
			if ( db->mfc[LMFC_NMFB] != lastblock
				&& db->mfc[LMFC_NMFB] != lastblock+1
			)
				log_msg( LOG_VERBOSE, "NMFB mismatch: NMFB %d ~ %d",
					db->mfc[LMFC_NMFB], lastblock );
			*/
			/* set LOGICAL mf length */
			db->mflen = (db->mfc[LMFC_NMFB]-1)*512 + db->mfc[LMFC_NMFP];
		}
		db->ptrl = 512;
		db->xrlen = lio_size( db->mst[MST_XRF] ) / 512;
		if ( (DB_MMAP & db->flags)
			&& db->xrlen
			&& db->xrlen*512
			== lio_mmap( &db->mst[MST_XRF], (void**)&db->mmap, db->xrlen*512 )
		)
			db->mmlen = db->xrlen;
	}

	if ( txtfd ) {
		int remake = 0;
		/* TODO: make on-demand preparation even faster using buffered IO */
		if ( !lio_size(txtfd) ) {
			const char newline = LF;
			if ( gotopt
				&& 0 < (i = lio_open( setext(path,EXT_TXT_OPT,uc), LIO_RD ))
			) { /* copy the options file */
				log_msg( LOG_INFO, "copying %d bytes options", lio_size(i) );
				while ( 0 < (sz = lio_read( &i, buf, sizeof(buf)-1 )) )
					lio_write( &txtfd, buf, sz );
				if ( LIO_INOUT & i ) { /* is supposed to autoclose */
					log_msg( LOG_WARN, "tss tss tss ..." );
					lio_close( &i, LIO_INOUT );
				}
			}
			lio_write( &txtfd, &newline, 1 );
		}

		if ( db->mst[MST_MST] ) { /* copy to new empty txt */
			int end = db->mfc[LMFC_NMFN];
			/*
				max recsize for traditionals is 32K.
				field values may double, if consisting entirely of newlines.
				rec->used may be more than 32K, since we 12 bytes per field.
				However, we know there are only sign+5digits+tab+newline used per tag,
				fitting within 2* the original 6 bytes per field.
			*/

			log_msg( LOG_INFO, "copying traditional data" );
			db->flags |= DB_OPEN; /* pretend */
			for ( i=1; i<end; i++ ) {
				Rec *r = dRead( dbid, i );
				if ( !r )
					sz = 1;
				else if ( (int)sizeof(buf) <= (sz = rSerB( buf, r )) ) {
					log_msg( ERR_IDIOT, "serialized %d bytes" );
					exit(42);
				}
				lio_write( &txtfd, buf, sz );
			}
			db->flags &= ~DB_OPEN; /* pret end */
			remake = 1;
			if ( db->mmap )
				lio_mmap( 0, (void**)&db->mmap, db->mmlen*512 );
			db->mmlen = 0;
			closefiles( db->mst, MST_FILES );
		} /* copying */
		db->mst[MST_MST] = txtfd;
		db->mflen = lio_size( db->mst[MST_MST] );

		db->ptr = 0x0134; /* should be config opt */
		if ( !remake ) { /* other reasons why we should remake */
			unsigned short ptr;
			unsigned isix = GETINT(ISIX);
			unsigned magic;

			remake = 1;
			if ( 0 > (db->mst[MST_XRF] = lio_open( setext(path,EXT_TXT_PTR,uc),
				LIO_SEEK|(writable?LIO_RDWR:LIO_RD) ))
			)
				log_msg( LOG_INFO, "'%s' not found", path );
			else if ( 6 != lio_read(&db->mst[MST_XRF],buf,6) )
				log_msg( LOG_WARN, "'%s' too short", path );
			else if ( isix != (magic = GETINT(buf)) ) /* FOO! */
				log_msg( LOG_WARN, "'%s' has black magic 0x%08x", path, magic );
				/* TODO: save that foo if it doesn't read ISIX ? */
			else if ( 0xf000 & (ptr = GETSHORT(buf+4)) ) /* bad endianess */
				log_msg( LOG_WARN, "'%s' has bad endianess type 0x%04x", path, ptr );
			else if ( (db->ptr && db->ptr != ptr) ) /* other type configured */
				log_msg( LOG_WARN, "'%s' type 0x%04x != cfg 0x%04x", path, ptr, db->ptr );
			else if ( lio_time(db->mst[MST_XRF]) < lio_time(db->mst[MST_MST]) )
				log_msg( LOG_WARN, "'%s' older than data", path );
			else {
				db->ptr = ptr;
				remake = 0;
			}
		}
		if ( ! db->ptr ) {
			db->ptr = 0x0134; /* m*256 + l*16 + k, doc/Serialized */
			/* BTW: 0x34 is ASCII digit '4', so it's ISIX4^A on little endian */
			db->ptrl = 8;
		} else { /* fix unsupported type */
			unsigned m = 0xf&(db->ptr>>8);
			unsigned l = 0xf&(db->ptr>>4);
			unsigned k = 0xf&db->ptr;
			int mod = 0;
			if ( m > 4 ) { m = 4; mod = 1; }
			if ( l > 4 ) { l = 4; mod = 1; }
			if ( k > 4 ) { k = 4; mod = 1; } /* TODO: allow 8 with large files */
			/* total ptr bytes = sum(nibbles) <= 45, but won't use more than 8+4+4 */
			if ( mod ) {
				log_msg( LOG_WARN, "fixing unsupported ptr type 0x%04x", db->ptr );
				db->ptr = (unsigned short)(m<<8 | l<<4 | k);
				remake = 1;
			}
			db->ptrl = k+l+m;
		}
		if ( remake ) {
			Ptr pt;
			unsigned base = 0; /* of current block */
			unsigned pos = 0; /* of last record */
			unsigned fld = 0; /* of last record */
			unsigned nmfn = 0; /* next mfn = maxmfn+1 */
			unsigned xmfn = 0; /* explicitly given */
			char op = 0;
			int more; /* buf not empty flag */
			char *last; /* of current block */

			lio_close( &db->mst[MST_XRF], LIO_INOUT );
			if ( 0 > (db->mst[MST_XRF] = lio_open(
				setext(path,EXT_TXT_PTR,uc), OPEN_BLANK ))
			)
				goto cleanup;
			/* write signature */
			memcpy( pt.r, "ISIX", 4 );
			memcpy( pt.r+4, &db->ptr, 2 );
			memcpy( pt.r+6, ":)", 2 );
			if ( 8 < db->ptrl )
				memset( pt.r+8, ')', db->ptrl - 8 );
			lio_pwrite( &db->mst[MST_XRF], pt.r, db->ptrl, 0 );
			/* loop the masterfile */
			lio_seek( &db->mst[MST_MST], 0 );
			last = (p = buf) + lio_read( &db->mst[MST_MST], buf, 8192 ) - 1;
			more = last > buf; /* one byte is no byte ;) */
			if ( more && LF == *p ) { /* no options: no \n\n */
				nmfn = pos = 1;
				p++;
			}
			for (;;) { /* records */
				unsigned len, mfn;
				for (;;) { /* lines and stuff to end of record */
					if ( p < last ) { /* have one lookahead */
						if ( LF != *p++ )
							continue; /* the tight loop ... or use memchr ? */
						if ( LF != *p ) { /* now p <= last */
							if ( fld || !(0xc0 & *p) ) { /* < '@', 'A', ... */
								if ( TAB != *p ) /* no continuation */
									fld++;
								continue;
							}
							fld++; /* count field, unless we really recognize a opline */
							if ( 'Z' < *p )
								continue;
							/* now we have '@'...'Z' at start of 1st line */
							sz = last - p; /* avail after p */
							if ( sz && TAB != p[1] ) /* no opline */
								continue;
							switch (*p) {
							case 'D':
							case 'I':
							case 'W':
								break; /* give it a try */
							default:
								log_msg( LOG_WARN, "unknown opline %c at mfn %d", *p, nmfn );
								continue;
							}
							if ( sz > 127 ) /* longer -> no opline */
								sz = 127;
							if ( ! sz || ! (q = memchr(p+1, LF, sz)) ) {
								if ( sz >= 127 || ! more )
									continue; /* too long or undelimited last */
								p--; /* back to \n, so we come here again */
								goto gimmemore;
							}
							if ( q < p+3 || p[2] < '0' || '9' < p[2] )
								continue;
							/* TODO:
							take a closer look at whether the whole line makes sense
							*/
							if ( op ) { /* yeah, two metas in sequence! weird stuff! */
								p--; /* step back to newline */
								pos = base+(p-buf); /* fake pos as if we had no line at all */
								break; /* go handle the PREVIOUS opline */
							}
							op = *p;
							xmfn = a2i( p+2, q-p-2 );
							fld--; /* uncount this line */
							pos = base + (q-buf) + 1; /* start after q */
							continue;
						}
						break;
					}
				gimmemore:
					LOG_DBG( LOG_DEBUG, "MORE %d at pos %d base %d p +%d last +%d",
						more, pos, base, p-buf, last-buf );
					if ( !more )
						goto schicht; /* german: done */
					base += p - buf; /* shift out bytes before p */
					len = last-p; /* bytes to keep after p; < 128 */
					if ( len ) /* we're probing for more lookahead */
						memmove( buf, p, 1+last-p );
					else /* typically */
						*buf = *p; /* but save the last dance */
					p = buf;
					last = buf + len;
					/* reload */
					if ( 0 < (sz = lio_read( &db->mst[MST_MST], buf+1+len, 8192 )) ) {
						last += sz;
						continue;
					}
					more = 0; /* but yet, finish this up */
					/* since *buf = *last was the files last character,
						we'd expect a newline
					*/
					if ( last == p )
						p = buf+(LF==*buf ? 1 : 2); /* pretend buf started \n */
					if ( ! len )
						break;
					/* else try again opline */
				} /* lines and stuff */
				/* now p is on a delimiting blank lines \n -- or such ... */
				len = base + (p-buf) - pos; /* >= 0 */
				mfn = xmfn ? xmfn : nmfn;
				log_msg( LOG_INFO, "ptr %c %d(%d/%d) pos %d len %d",
					op?op:'>', mfn, xmfn, nmfn, pos, len );
				if ( base + (p-buf) < pos ) /* FOO !!! */
					len = 0;
				if ( len ) /* could have been completely empty */
					len--; /* mute last \n */
				if ( 'D' == op && len ) /* FOO !!! */
					len = 0;
				if ( mfn && (len || op) )
					lio_pwrite( &db->mst[MST_XRF],
						mkptr( pt.r, db, pos, len, fld), db->ptrl, mfn*db->ptrl );
				pos = base + (p-buf) + 1; /* next starts after p */
				if ( 'D' != op ) { /* 'D'elete does not lead to implicit reuse */
					if ( op && nmfn < xmfn )
						nmfn = xmfn;
					nmfn++; /* continue after this */
				}
				xmfn = fld = op = 0;
			}
		schicht: ;
		} /* remake */
		db->mfc[LMFC_NMFN] =
		db->xrlen = lio_size( db->mst[MST_XRF] ) / db->ptrl;
		if ( (DB_MMAP & db->flags)
			&& db->xrlen
			&& db->xrlen*db->ptrl
			== lio_mmap( &db->mst[MST_XRF], (void**)&db->mmap, db->xrlen*db->ptrl )
		)
			db->mmlen = db->xrlen;
		log_msg( LOG_INFO, "mapped %d*%d = %d",
			db->xrlen, db->ptrl, db->xrlen*db->ptrl );
		db->flags |= DB_TXTOPEN;
		db->flags &= ~DB_VARI; /* clear alignment and such */
	} /* if ( txtfd ) */

	/* supporting files, ctables */
	p = buf;
	if ( 0 >= (sz = lio_slurp( &p, sizeof(buf), setext(path,EXT_SUP_ACT,uc), 1 ))
		|| lcs_mktab( db->ctab+LCS_CTYPE, p, sz, LCS_A )
	)
		memcpy( db->ctab+LCS_CTYPE, lcs_latin1_ct, sizeof(db->ctab[0]) );
	if ( 0 >= (sz = lio_slurp( &p, sizeof(buf), setext(path,EXT_SUP_UCT,uc), 1 ))
		|| lcs_mktab( db->ctab+LCS_UCASE, p, sz, 0 )
	)
		memcpy( db->ctab+LCS_UCASE, lcs_latin1_uc, sizeof(db->ctab[0]) );
	/* fill header */

	if (! fdt) {
		if ( (p = rString (dbpar, OPENISIS_DFDT, 0, buf, sizeof(buf))) ) {
			Rec *recfdt = 0;
			Db *dbfdt = nDbByName (openisis_stub0, p);
			if ( dbfdt)
				recfdt = dRead (dbfdt->dbid, 1);
			else {
				int idfdt = ldb_open (p, 0, syspar, 0);
				if (0 <= idfdt) {
					recfdt = dRead (idfdt, 1); /*MMM*/
					cDClose (idfdt);
				}
			}
			if (recfdt)
				fdt = fRec2Fdt (recfdt);
		} else if ( gotopt )
			fdt = fRec2Fdt(dbpar);
		if (! fdt)
			fdt = fFromFile (path);
	}
	db->head.fdt = fdt;
	if (fdt)
		log_msg( LOG_INFO, "have %d fdt entries for %s",
			fdt->len, db->head.name);
	else
		log_msg( LOG_INFO, "have no fdt for %s", db->head.name);

	db->head.tms = timeUpd(0); /* what watch? */
	log_msg( LOG_INFO, "tms %d for %s", db->head.tms, db->head.name);


	/* set path and name */
	if (0 <= (i = ldb_last_path_sep (db->path))) {
		if (i)
			strncpy(path, db->path, i)[i] = 0;
		else
			strcpy (path, "/");
		dbpar = rSet (dbpar, RCHG | RDIS, OPENISIS_DPATH, path, 0);
	}
	dbpar = rSet (dbpar, RCHG | RDIS, OPENISIS_DNAME, db->head.name, 0);

	/* set encoding */
	if (!(p = rString (dbpar, OPENISIS_DENC, 0, buf, sizeof(buf))))
		if ( (syspar
				&& (p = rString (syspar, OPENISIS_DENC, 0, buf, sizeof(buf))))
			|| (p = autoenc)
		)
			dbpar = rSet(dbpar, RDIS, OPENISIS_DENC, p, 0);
	if ( p )
		log_msg( LOG_INFO, "using encoding %s for %s", p, db->head.name);

	db->head.cfg = dbpar;

	/* done */
	db->flags |= DB_OPEN;

	if ( writable && LVAR_PAC == (DB_VARI & db->flags) )
		db->flags |= DB_WRITABLE;

	/*
	if ( (dbpar || syspar) && 0 < rInt2(dbpar, syspar, OPENISIS_DDUMP, -1) ) {
		int off = 0;
		int *r;
		do {
			if ( (r = ldb_readRecAtOff(dbid,off,&off)) )
				mFree( r );
		} while ( 0 < off );
		exit(0);
	}
	*/

	/* init oxi */
	if ( writable )
		db->oxi.flg |= LBT_WRITE;
	if ( (p = getenv("OXITYP")) && 0 < (i = atoi(p)) && 4 > i )
		db->oxi.typ = i << 4;
	if ( !lbtret && !lbt_init( &db->oxi ) )
		db->flags |= DB_LBTOPEN;

	if ( ! invret
		&& ! (ret = readlog( db->cnt[0], db->inv[INV_CNT],
			0, db, LSET_INV, LSTR_CNT ))
		&& ! (ret = readlog( db->cnt[1], db->inv[INV_CNT],
			-1, db, LSET_INV, LSTR_CNT ))
	) {
		if ( lbtret )
			db->flags |= DB_INVOPEN;
		else {
			if ( copyidx ) {
				DXLoop l;
				log_msg( LOG_INFO, "copying traditional index" );
				lbtret = 0;
				memset( &l, 0, sizeof(l) );
				l.me = & db->oxi;
				l.cb = (DXCb*)cXAdd;
				lbt_batch( & db->oxi, 5 );
				search( db, 0, 0, 0, &l );
				cXAdd( & db->oxi, 0, 0 );
			}
			closefiles( db->inv, INV_FILES );
		}
	}

	return dbid;

cleanup:
	/* cleanup ... */
	db->flags = 0;
	closefiles( &db->oxi.fd, 1 );
	closefiles( &txtfd, 1 );
	closefiles( db->inv, INV_FILES );
	closefiles( db->mst, MST_FILES );
	return 0 > ret ? ret : ret ? -ret : -1;
}	/* ldb_open */


/* ************************************************************
	package data
*/



/* ************************************************************
	package functions
*/

int *ldb_readRecAtOff ( int dbid, lxref off, int *nxtoff )
{
	int *rec;
	LDb *db = getDb( dbid );
	if ( ! db ) {
		log_msg( LOG_ERROR, "\tat ldb_readRecAtOff" );
		return 0;
	}
	if ( 0 == off )
		off = 64;
	rec = getMfr( db, off, nxtoff );
	if ( ! rec )
		return 0;
	LOG_DBG( LOG_VERBOSE, "db %d off %d: got %hd bytes",
		dbid, off, !rec ? -1 : rec[LMFR_RECL] );
	return rec;
}	/* ldb_readRecAtOff */



int ldb_search ( int dbid, const char *key, LdbPost *post, Rec *rec )
{
	LDb *db = getDb( dbid );
	Key k;

	if ( ! db )
		return -ERR_BADF;
	if ( post ) { /* prepare for postings */
		if ( ! post->len )
			post->len = sizeof(post->p)/sizeof(post->p[0]); /* standard length */
		if ( LDB_NOT & post->mode )
			post->mode |= LDB_AND;
	}
	if ( DB_INVOPEN & db->flags )
		return search( db, key, post, rec, 0 );
	if ( !(DB_LBTOPEN & db->flags) )
		return -ERR_BADF;
	if ( db->oxi.bat )
		return -ERR_BUSY;
	memset( &k, 0, sizeof(k) );
	if ( ! key ) {
		k.byt[0] = '$';
		k.len = 1;
	} else {
		unsigned char *uk = (unsigned char*)key;
		int l = strlen( key );
		if ( l > 255 )
			l = 255;
		k.len = (unsigned char)l;
		while ( l-- )
			k.byt[l] = db->ctab[LCS_UCASE].c[ uk[l] ];
	}
	return lbt_search( &db->oxi, &k, post, rec );
} /* ldb_search */


int ldb_p2s ( Set *set, LdbPost *post )
{
	int *s = set->id;
	int last=0, max = set->len;
	int i;
	set->len = 0;
	if ( ! max )
		max = OPENISIS_SETLEN;
	max--;
	if ( !post->fil )
		return 0L;
	s[0] = LDBP_ROW(post->p);
	for ( i=1; i<post->fil && last < max; i++ ) {
		int row = LDBP_ROW(post->p+i);
		if ( s[last] != row )
			s[++last] = row;
	}
	return set->len = last+1;
}	/* ldb_p2s */


#if 0
LcsTab *ldb_tabs( int dbid )
{
	LDb *db = getDb( dbid );
	return ! db ? 0 : db->ctab;
}	/* ldb_tabs */
#endif


Db *ldb_getdb (int dbid) {
	LDb *db = getDb (dbid);
	return db ? &db->head : 0;
}

/* ************************************************************
	public functions
*/
int dMaxId ( int dbid )
{
	LDb *db = getDb( dbid );
	if ( ! db )
		return -ERR_BADF;
	return db->mfc[LMFC_NMFN] - 1;
}	/* dMaxId */


Raw *dRaw ( int dbid, int rowid )
{
	int off;
	int *rec = 0;
	LDb *db;

	if ( LIO_LOCK() ) return 0;
	db = getDb( dbid );
	if ( ! db ) {
		log_msg( LOG_ERROR, "\tat openIsisReadRaw %d", rowid );
		goto done;
	}
	off = getOff( db, rowid, 0 );
	log_msg( LOG_INFO, "found xref 0x%08x for %d", off, rowid );
	if ( 0 >= off ) {
		log_msg( LOG_INFO, "found deleted xref 0x%08x for %d", off, rowid );
		goto done;
	}
	rec = getMfr( db, off, 0 );
	if ( ! rec ) {
		log_msg( LOG_WARN, "\tno record at %d rowid %d", off, rowid );
		goto done;
	}
	LOG_DBG( LOG_VERBOSE, "db %d row %d: got %hd bytes",
		dbid, rowid, !rec ? -1 : rec[LMFR_RECL] );
	if ( rec[LMFR_MFN] != rowid ) {
		log_msg( LOG_ERROR, "got mfn %d expected %d", rec[LMFR_MFN], rowid );
		mFree( rec );
		rec = 0;
		goto done;
	}
done:
	(void)LIO_RELE();
	return (Raw*)rec;
}	/* dRaw */


Rec *dRead ( int dbid, int rowid )
{
	LDb *db = getDb( dbid );
	Rec *r;
	if ( DB_TXTOPEN & db->flags )
		return dText( db, rowid, 0 );
	if ( (r = (Rec *) dRaw( dbid, rowid )) ) {
		char * base = (char*)r;
		Field *f = r->field;
		int i = r->len;
		for ( ; i--; f++ )
			f->val = base + (int)f->val;
		assert( RECOK( r ) );
	}
	return r;
}	/* dRead */


int dWritex ( int dbid, Rec *rec, Rec *idx )
{
	LDb *db = getDb( dbid );
	int ret = 0;

	if ( ! db )
		return -ERR_BADF;
	if ( !(DB_WRITABLE & db->flags) )
		return log_msg( ERR_INVAL, "db %d not writable", dbid );
	if ( rec && (ret =
		DB_TXTOPEN & db->flags ? pText( db, rec, 0 ) : putRec( db, rec )
	) )
		return ret;
	if ( idx ) {
		const unsigned char *const uc = db->ctab[LCS_UCASE].c;
		int delmode = 0;
		int tag = -1;
		int mode = 'f'; /* 'w', 's' */
		int occ = 0;
		int pos = 0;
		int cut = 30;
		int mfn = rec ? rec->rowid : 0;
		Hit h;
		Key k;
		Field *f = idx->field, *last = f + idx->len - 1;

		for ( ; f <= last; f++ ) {
			const char *val = f->val;
			int len = f->len;
			int del = delmode;

			k.val.len = 0;
			switch ( f->tag ) {
			case XCTL: { /* index cmd [opt] */
				const char *cmd = val, *e = val + len;
				int cmdlen, opt = 0, haveopt;
				while ( val < e && 64 < *val ) /* eat ASCII letters */
					val++;
				cmdlen = val - cmd;
				if ( val < e && (TAB == *val || ' ' == *val) )
					val++;
				haveopt = val < e && a2il( val, e-val, &opt );
				if ( ! cmdlen ) {
					cut = haveopt ? opt : 30;
					continue;
				}
				switch (*cmd) {
				case 'f': /* fields */
					mode = 'f';
					occ = opt;
					pos = 0;
					continue;
				case 'w': /* words */
					mode = 'w';
					pos = opt;
					continue;
				case 's': /* split */
					mode = 's';
					pos = opt;
					continue;
				case 'a': /* add */
					delmode = 0;
					occ = pos = 0;
					continue;
				case 'd': /* del */
					delmode = 1;
					occ = pos = 0;
					continue;
				case 'm': /* mfn */
					mfn = opt;
					occ = pos = 0;
					continue;
				}
				return log_msg( ERR_INVAL, "bad index control '%.*s'", cmdlen, cmd );
			}
			case XHIT: {
				int i = 0, v[5], *pv = v;
				const char *e = val + len;
				if ( len )
					switch (*val) {
					case '+': del = 0; val++; break;
					case '-': del = 1; val++; break;
					}
				for ( ; val < e && i<5; i++ ) {
					int dig = a2il( val, e-val, v+i );
					val += dig;
					if ( val >= e || TAB == *val )
						break;
					if ( '.' != *val )
						return log_msg( ERR_INVAL,
							"bad HIT '%.*s' after %d", e-val, val, v[i] );
					val++;
				}
				h.dbn = 0;
				h.mfn = mfn;
				h.pos = pos;
				h.occ = occ;
				h.tag = tag;
				switch ( i ) {
				case 5: h.dbn = (unsigned short)*pv++;
				case 4: h.mfn = (unsigned)*pv++;
				case 3: h.pos = (unsigned short)pv[2];
				case 2: h.occ = (unsigned short)pv[1];
				case 1: h.tag = (unsigned short)pv[0];
				/* case 0: ! f->len */
				}
				if ( val < e && TAB == *val )
					val++;
				len = e - val;
			} break; /* case XHIT */
			case XFST:
				return log_msg( ERR_IDIOT, "sorry, XFST not implemented" );
#if 0
			case XADD: /* binary key */
				/* if ( f->len < db->oxi.vsz )
					memset( k.val.byt, 0, db->oxi.vsz - f->len );
				*/
				memcpy( k.val.byt
					+ (f->len < (int)db->oxi.vsz ? (int)db->oxi.vsz - f->len : 0),
					f->val, f->len > (int)db->oxi.vsz ? (int)db->oxi.vsz : f->len );
				k.val.len = db->oxi.vsz;
				break;
#endif
			default:
				if ( 0 > f->tag )
					return log_msg( ERR_INVAL, "bad index control tag %d", f->tag );
				switch ( mode ) { /* check for tag change */
				case 'f':
					if ( tag == f->tag )
						occ++;
					else
						occ = 0;
					break;
				case 'w':
					if ( tag == f->tag )
						pos++;
					else
						pos = 0;
					break;
				}
				tag = f->tag;
				h.dbn = 0;
				h.mfn = mfn;
				h.pos = pos;
				h.occ = occ;
				h.tag = f->tag;
			}
			if ( ! k.val.len ) { /* not ADD/DEL: use hit, val */
				unsigned char *dst = k.byt;
				const unsigned char *src = (const unsigned char *)val;
				if ( cut < len )
					len = cut;
				k.len = (unsigned char)len;
				while ( len-- )
					*dst++ = uc[ *src++ ];
				cXMkVal( &db->oxi, &k.val, &h );
				LOG_DBG( LOG_DEBUG, "#%d %c key '%.*s' hit %d.%d.%d.%d.%d",
					f - idx->field, del ? '-' : '+', k.len, k.byt,
					h.dbn, h.mfn, h.tag, h.occ, h.pos );
			}
			ret = del ? lbt_del( &db->oxi, &k ) : lbt_add( &db->oxi, &k );
		}
	}
	return ret;
}	/* dWritex */


int dWrite ( int dbid, Rec *rec )
{
	/* TODO: use FST lines as idx */
	return dWritex( dbid, rec, 0 );
}	/* dWrite */


Rec* dTerm ( Rec *rec, int dbid, const char *key )
{
	return 0 > ldb_search( dbid, key, 0, rec ) ? 0 : rec;
}	/* dTerm */


int dXLoop ( int dbid, DXLoop *l )
{
	LDb *db = getDb( dbid );

	if ( !db )
		return -ERR_BADF;
	if ( OPENISIS_IDXTRAD & l->flg ) {
		if ( !(db->flags & DB_INVOPEN) )
			return -ERR_BUSY;
		return search( db, 0, 0, 0, l );
	}
	if ( !(db->flags & DB_LBTOPEN) || db->oxi.bat )
		return -ERR_BUSY;
	return lbt_loop( & db->oxi, l );
}	/* dXLoop */



int cInit ( int argc, const char **argv, CLockFunc lockfunc )
{
	(void)argc; (void)argv;
	cOpen( 0 );
	if ( lockfunc )
		lio_lock = lockfunc;
	return 0;
}


Db* cDOpen (const char *dbname, Rec *dbpar, Rec *syspar, Fdt *fdt) {
	int dbid;
	if ( ! init )
		cOpen( 0 );
	dbid = ldb_open (dbname, dbpar, syspar, fdt);
	if (0 <= dbid) {
		return &dbs[dbid].head;
	}
	return 0;
}

int cDOpenv ( const char *dbname, const char **argv, int argc )
{
	Rec *dbpar = 0;
	int  rt;
	if ( ! init )
		cOpen( 0 );
	if (argc) {
		dbpar = rSet (0, RARGV | RFDT | RNOC | RIGN | argc,
			openIsisFdtDbpar, argv);
	}
	rt = ldb_open (dbname, dbpar, 0, 0);
	if (dbpar) {
		mFree (dbpar);
	}
	return rt;
}


int cDClose ( int dbid )
{
	LDb *db = getDb( dbid );
	if ( ! db )
		return -ERR_BADF;
	if ( LIO_LOCK() ) return -ERR_BUSY;
	if ( DB_MODIFIED == ((DB_MODIFIED|DB_TXTOPEN) & db->flags) ) {
		/* write back the MF control */
		Mfc mfc;
		/* if ( 498 < (db->mflen & 511) ) db->mflen = ~511 & (db->mflen + 14); */
		mfc.ctlm = rvi( db->mfc[LMFC_CTLM] );
		mfc.nmfn = rvi( db->mfc[LMFC_NMFN] );
		mfc.nmfb = rvi( 1 + (db->mflen >> 9) );
		mfc.nmfp = rvs( 511 & db->mflen );
		mfc.type = rvs( db->mfc[LMFC_TYPE] );
		mfc.rcnt = rvi( db->mfc[LMFC_RCNT] );
		mfc.mfx1 = rvi( db->mfc[LMFC_MFX1] );
		mfc.mfx2 = rvi( db->mfc[LMFC_MFX2] );
		mfc.mfx3 = rvi( db->mfc[LMFC_MFX3] );
		if ( sizeof(mfc) != lio_pwrite( &db->mst[MST_MST], &mfc, sizeof(mfc), 0) )
			log_msg( ERR_TRASH, "could not write MST header" );
	}
	if ( db->mmap ) {
		if ( (DB_MODIFIED|DB_TXTOPEN) == ((DB_MODIFIED|DB_TXTOPEN) & db->flags) )
			memcpy( db->mmap, ISIX, 4 ); /* force newer mtime on proper close */
		lio_mmap( 0, (void**)&db->mmap, db->mmlen*db->ptrl );
	}
	db->mmlen = 0;
	closefiles( db->mst, MST_FILES );
	if ( DB_INVOPEN & db->flags )
		closefiles( db->inv, INV_FILES );
	if ( DB_LBTOPEN & db->flags )
		lbt_close( &db->oxi );
	db->flags = 0L;
	if ( db->path ) mFree( (char*)db->path );
	if (db->head.cfg) mFree (db->head.cfg);
	if (db->head.fdt) fFree (db->head.fdt);
	memset( db, 0, sizeof(db) );
	(void)LIO_RELE();
	return 0;
}	/* cDClose */


int cDCheck ( int dbid, int flags )
{
	static char dot = '.';
	int *r;
	LDb *db = getDb( dbid );
	int nxtoff = 64, off;

	if ( ! db )
		return -ERR_BADF;
	(void)flags;
	do {
		lio_write( &lio_out, &dot, 1 );
		if ( (r = ldb_readRecAtOff(dbid,off=nxtoff,&nxtoff)) ) {
			int o = getOff( db, r[LMFR_MFN], 0 );
			if ( o != off ) {
				log_msg( LOG_WARN, "mfn %d xrf %d != real %d\n",
					r[LMFR_MFN], o, off );
			}
			mFree( r );
		}
	} while ( 0 < nxtoff );
	return 0;
}	/* cDCheck */


OpenIsisIdx *cXOpen ( int dbid, int mode )
{
	LDb *db = getDb( dbid );
	if ( !db
		|| !(db->flags & DB_LBTOPEN)
		|| !(db->oxi.flg & LBT_WRITE) /* may be writable if db is not */
		|| db->oxi.bat
		/*
		preliminary undocumented feature:
		mode -1 gives direct access in non-batch mode
		*/
		|| (0 <= mode && lbt_batch( & db->oxi, (unsigned char)mode ))
	)
		return 0;
	return & db->oxi;
}	/* cXOpen */