Revision 238 (by dpavlin, 2004/03/08 17:46:16) tagging openisis 0.9.0
/*
	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: lstb.c,v 1.41 2003/06/15 15:57:43 mawag Exp $
	implementation of client side session and communication functions.
*/

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

#include "openisis.h"
#include "loi.h"
#include "lcli.h"
#include "ldb.h"
#include "ldsp.h"
#include "lfdt.h"
#include "luti.h"

#ifdef WIN32
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

#define LSTB_STHOSTLEN   32

#define LSTB_MAXDBS      255  /* according to OPENISIS_DBIDMSK */
#define LSTB_MAXSTUBS    255  /* according to OPENISIS_SCIDMSK */

#define LSTB_INCRDB      8
#define LSTB_INCRSTUB    8

/* ************************************************************
 * meta
*/

/* stub id 0      has idx -1 */
/* stub id 0x0100 has idx  0 */

#define SC_X2I(s)  ( ( (1 + (s)) << 8 ) & OPENISIS_SCIDMSK )

#define SC_I2X(s)  ( ( ((s) & OPENISIS_SCIDMSK) >> 8 ) - 1 )

#define DB_X2I(d,s)  ( ((s) & OPENISIS_SCIDMSK) | ((d) & OPENISIS_DBIDMSK) )

#define DB_I2X(d)    ( (d) & OPENISIS_DBIDMSK )

#define DB_IDX_OK(st,idx)   \
	(0 <= (idx) && (st)->head.ndbs > (idx) && * (st)->head.dbs[idx]->name)

/* ************************************************************
 * client view of db
*/

typedef struct {
	Db head;
} CDb;

static void cdbCtor (CDb *that,
	const char *name, int dbid, int tms, Fdt *fdt, Rec *cfg
) {
	strncpy (that->head.name, name, DBNLEN - 1) [DBNLEN - 1] = 0;
	that->head.dbid = dbid;
	that->head.tms  = tms;
	that->head.fdt  = fdt;
	that->head.cfg  = cfg;
}

static void cdbDtor (CDb *that) {
	if (that->head.fdt) {
		fFree (that->head.fdt);
	}
	if (that->head.cfg) {
		mFree (that->head.cfg);
	}
	memset (that, 0, sizeof (CDb));
}

/* ************************************************************
 * client session
*/

typedef struct OpenIsisStb {
	Schema    head;
	char      host[LSTB_STHOSTLEN];
	int       port;
	int       srq;   /* rqs serial no */
	Rec      *rqs;   /* last request */
	Rec      *rsp;   /* last response */
	Db       *rdb;   /* db of embedded rsp recs */
	CDb      *dbase; /* db array of non-local schema */
	LutiLT    hsh;
	CliChnl   chn;
	OpenIsisStbRqsProc  *rqsp;
	OpenIsisStbDelProc  *delp;
	void     *cld;   /* client_data of rqsp */
	/*MMM deprecated*/
	OpenIsisRspCb       *actcb;
	void                *actcld;
	OpenIsisStubCbData   cbdta;
} OpenIsisStb;

Stub stub0 = 0;

static Stub  RmtStubs = 0;
static int   NumStubs = 0;

static LutiLT StubLT = 0;

#define STBSIZE  (sizeof(Stb))

static int stbLocalRqs (Stub that, Rec *rqs);
static int stbSyncRqs (Stub that, Rec *rqs);

static void stbClear (Stub that) {
	memset (that, 0, STBSIZE);
	that->chn.sd = -1;
}

static int stbCtor (Stub that,
	const char *name, int scid, Rec *cfg,
	OpenIsisStbRqsProc *rqsp, OpenIsisStbDelProc *delp, void *cld,
	OpenIsisStubCbData *cbd
) {
	strncpy (that->head.name, name, SCNLEN - 1) [SCNLEN - 1] = 0;
	that->head.scid = scid;
	if (scid) {
		if (! rString (cfg, OPENISIS_SC_HOST, 0, that->host, LSTB_STHOSTLEN)) {
			return 0;
		}
		that->port = rInt (cfg, OPENISIS_SC_PORT, 0, 0);
		if (0 >= that->port) {
			return 0;
		}
		if (rqsp) {
			that->rqsp = rqsp;
		}
		else {
			that->rqsp = &stbSyncRqs;
		}
	}
	else {
		that->rqsp = &stbLocalRqs;
	}
	that->head.cfg =
		rSet (cfg, RCHG | RDIS, OPENISIS_SC_NAME, that->head.name, 0);
	that->delp = delp;
	that->cld  = cld;
	that->hsh  = luti_ltnew ();
	if (cbd) {
		memcpy (&that->cbdta, cbd, sizeof (OpenIsisStubCbData));
	}
	luti_ltadd (StubLT, that->head.name, scid);
	return !0;
}

/* return idx or -err */
static int cdbNewIdx (Stub that) {
	int     ndb = that->head.ndbs;
	int     rt, j;
	for (j = 0; ndb > j; ++j) {
		if (! * that->dbase[j].head.name) {
			return j;
		}
	}
	rt = luti_ptrincr (&that->dbase, &ndb,
		LSTB_INCRDB, sizeof (CDb), LSTB_MAXDBS);
	if (0 > rt) {
		return sMsg (ERR_NOMEM, "%s: cdbNewIdx: out of memory",
			that->head.name);
	}
	rt = luti_ptrincr (&that->head.dbs, &that->head.ndbs,
		LSTB_INCRDB, sizeof (CDb*), LSTB_MAXDBS);
	if (0 > rt) {
		return sMsg (ERR_NOMEM, "%s: cdbNewIdx: out of memory",
			that->head.name);
	}
	for (j = that->head.ndbs; 0 <= --j; ) {
		that->head.dbs[j] = (Db*) (that->dbase + j);
	}
	return rt;
}

/* return bool newly allocated or -err */
static int cdbNewIdx0 (Stub that, int dbid) {
	int  rt;
	if (dbid < that->head.ndbs) {
		if (that->head.dbs[dbid]) {
			return 0;
		}
		return 1;
	}
	rt = luti_ptrincr (&that->head.dbs, &that->head.ndbs,
		1 + dbid - that->head.ndbs, sizeof (CDb*), -1);
	if (0 > rt) {
		return sMsg (ERR_NOMEM, "%s: cdbNewIdx0(%d): out of memory",
			that->head.name, dbid);
	}
	return 1;
}

static int stbNewIdx () {
	int   j;
	for (j = 0; NumStubs > j; ++j) {
		if (! * RmtStubs[j].head.name) {
			return j;
		}
	}
	j = luti_ptrincr (&RmtStubs, &NumStubs,
		LSTB_INCRSTUB, STBSIZE, LSTB_MAXSTUBS);
	/* stbClear (RmtStubs + j) done in nOpen */
	return j;
}

static Stub stbById (int sid) {
	int idx;
	if (0 > sid || ! stub0) {
		return 0;
	}
	if (0 == sid) {
		return stub0;
	}
	idx = SC_I2X (sid);
	if (0 > idx) {
		log_msg (LOG_ERROR, "stbById: illegal id %x", sid);
		return 0;
	}
	if (NumStubs <= idx) {
		return 0;
	}
	if (! * RmtStubs[idx].head.name) {
		return 0;
	}
	return RmtStubs + idx;
}

static void stbDtor (Stub that) {
	luti_ltrmv (StubLT, that->head.name);
	if (that->cbdta.delcb) {
		if (that->actcld) {
			(*that->cbdta.delcb) (
				that->cbdta.delcld, that, that->actcld);
		}
		if (that->cbdta.dfltcld) {
			(*that->cbdta.delcb) (
				that->cbdta.delcld, that, that->cbdta.dfltcld);
		}
		(*that->cbdta.delcb) (that->cbdta.delcld, that, 0);
	}
	if (that->dbase) {
		int j;
		for (j = that->head.ndbs; 0 <= --j;  ) {
			if (* that->dbase[j].head.name) {
				cdbDtor (that->dbase + j);
			}
		}
		mFree (that->dbase);
	}
	if (that->head.dbs) {
		mFree (that->head.dbs);
	}
	if (that->head.cfg) {
		mFree (that->head.cfg);
	}
	luti_ltdel (that->hsh);
	nClean (that);
	stbClear (that);
}

static int stbDbidFromName (Stub that, const char *dbn) {
	int id;
	if (! dbn) {
		return -1;
	}
	id = luti_ltget (that->hsh, dbn);
	return id;
}

static int stbDbIdxFromName (Stub that, const char *dbn) {
	int idx = stbDbidFromName (that, dbn);
	if (0 > idx) {
		return -1;
	}
	idx = DB_I2X (idx);
	if (! DB_IDX_OK (that, idx)) {
		return -1;
	}
	return idx;
}

static int stbOpenDb (Stub that) {
	char  dbn[DBNLEN];
	Fdt  *fdt;
	Rec  *cfg;
	int   idx, dbid, tms;
	int   newdb = 0;

	if (! rString (that->rsp, OPENISIS_COM_DBN, 0, dbn, DBNLEN)) {
		return sMsg (ERR_IDIOT, "%s: stbOpenDb: missing db name in rsp",
			that->head.name);
	}
	idx = stbDbIdxFromName (that, dbn);

	if (0 > idx) {
		idx = cdbNewIdx (that);
		if (0 > idx) {
			return idx;
		}
		newdb = !0;
	}
	else {
		cdbDtor (that->dbase + idx);
	}

	fdt = fRec2Fdt (that->rsp);
	cfg = luti_unwrap (that->rsp, 0, OPENISIS_COM_CFG, -1);
	tms = rInt (that->rsp, OPENISIS_COM_TMS, 0, 0);
	dbid = DB_X2I (idx, that->head.scid);

	cdbCtor (that->dbase + idx, dbn, dbid, tms, fdt, cfg);

	if (newdb) {
		luti_ltadd (that->hsh, dbn, dbid);
	}
	return 0;
}

static int stbOpenDb0 (Stub that) {
	Db  *dbh;
	int  newdb = 0;
	int  dbid  = rInt (that->rsp, OPENISIS_RSP_DBID, -1, 0);
	if (0 > dbid) {
		return sMsg (ERR_IDIOT, "stbOpenDb0: missing dbid in rsp");
	}

	newdb = cdbNewIdx0 (that, dbid);
	if (0 > newdb) {
		return newdb;
	}

	dbh = ldb_getdb (dbid);
	if (! dbh) {
		return sMsg (ERR_TRASH, "stbOpenDb0: no such db %d", dbid);
	}
	log_msg (LOG_INFO, "stbOpenDb0 %d %s", dbid, dbh->name);

	that->head.dbs[dbid] = dbh;
	if (newdb) {
		luti_ltadd (that->hsh, dbh->name, dbid);
	}
	return 0;
}

static int stbCloseDb (Stub that) {
	char  dbn[DBNLEN];
	CDb  *db;
	int   dbid;
	if (! rString (that->rsp, OPENISIS_COM_DBN, 0, dbn, sizeof (dbn))) {
		return sMsg (ERR_IDIOT, "%s: stbCloseDb: missing db name in rsp",
			that->head.name);
	}
	dbid = stbDbIdxFromName (that, dbn);
	if (0 > dbid) {
		return sMsg (ERR_TRASH, "%s: stbCloseDb: illegal db name <%s> in rsp",
			that->head.name, dbn);
	}
	db = that->dbase + dbid;
	luti_ltrmv (that->hsh, db->head.name);
	cdbDtor (db);
	return 0;
}

static int stbCloseDb0 (Stub that) {
	char   dbn[DBNLEN];
	int    dbid = rInt (that->rsp, OPENISIS_RSP_DBID, -1, 0);
	if (0 > dbid) {
		return sMsg (ERR_IDIOT, "stbCloseDb0: missing dbid in rsp");
	}
	if (that->head.ndbs <= dbid) {
		return sMsg (ERR_TRASH, "stbCloseDb0: illegal dbid %d(%d) in rsp",
			dbid, that->head.ndbs);
	}
	if (! rString (that->rsp, OPENISIS_COM_DBN, 0, dbn, DBNLEN)) {
		return sMsg (ERR_IDIOT, "stbCloseDb0: missing db name in rsp\n");
	}
	log_msg (LOG_INFO, "stbCloseDb0 %d %s", dbid, dbn);
	luti_ltrmv (that->hsh, dbn);
	that->head.dbs[dbid] = 0;
	return 0;
}

/*	(local) response callback
*/
static int stbRspCb (Rec **rsp, void *cld) {
	Stub      that;
	Rec      *rsp2;
	int       sid, ser, err, err2, err3;

	(void)cld;
	sid = rInt (*rsp, OPENISIS_COM_SID, -1, 0);
	ser = rInt (*rsp, OPENISIS_COM_SER, -1, 0);
	err = rInt (*rsp, OPENISIS_RSP_ERR, -1, 0);

	that = stbById (sid);
	if (! that) {
		return sMsg (ERR_TRASH, "illegal stub id %d in stbRspCb\n", sid);
	}
	if (ser != that->srq) {
		return sMsg (ERR_TRASH, "%s: rqs serial mismatch %d != %d",
			that->head.name, ser, that->srq);
	}
	if (! that->rqs) {
		return sMsg (ERR_TRASH, "%s/%d: response without request",
			that->head.name, sid);
	}
	if (that->rsp) {
		sMsg (ERR_TRASH, "%s: multiple responses",
			that->head.name);
		mFree (that->rsp);
	}
	that->rdb = 0;
	that->rsp = *rsp;
	*rsp = 0;

	err2 = err3 = 0;

	if (0 == err) {
		int rtyp = rInt (that->rqs, OPENISIS_RQS_TYPE, -1, 0);
		switch (rtyp) {

		case OPENISIS_RQST_LSDB:
		case OPENISIS_RQST_EVAL:
			break;

		case OPENISIS_RQST_CLOS:
			if (sid) {
				err2 = sMsg (ERR_IDIOT, "%s: db close on remote stub",
					that->head.name);
			}
			else {
				err2 = stbCloseDb0 (that);
			}
			break;
		
		case OPENISIS_RQST_OPEN:
			if (sid) {
				err2 = sMsg (ERR_IDIOT, "%s: db open on remote stub",
					that->head.name);
				break;
			}
			err2 = stbOpenDb0 (that);
			goto setdb;
		
		case OPENISIS_RQST_MNT:
			if (0 == sid) {
				err2 = sMsg (ERR_IDIOT, "db mount on local stub");
				break;
			}
			err2 = stbOpenDb (that);
			goto setdb;

		default:
		setdb: {
			char dbn[DBNLEN];
			int rdid, rx;
			if (! rString (that->rsp, OPENISIS_COM_DBN, 0, dbn, sizeof(dbn))) {
				if (sid) {
					err2 = sMsg (ERR_IDIOT,
						"%s: stbRspCb: missing db name in rsp",
						that->head.name);
				}
				else {
					err2 = sMsg (ERR_IDIOT,
						"stbRspCb: missing db name in rsp");
				}
			}
			else {
				rdid = stbDbidFromName (that, dbn);
				if (0 > rdid) {
					if (sid) {
						err2 = sMsg (ERR_TRASH,
							"%s: stbRspCb: illegal db name <%s> in rsp",
							that->head.name, dbn);
					}
					else {
						err2 = sMsg (ERR_TRASH,
							"stbRspCb: illegal db name <%s> in rsp", dbn);
					}
				}
				else {
					/* see nDbById */
					rx = DB_I2X (rdid);
					if (! DB_IDX_OK (that, rx)) {
						if (sid) {
							err2 = sMsg (ERR_TRASH,
								"%s: stbRspCb: illegal db id %d %d <%s>",
								that->head.name, rdid, rx, dbn);
						}
						else {
							err2 = sMsg (ERR_TRASH,
								"stbRspCb: illegal db id %d %d <%s>",
								rdid, rx, dbn);
						}
					}
					else {
						that->rdb = that->head.dbs[rx];
					}
				}
			}
		}
		} /* switch */
	}
	else { /* err */
		int tms;
		tms = rInt (that->rsp, OPENISIS_COM_TMS, 0, 0);
		if (tms && sid) { /* remounted */
			err2 = stbCloseDb (that);
		}
	}

	rsp2 = rAddI (that->rsp, OPENISIS_RSP_CERR, err2, !0);
	if (rsp2) {
		that->rsp = rsp2;
	}

	if (that->actcb) {
		err3 = (*that->actcb) (that->actcld,
			that, that->rsp, that->rdb);
		if (that->actcld && that->cbdta.delcb) {
			(*that->cbdta.delcb) (
				that->cbdta.delcld, that, that->actcld);
		}
		that->actcb = 0;
		that->actcld = 0;
	}
	else if (that->cbdta.dfltcb) {
		err3 = (*that->cbdta.dfltcb) (that->cbdta.dfltcld,
			that, that->rsp, that->rdb);
	}

	sMsg (LOG_VERBOSE, "stbRspCb(%s): %d/%d err = %x,%x,%x",
		that->head.name, sid, ser, err, err2, err3);

	return err3;
}

static int stbLocalRqs (Stub that, Rec *rqs) {
	(void)that;
	return ldspProcess (rqs, 0, &stbRspCb, 0);
}

static int stbSyncRqs (Stub that, Rec *rqs) {
	Rec *rsp;
	int  rt;
	if (0 > that->chn.sd) {
		rt = cliConnect (&that->chn, that->host, that->port);
		if (0 > rt) {
			return rt;
		}
	}
	rt = cliWrite (&that->chn, rqs);
	if (0 > rt) {
		return rt;
	}
	rsp = cliRead (&that->chn);
	if (! rsp) {
		return sMsg (ERR_IO, "stbSyncRqs: read error %d - %s", 
			that->chn.err, strerror (that->chn.err));
	}
	rt = stbRspCb (&rsp, 0);
	if (rsp) {
		mFree (rsp);
	}
	return rt;
}

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

/* ************************************************************
	public functions
*/

Stub nInit (
	int argc, const char **argv, OpenIsisStubCbData *dta
) {
	Rec *cfg = 0;

	if (stub0) {
		return stub0;
	}

	StubLT = luti_ltnew ();
	stub0 = mAlloc (STBSIZE);
	stbClear (stub0);

	if (0 < argc) {
		char  buf[PATH_MAX];
		char *logn;
		int   logl;

		cfg = rSet (0, RFDT | RARGV | RIGN | argc, openIsisFdtSyspar, argv);

		logn = rString (cfg, OPENISIS_SLOGF, 0, buf, sizeof(buf));
		logl = rInt (cfg, OPENISIS_SLOGV, -1, 0);
		if (logn || 0 <= logl) {
			cLog (logl, logn);
		}
	}

	stbCtor (stub0, "", 0, cfg, 0, 0, 0, dta);
	return stub0;
}

Stub nOpen (
	const char *name, int argc, const char *argv[], OpenIsisStubCbData *dta
) {
	OpenIsisStb buf;
	Stub  that;
	Rec  *cfg   =  0;
	int   idx, sid;

	if (! stub0 || ! name || ! *name) {
		return 0;
	}

	idx = luti_ltget (StubLT, name);
	if (0 <= idx) {
		idx = SC_I2X (idx);
		if (0 > idx || NumStubs <= idx || ! * RmtStubs[idx].head.name) {
			log_msg (LOG_ERROR, "nOpen: illegal idx %d(%d) for %s",
				idx, NumStubs, name);
			return 0;
		}
		return RmtStubs + idx;
	}

	idx = stbNewIdx ();
	if (0 > idx) {
		return 0;
	}
	if (0 < argc) {
		cfg = rSet (0, RFDT | RARGV | RIGN | argc, openIsisFdtScheme, argv);
	}
	sid = SC_X2I (idx);
	stbClear (&buf);
	if (! stbCtor (&buf, name, sid, cfg, 0, 0, 0, dta)) {
		if (cfg) {
			mFree (cfg);
		}
		return 0;
	}

	that = RmtStubs + idx;
	memcpy (that, &buf, STBSIZE);
	return that;
}

void nClose (Stub that) {
	if (that && *that->head.name) {
		stbDtor (that);
	}
}

void nDeinit () {
	if (stub0) {
		int j;
		if (RmtStubs) {
			for (j = NumStubs; 0 <= --j;  ) {
				if (* RmtStubs[j].head.name) {
					stbDtor (RmtStubs + j);
				}
			}
			mFree (RmtStubs);
			RmtStubs = 0;
			NumStubs = 0;
		}
		for (j = stub0->head.ndbs; 0 <= --j;  ) {
			if (stub0->head.dbs[j]) {
				cDClose (stub0->head.dbs[j]->dbid);
			}
		}
		stbDtor (stub0);
		mFree (stub0);
		stub0 = 0;
		luti_ltdel (StubLT);
		StubLT = 0;
	}
}

Schema* nSchema (Stub that) {
	int idx;
	if (that && ( stub0 == that || ( *that->head.name &&
		0 <= (idx = SC_I2X (that->head.scid)) && NumStubs > idx
	))) {
		return &that->head;
	}
	return 0;
}

void nClean (Stub that) {
	if (that->rqs) {
		mFree (that->rqs);
		that->rqs = 0;
	}
	if (that->rsp) {
		mFree (that->rsp);
		that->rsp = 0;
	}
	that->rdb = 0;
}

int nSend (Stub that,
	Rec *rqs, OpenIsisRspCb *actcb, void *actcld, int dup
) {
	char  buf[DBNLEN];
	char  sid[16];
	char  ser[16];
	char  tms[16];
	char *dbl, *dbn;
	int   dbid, rtyp, setn;

	if (!(that && rqs)) {
		return sMsg (ERR_IDIOT, "nSend: null pointer %x %x",
			(int)that, (int)rqs);
	}
	if (stub0 != that && ! *that->head.name) {
		return sMsg (ERR_IDIOT, "nSend: invalid stub");
	}
	setn = !0;
	rtyp = rInt (rqs, OPENISIS_RQS_TYPE, -1, 0);
	switch (rtyp) {
	case -1:
		return sMsg (ERR_IDIOT, "nSend(%s): request without type",
			that->head.name);
	case OPENISIS_RQST_LSDB:
	case OPENISIS_RQST_EVAL:
		setn = 0;
		break;
	case OPENISIS_RQST_OPEN:
	case OPENISIS_RQST_CLOS:
		if (stub0 != that) {
			return sMsg (ERR_IDIOT, "nSend(%s): request %d denied",
				that->head.name, rtyp);
		}
		break;
	case OPENISIS_RQST_MNT:
		if (stub0 == that) {
			return sMsg (ERR_IDIOT, "nSend: local mount denied");
		}
		rqs = rSet (rqs, RFDT | RDIS | RCHG, openIsisFdtRqs,
			OPENISIS_COM_TMS, "0", 0);
		if (! rqs) {
			return sMsg (ERR_NOMEM, "nSend(%s): out of memory",
				that->head.name);
		}
		break;
	}

	dbid = -1;
	dbn = 0;
	dbl = that->rdb ? that->rdb->name : 0;
	nClean (that);

	i2a (sid, that->head.scid);
	if (0 >= ++that->srq) {
		that->srq = 1;
		log_msg (LOG_INFO, "stub %s: srq loop over 0", that->head.name);
	}
	i2a (ser, that->srq);

	if (setn) {
		if ((dbn = rString (rqs, OPENISIS_COM_DBN, 0, buf, DBNLEN))) {
			setn = 0;
		}
		else {
			if (dbl) {
				dbn = dbl;
			}
			else {
				dbn = rString (that->head.cfg,
					OPENISIS_SC_DFLTDB, 0, buf, DBNLEN);
			}
		}
		if (! dbn) {
			return sMsg (ERR_IDIOT, "nSend(%s): request without db name",
				that->head.name);
		}
		if (0 > rInt (rqs, OPENISIS_COM_TMS, -1, 0)) {
			dbid = stbDbIdxFromName (that, dbn);
			if (0 <= dbid) {
				i2a (tms, that->head.dbs[dbid]->tms);
			}
		}
	}

	if (dup) {
		rqs = rDup (rqs, 0, 0);
		if (! rqs) {
			return -1;
		}
	}

	rqs = rSet (rqs, RFDT | RDIS | RCHG, openIsisFdtRqs,
		OPENISIS_COM_SID, sid, OPENISIS_COM_SER, ser,
		0 <= dbid ? OPENISIS_COM_TMS : 0, tms, 0);
	if (! rqs) {
		return sMsg (ERR_NOMEM, "nSend(%s): out of memory",
			that->head.name);
	}
	if (setn) {
		rqs = rSet (rqs, RFDT | RDIS | RCHG, openIsisFdtRqs,
			OPENISIS_COM_DBN, dbn, 0);
		if (! rqs) {
			return sMsg (ERR_NOMEM, "nSend(%s): out of memory",
				that->head.name);
		}
	}

	that->rqs = rqs;

	if (that->actcld && that->cbdta.delcb) {
		/* ooups */
		(*that->cbdta.delcb) (
			that->cbdta.delcld, that, that->actcld);
	}
	if (actcb) {
		that->actcb = actcb;
		that->actcld = actcld;
	}
	else {
		that->actcb = 0;
		that->actcld = 0;
	}

	return (*that->rqsp) (that, that->rqs);
}

Rec* nRecv (Stub that, Db **db) {
	if (that && (
		stub0 == that || *that->head.name
	)) {
		if (db) {
			*db = that->rdb;
		}
		return that->rsp;
	}
	return 0;
}

Db* nDbByName (Stub that, const char *dbname) {
	if (that && (
		stub0 == that || *that->head.name
	)) {
		int dbid = stbDbIdxFromName (that, dbname);
		if (0 <= dbid) {
			return that->head.dbs[dbid];
		}
	}
	return 0;
}

Db* nDbById (int id) {
	Stub that = stbById (id & OPENISIS_SCIDMSK);
	if (! that) {
		return 0;
	}
	id = DB_I2X (id);
	if (! DB_IDX_OK (that, id)) {
		return 0;
	}
	return that->head.dbs[id];
}

#define FREEPTR(p) if (p) { mFree (p); p = 0; }
#define FREEROW    if (rowids) { FREEPTR(*rowids) }

int nResult (
	Stub that, int **rowids, Rec ***recs, Db **db, int *tot
) {
	Field     *F;
	Rec      **R;
	int       *I;
	int        numt, numr, err, pos, j;

	if (rowids) {
		*rowids = 0;
	}
	if (recs) {
		*recs = 0;
	}
	if (db) {
		*db = 0;
	}
	if (tot) {
		*tot = 0;
	}
	if (! that) {
		return sMsg (ERR_IDIOT, "nGetResult: null pointer");
	}
	if (! that->rsp) {
		return sMsg (ERR_IDIOT, "nGetResult(%s): no response",
			that->head.name);
	}
	err = rInt (that->rsp, OPENISIS_RSP_ERR, 0, 0);
	if (err) {
		return sMsg (ERR_IDIOT, "nGetResult(%s): response error %d",
			that->head.name, err);
	}
	numr = rInt (that->rsp, OPENISIS_RSP_NUMR, -1, 0);
	numt = rInt (that->rsp, OPENISIS_RSP_NUMT, -1, 0);
	if (0 > numr || 0 > numt) {
		return sMsg (ERR_IDIOT, "nGetResult(%s): no record count in response",
			that->head.name);
	}
	if (numr) {
		if (rowids) {
			*rowids = (int*) mAlloc (numr * sizeof (int));
			if (! *rowids) {
				return sMsg (ERR_NOMEM, "nGetResult(%s): out of memory",
					that->head.name);
			}
			for (I = *rowids, j = pos = 0; numr > j; ++I, ++j) {
				*I = rInt (that->rsp, OPENISIS_COM_ROW, -1, &pos);
				if (0 > *I) {
					FREEROW;
					return sMsg (ERR_TRASH,
						"nGetResult(%s): missing rowid %d(%d)",
						that->head.name, j, numr);
				}
			}
		}

		if (recs) {
			*recs = (Rec**) mAlloc (numr * sizeof (Rec*));
			if (! *recs) {
				FREEROW;
				return sMsg (ERR_NOMEM, "nGetResult(%s): out of memory",
					that->head.name);
			}
			memset (*recs, 0, numr * sizeof (Rec*));
			R = *recs;
			I = rowids ? *rowids : 0;
			j = pos = 0;
			F = rGet (that->rsp, OPENISIS_COM_REC, &pos);
			if (! F) {
				sMsg (LOG_WARN,
					"nGetResult(%s): no records in response",
					that->head.name);
				FREEPTR (*recs);
			}
			else {
				--pos;
				while (numr > j) {
					*R = luti_unwrap (that->rsp, &pos, OPENISIS_COM_REC,
						that->rdb ? that->rdb->dbid : -1);
					if (! *R) {
						FREEROW;
						luti_free ((void**)*recs, j);
						*recs = 0;
						return sMsg (ERR_NOMEM,
							"nGetResult(%s): out of memory", that->head.name);
					}
					if (I) {
						(*R)->rowid = *I;
						++I;
					}
					++R;
					++j;
				}
			}
		} /* recs */

	} /* numr */

	if (db) {
		*db = that->rdb;
	}
	if (tot) {
		*tot = numt;
	}

	return numr;
}