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: ldsp.c,v 1.32 2003/06/15 15:57:43 mawag Exp $
	server side request processing.
*/

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

#include "openisis.h"
#include "loi.h"
#include "ldb.h"
#include "ldsp.h"
#include "lfdt.h"
/* #include "lsv.h" */
#include "luti.h"

#ifdef WIN32
#define vsnprintf _vsnprintf
#endif

#define ISID   0
#define ISER   1
#define ITMS   2
#define SSTS   3

#define RSPVAR \
	Db  *dbh = 0; \
	Rec *rsp = 0; \
	int sdbid;

#define RSPADDR \
	if (! rspa) { rspa = &rsp; }

#define RSPHEAD \
	*rspa = rAddI (*rspa, OPENISIS_COM_SID, sst[ISID], !0); \
	*rspa = rAddI (*rspa, OPENISIS_COM_SER, sst[ISER], !0);

#define RSPNOERR \
	*rspa = rAddI (*rspa, OPENISIS_RSP_ERR, 0, !0);

#define RSPDB \
	RADDS (*rspa, OPENISIS_COM_DBN, dbn, !0);

#define RSPFREE  mFree (rsp);

#define GETDB(rname)                                           \
	sdbid = ldspGetDb (&dbh, sst, dbn);                        \
	if (0 > sdbid) {                                           \
		int rt = _RqsError (sst, ERR_INVAL, rspa, cb, \
			cld, rname, sst[ITMS] ?                            \
			"db %s remounted" : "db %s not open", dbn);        \
		return rt;                                             \
	}

#define RSPFDT \
	if (dbh->fdt) { \
		*rspa = openIsisFFdt2Rec (dbh->fdt, *rspa, 0); \
	} \
	if (dbh->cfg) { \
		*rspa = luti_wrap (*rspa, dbh->cfg, OPENISIS_COM_CFG); \
	}

#define DELIVER \
	if (*rspa) { \
		int rt = 0; \
		if (cb) { \
			rt = (*cb) (rspa, cld); \
			mFree (*rspa); \
		} \
		return rt; \
	} \
	return sMsg (ERR_NOMEM, "ldsp: deliver: rsp == 0");

#define UTF8REC(rec,inv,rname) \
	if (openIsisEnc2Utf8 && dbh->cfg) { \
		char ebuf[128]; \
		char *enc = rString (dbh->cfg, OPENISIS_DENC, 0, ebuf, sizeof(ebuf)); \
		if (enc && *enc && strcmp (enc, "utf-8")) { \
			ldspUtf8Rec (&rec, enc, inv); \
		} \
	}

/* ************************************************************
*/

static int ldspGetDb (Db **db, int sst[], const char *dbn) {
	Db *dbh = nDbByName (stub0, dbn);
	if (! dbh) {
		sst[ITMS] = 0;
		return -1;
	}
	if (sst[ITMS]) {
		if (-1 == sst[ITMS] || /* dont want default of rInt call */
			(dbh->tms && dbh->tms != sst[ITMS])
		) {
			sst[ITMS] = dbh->tms;
			return -1;
		}
	}
	if (db) {
		*db = dbh;
	}
	return dbh->dbid;
}

static int ldspUtf8Rec (Rec **rec, const char *enc, int invert) {
	char    buf[4096];
	const char *dst;
	Rec    *res  =  0;
	Field  *F, *E;
	if (*rec && (*rec)->len) {
		E = (*rec)->field + (*rec)->len;
		for (F = (*rec)->field; E > F; ++F) {
			if (0 == F->len || (1 == F->len && ! *(F->val))) {
				RADDS (res, F->tag, "", !0);
			}
			else {
				dst = (*openIsisEnc2Utf8) (enc,
					F->val, F->len, buf, sizeof(buf), invert);
				if (! dst) {
					if (res) {
						mFree (res);
					}
					return sMsg (ERR_TRASH,
						"ldsp: cannot convert %s %s utf8: %.*s",
						enc, (invert ? "from" : "to"), F->len, F->val);
				}
				RADDS (res, F->tag, dst, !0);
				if (dst != buf && dst != F->val) {
					(*openIsisEnc2Utf8) (0, dst, 0, 0, 0, 0);
				}
			}
			if (! res) {
				return sMsg (ERR_NOMEM, "ldsp: convert %s %s utf8: res == 0",
					enc, (invert ? "from" : "to"));
			}
		}
		res->rowid = (*rec)->rowid;
		res->dbid = (*rec)->dbid;
		mFree (*rec);
		*rec = res;
	}
	return 0;
}

static int _RqsError (
	int sst[], int err, Rec **rspa, LdspDlvCb *cb,
	void *cld, const char *rname, const char *fmt, ...
) {
	Rec *rsp = 0;
	char msg[OPENISIS_ERRMSGLEN];
	va_list ap;
	RSPADDR
	RSPHEAD
	sMsg (LOG_VERBOSE, "ldsp: _RqsError %s %x from %d/%d",
		rname, err, sst[ISID], sst[ISER]);
	if (sst[ITMS] && -1 != sst[ITMS]) {
		*rspa = rAddI (*rspa, OPENISIS_COM_TMS, sst[ITMS], !0);
	}
	*rspa = rAddI (*rspa, OPENISIS_RSP_ERR, err, !0);
	if (0 != fmt && 0 != *fmt) {
		va_start (ap, fmt);
		vsnprintf (msg, OPENISIS_ERRMSGLEN - 1, fmt, ap);
		msg[OPENISIS_ERRMSGLEN - 1] = 0;
		va_end (ap);
		RADDS (*rspa, OPENISIS_RSP_MSG, msg, !0);
		sMsg (LOG_WARN, "ldsp: err on rqs %s: %d(%x) - %s",
			rname, err, err, msg);
	}
	else {
		sMsg (LOG_WARN, "ldsp: err on rqs %s: %d(%x)",
			rname, err, err);
	}
	DELIVER
}

static int _RqsLsDb (
	int sst[], Rec **rspa, LdspDlvCb *cb, void *cld
) {
	Rec     *rsp = 0;
	Schema  *sc;
	char    *n;
	int      j;
	sc = nSchema (stub0);
	if (! sc) {
		j = _RqsError (sst, ERR_BADF, rspa, cb, cld,
			"lsdb", "local schema not initialized");
		return j;
	}
	RSPADDR
	RSPHEAD
	RSPNOERR
	sMsg (LOG_VERBOSE, "ldsp: _RqsLsDb from %d/%d", sst[ISID], sst[ISER]);
	for (j = 0; sc->ndbs > j; ++j) {
		if (*(n = sc->dbs[j]->name)) {
			RADDS (*rspa, OPENISIS_COM_DBN, n, !0);
		}
	}
	DELIVER
}

static int _RqsEval (
	int sst[], Rec *cmd, Rec **rspa, LdspDlvCb *cb, void *cld
) {
	Rec  *res = 0;
	Rec  *rsp = 0;
	int   j;
	if (! openIsisEval) {
		j = _RqsError (sst, ERR_IDIOT, rspa, cb, cld,
			"eval", "server has no eval function");
		mFree (cmd);
		return j;
	}
	j = (*openIsisEval) (cmd, &res);
	mFree (cmd);
	RSPADDR
	RSPHEAD
	*rspa = rAddI (*rspa, OPENISIS_RSP_ERR, j, !0);
	if (res) {
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMT, 1, !0);
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMR, 1, !0);
		*rspa = rAddI (*rspa, OPENISIS_COM_ROW, 0, !0);
		*rspa = luti_wrap (*rspa, res, OPENISIS_COM_REC);
		mFree (res);
	}
	else {
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMT, 0, !0);
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMR, 0, !0);
	}
	DELIVER
}

static int _RqsOpen (
	int sst[], const char *dbn, Rec *dbpar, Fdt *fdt,
	Rec **rspa, LdspDlvCb *cb, void *cld
) {
#define STRERRNOLEN  128
	RSPVAR
	Schema *sc;
	char    msg[STRERRNOLEN];
	sst[ITMS] = 0;
	sc = nSchema (stub0);
	if (! sc) {
		int rt = _RqsError (sst, ERR_BADF, rspa, cb, cld,
			"open", "%s: local schema not initialized", dbn);
		mFree (dbpar);
		return rt;
	}
	sMsg (LOG_VERBOSE, "ldsp: _RqsOpen %s from %d/%d",
		dbn, sst[ISID], sst[ISER]);
	errno = 0;
	dbh = cDOpen (dbn, dbpar, sc->cfg, fdt);
	if (! dbh) {
		int rt;
		strncpy (msg, strerror (errno), STRERRNOLEN - 1)
			[STRERRNOLEN - 1] = 0;
		rt = _RqsError (sst, ERR_BADF, rspa, cb, cld,
			"open", "%s: %s", dbn, msg);
		mFree (dbpar);
		return rt;
	}
	sdbid = dbh->dbid;
	dbn = dbh->name;
	RSPADDR
	RSPHEAD
	RSPNOERR
	RSPDB
	*rspa = rAddI (*rspa, OPENISIS_RSP_DBID, sdbid, !0);
	*rspa = rAddI (*rspa, OPENISIS_COM_TMS, dbh->tms, !0);
	mFree (dbpar);
	DELIVER
}

static int _RqsClose (
	int sst[], const char *dbn, Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	int err;
	GETDB("close")
	sMsg (LOG_VERBOSE, "ldsp: _RqsClose %s from %d/%d",
		dbn, sst[ISID], sst[ISER]);
	err = cDClose (sdbid);
	RSPADDR
	RSPHEAD
	*rspa = rAddI (*rspa, OPENISIS_RSP_DBID, sdbid, !0);
	*rspa = rAddI (*rspa, OPENISIS_RSP_ERR, err, !0);
	RSPDB
	DELIVER
}

static int _RqsMount (
	int sst[], const char *dbn, Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	GETDB("mount")
	RSPADDR
	RSPHEAD
	sMsg (LOG_VERBOSE, "ldsp: _RqsMount %s from %d/%d",
		dbn, sst[ISID], sst[ISER]);
	RSPNOERR
	RSPDB
	RSPFDT
	DELIVER
}

static int _RqsMaxrow (
	int sst[], const char *dbn, int flags,
	Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	int maxrow;
	GETDB("maxrow")
	maxrow = dMaxId (sdbid);
	if (0 > maxrow) {
		int rt = _RqsError (sst, ERR_TRASH, rspa, cb, cld, "maxrow",
			"no maxrow for %s", dbn);
		RSPFREE
		return rt;
	}
	RSPADDR
	RSPHEAD
	sMsg (LOG_VERBOSE, "ldsp: _RqsMaxrow %s from %d/%d",
		dbn, sst[ISID], sst[ISER]);
	RSPNOERR
	RSPDB
	if (OPENISIS_RQSF_MNT & flags) {
		RSPFDT
	}
	*rspa = rAddI (*rspa, OPENISIS_COM_ROW, maxrow, !0);
	DELIVER
}

static int _RqsQuery (
	int sst[], const char *dbn, const char *key, int mode, int skip,
	int size, int flags, Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	Set         set;
	Rec        *rec;
	int        lrt;
	int         j;
	int         rdr = OPENISIS_RQSF_QRR & flags;
	GETDB("query")
	sMsg (LOG_VERBOSE, "ldsp: _RqsQuery %s from %d/%d",
		dbn, sst[ISID], sst[ISER]);
	set.len = 0; /* input */
	lrt = dQuery (&set, sdbid, key, mode, skip);
	if (0 > lrt) {
		int rt = _RqsError (sst, ERR_TRASH, rspa, cb, cld, "query",
			"%d,%s = %d", sdbid, key, lrt);
		return rt;
	}
	RSPADDR
	RSPHEAD
	RSPNOERR
	RSPDB
	if (OPENISIS_RQSF_MNT & flags) {
		RSPFDT
	}
	*rspa = rAddI (*rspa, OPENISIS_RSP_NUMT, lrt, !0);
	if (0 < size && lrt > size) {
		lrt = size;
	}
	*rspa = rAddI (*rspa, OPENISIS_RSP_NUMR, lrt, !0);
	for (j = 0; lrt > j; ++j) {
		*rspa = rAddI (*rspa, OPENISIS_COM_ROW, set.id[j], !0);
	}
	if (rdr) {
		for (j = 0; *rspa && lrt > j; ++j) {
			rec = dRead (sdbid, set.id[j]);
			if (rec) {
				UTF8REC (rec, 0, "query")
			}
			else {
				sMsg (ERR_TRASH | LOG_WARN,
					"db %s: non-existent mfn %d for key %s",
					dbn, set.id[j], key);
				rec = rDup (0, 0, 0);
			}
			*rspa = luti_wrap (*rspa, rec, OPENISIS_COM_REC);
			mFree (rec);
		}
	}
	DELIVER
}

static int _RqsRead (
	int sst[], const char *dbn, int row, int flags,
	Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	Rec *rec;
	GETDB("read")
	RSPADDR
	RSPHEAD
	sMsg (LOG_VERBOSE, "ldsp: _RqsRead %s %d from %d/%d",
		dbn, row, sst[ISID], sst[ISER]);
	RSPNOERR
	RSPDB
	if (OPENISIS_RQSF_MNT & flags) {
		RSPFDT
	}
	rec = dRead (sdbid, row);
	if (rec) {
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMT, 1, !0);
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMR, 1, !0);
		*rspa = rAddI (*rspa, OPENISIS_COM_ROW, row, !0);
		UTF8REC (rec, 0, "read")
		*rspa = luti_wrap (*rspa, rec, OPENISIS_COM_REC);
		mFree (rec);
	}
	else {
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMT, 0, !0);
		*rspa = rAddI (*rspa, OPENISIS_RSP_NUMR, 0, !0);
	}
	DELIVER
}

static int _RqsUpdate (
	int sst[], const char *dbn, int row, Rec *rec, Rec *idx, int flags,
	Rec **rspa, LdspDlvCb *cb, void *cld
) {
	RSPVAR
	int rtw;
	GETDB("update")
	sMsg (LOG_VERBOSE, "ldsp: _RqsUpdate %s %d from %d/%d",
		dbn, row, sst[ISID], sst[ISER]);
	if (rec) {
		rec->rowid = row;
		rec->dbid = sdbid;
		UTF8REC (rec, !0, "update")
	}
	rtw = dWritex (sdbid, rec, idx);
	if (rtw) {
		rtw = _RqsError (sst, ERR_TRASH, rspa, cb, cld,
			"update", "write error %d", rtw);
		mFree (rec);
		mFree (idx);
		return rtw;
	}
	RSPADDR
	RSPHEAD
	RSPNOERR
	RSPDB
	if (OPENISIS_RQSF_MNT & flags) {
		RSPFDT
	}
	if (rec) {
		row = rec->rowid;
		mFree (rec);
	}
	mFree (idx);
	*rspa = rAddI (*rspa, OPENISIS_COM_ROW, row, !0);
	DELIVER
}

static int _RqsDelete (
	int sst[], const char *dbn, int row, int flags,
	Rec **rspa, LdspDlvCb *cb, void *cld
) {
	(void)flags;
	sMsg (LOG_VERBOSE, "ldsp: _RqsDelete %s %d from %d/%d",
		dbn, row, sst[ISID], sst[ISER]);
	return _RqsError (sst, ERR_TRASH, rspa, cb, cld, "delete",
		"request not implemented yet");
}

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

int ldspProcess (Rec *rqs, Rec **rspa, LdspDlvCb *cb, void *cld) {
	char   buf[PATH_MAX];
	char  *dbn  =  0;
	int    sst[SSTS];
	int    rtyp;
	int    flags  =  0;

	if (0 == rqs) {
		return sMsg (ERR_IDIOT, "ldspProcess: no request");
	}
	if (0 == rspa && 0 == cb) {
		return sMsg (ERR_IDIOT, "ldspProcess: no callback");
	}

	rtyp      = rInt (rqs, OPENISIS_RQS_TYPE, -1, 0);
	sst[ISID] = rInt (rqs, OPENISIS_COM_SID, -1, 0);
	sst[ISER] = rInt (rqs, OPENISIS_COM_SER, -1, 0);
	sst[ITMS] = rInt (rqs, OPENISIS_COM_TMS, -1, 0);

	if (OPENISIS_RQST_LSDB != rtyp &&
		OPENISIS_RQST_EVAL != rtyp
	) {
		dbn = rString (rqs, OPENISIS_COM_DBN, 0, buf, sizeof(buf));
		if (!dbn) {
			i2a (buf, rtyp);
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld, buf,
				"no db name given");
		}
		flags = rInt (rqs, OPENISIS_RQS_FLG, 0, 0);
	}

	switch (rtyp) {

	case OPENISIS_RQST_LSDB:
		return _RqsLsDb (sst, rspa, cb, cld);

	case OPENISIS_RQST_OPEN: {
		Rec *dbpar = luti_unwrap (rqs, 0, OPENISIS_COM_CFG, -1);
		Fdt *fdt   = fRec2Fdt (rqs);
		return _RqsOpen (sst, dbn, dbpar, fdt, rspa, cb, cld);
	}

	case OPENISIS_RQST_CLOS:
		return _RqsClose (sst, dbn, rspa, cb, cld);

	case OPENISIS_RQST_MNT:
		return _RqsMount (sst, dbn, rspa, cb, cld);

	case OPENISIS_RQST_MROW:
		return _RqsMaxrow (sst, dbn, flags, rspa, cb, cld);

	case OPENISIS_RQST_QRY: {
		char  kybuf[OPENISIS_QRY_KEYLEN];
		char *key;
		int  mode, skip, size;
		key  = rString (rqs, OPENISIS_RQS_KEY, 0, kybuf, OPENISIS_QRY_KEYLEN);
		mode = rInt (rqs, OPENISIS_RQS_QMOD, -1, 0);
		skip = rInt (rqs, OPENISIS_RQS_SKIP, 0, 0);
		size = rInt (rqs, OPENISIS_RQS_SIZE, -1, 0);
		if (0 > mode || 0 == key) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld, "query",
				"missing mode or key");
		}
		return _RqsQuery (sst, dbn, key,
			mode, skip, size, flags, rspa, cb, cld);
	}

	case OPENISIS_RQST_READ: {
		int rowid;
		rowid = rInt (rqs, OPENISIS_COM_ROW, -1, 0);
		if (0 > rowid) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld, "read",
				"missing rowid");
		}
		return _RqsRead (sst, dbn, rowid, flags, rspa, cb, cld);
	}

	case OPENISIS_RQST_INS:
	case OPENISIS_RQST_UPD: {
		Rec  *rec, *idx;
		int   rowid = 0;
		int   ins = rtyp == OPENISIS_RQST_INS;
		rec = luti_unwrap (rqs, 0, OPENISIS_COM_REC, -1);
		idx = luti_unwrap (rqs, 0, OPENISIS_RQS_IDX, -1);
		if (!(rec || idx)) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld,
				"update", "missing record or index");
		}
		rowid = rInt (rqs, OPENISIS_COM_ROW, 0, 0);
		if (! ins && 0 >= rowid) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld,
				"update", "missing rowid");
		}
		return _RqsUpdate (sst, dbn, rowid, rec, idx, flags, rspa, cb, cld);
	}

	case OPENISIS_RQST_DEL: {
		int rowid;
		rowid = rInt (rqs, OPENISIS_COM_ROW, -1, 0);
		if (0 > rowid) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld, "delete",
				"missing rowid");
		}
		return _RqsDelete (sst, dbn, rowid, flags, rspa, cb, cld);
	}

	case OPENISIS_RQST_EVAL: {
		Rec  *rec;
		rec = luti_unwrap (rqs, 0, OPENISIS_COM_REC, -1);
		if (! rec) {
			return _RqsError (sst, ERR_INVAL, rspa, cb, cld,
				"eval", "missing command record");
		}
		return _RqsEval (sst, rec, rspa, cb, cld);
	}

	} /* switch */

	i2a (buf, rtyp);
	return _RqsError (sst, ERR_INVAL, rspa, cb, cld, buf,
		"unrecognized request type");
} /* ldspProcess */