Revision 237 (by dpavlin, 2004/03/08 17:43:12) initial import of openisis 0.9.0 vendor drop
/*
	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: lfdt.c,v 1.25 2003/06/15 15:57:43 mawag Exp $
	implementation of FDT calls and static FDTs.
*/

#include <limits.h>  /* CHAR_MAX,PATH_MAX */
#include <stdio.h>
#include <string.h>

#include "openisis.h"
#include "loi.h"
#include "lfdt.h"
#include "luti.h"

#define FDF_EXT       ".fdt"
#define FDF_NAMLEN    30
#define FDF_PATLEN    20  /* format of *.fdt */
#define FDF_TOOLEN    6

#if FDF_NAMLEN >= FD_NAMELEN
	fix me
#endif

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

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

#define _FD_DFLTDB \
	{ OPENISIS_SC_DFLTDB, 0, FTX, 0, 0, DBNLEN, "defaultdb", \
	"Name of default db", 0, 0, 0, 0 }

#define _FD_DPATH \
	{ OPENISIS_DPATH, 0, FTX, 0, 0, PATH_MAX, "dbpath", \
	"Database Path", 0, 0, 0, 0 }

#define _FD_DENC \
	{ OPENISIS_DENC, 0, FTX, 0, 0, 32, "encoding", \
	"Encoding", 0, 0, 0, 0 }

static Fd _fdsys[] = {
	/* 700 ... OpenIsis system parameters */
	{ OPENISIS_SPATH, 0, FTX, 0, 0, PATH_MAX, "syspath",
		"Global Database Path", 0, 0, 0, 0 },
	{ OPENISIS_SLOGF, 0, FTX, 0, 0, PATH_MAX, "logfile",
		"Logfile Name", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTE, 0, 0, 16, "v",
		"Verbosity of Logging", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 0, "off",
		"don't log anything", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 1, "fatal", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 3, "syserr", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 4, "error", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 5, "warn", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 6, "info", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 7, "verbose", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 8, "trace", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 9, "debug", "", 0, 0, 0, 0 },
	{ OPENISIS_SLOGV, 0, FTV, 0, 0, 10, "all", "", 0, 0, 0, 0 },
	_FD_DFLTDB,
	_FD_DPATH,
	_FD_DENC
};
static Fdt _fdtsys = {
	sizeof (_fdsys) / sizeof (_fdsys[0]),
	_fdsys,
	0
};

static Fd _fdsch[] = {
	{ OPENISIS_SC_NAME, 0, FTX, 0, 0, SCNLEN, "name",
		"Identification of remote scheme", 0, 0, 0, 0 },
	{ OPENISIS_SC_HOST, 0, FTX, 0, 0, 64, "host",
		"Hostname of remote scheme", 0, 0, 0, 0 },
	{ OPENISIS_SC_PORT, 0, FTN, 0, 0, 5, "port",
		"Port of remote scheme", 0, 0, 0, 0 },
	_FD_DFLTDB,
	_FD_DPATH,
	_FD_DENC
};
static Fdt _fdtsch = {
	sizeof (_fdsch) / sizeof (_fdsch[0]),
	_fdsch,
	0
};

static Fd _fddb[] = {
	{ OPENISIS_DNAME, 0, FTX, 0, 0, DBNLEN, "db",
		"Identification of database", 0, 0, 0, 0 },
	{ OPENISIS_DTYPE, 0, FTE, 0, 0, 256, "format",
		"Database Format", 0, 0, 0, 0 },
	{ OPENISIS_DTYPE, 0, FTV, 0, 0, 0, "autoformat",
		"Database Format", 0, 0, 0, 0 },
	{ OPENISIS_DTYPE, 0, FTV, 0, 0, 1, "naligned",
		"Database Format", 0, 0, 0, 0 },
	{ OPENISIS_DTYPE, 0, FTV, 0, 0, 2, "aligned",
		"Database Format", 0, 0, 0, 0 },
	{ OPENISIS_DRO  , 0, FTB, 0, 0, 1, "ro",
		"Readonly Flag", 0, 0, 0, 0 },
	_FD_DPATH,
	{ OPENISIS_DDUMP, 0, FTB, 0, 0, 1, "internaldump",
		"Internal Dump Flag", 0, 0, 0, 0 },
	_FD_DENC,
	{ OPENISIS_DFDT,  0, FTX, 0, 0, 256, "fdt",
		"Path to fdt", 0, 0, 0, 0 }
};
static Fdt _fdtdb = {
	sizeof (_fddb) / sizeof (_fddb[0]),
	_fddb,
	0
};

static Fd _fdfd[] = {
	{ OPENISIS_FDID, 0, FTN, 0, 0, 10, "tag",
		"Tag number of field", 0, 0, 0, 0 },
	{ OPENISIS_FDSUB, 0, FTX, 0, 0, 1, "subfield",
		"Subfield", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTE, 0, 0, 2, "type",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTX, "alphanum",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTA, "alpha",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTN, "numeric",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTP, "pattern",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTI, "iso",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTE, "enum",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTB, "boolean",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTT, "table",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTS, "structure",
		"Field type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTX, "subalphanum",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTA, "subalpha",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTN, "subnumeric",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTP, "subpattern",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTI, "subiso",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTE, "subenum",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTF | FTB, "subbool",
		"Subfield type", 0, 0, 0, 0 },
	{ OPENISIS_FDTYPE, 0, FTV, 0, 0, FTV, "enum value",
		"Enumeration value", 0, 0, 0, 0 },
	{ OPENISIS_FDREP, 0, FTB, 0, 0, 1, "repeatable",
		"Repeatable flag", 0, 0, 0, 0 },
	{ OPENISIS_FDNUMC, 0, FTN, 0, 0, 2, "numchilds",
		"Number of subfield childs", 0, 0, 0, 0 },
	{ OPENISIS_FDLEN, 0, FTN, 0, 0, 10, "length",
		"Field length or enum value", 0, 0, 0, 0 },
	{ OPENISIS_FDNAME, 0, FTX, 0, 0, 30, "name",
		"Field name", 0, 0, 0, 0 },
	{ OPENISIS_FDDESC, 0, FTX, 0, 0, 31, "description",
		"Description", 0, 0, 0, 0 },
	{ OPENISIS_FDPAT, 0, FTX, 0, 0, 128, "pattern",
		"Pattern", 0, 0, 0, 0 },
	{ OPENISIS_FDDFLT, 0, FTX, 0, 0, 1024, "default",
		"Default value", 0, 0, 0, 0 },
	{ OPENISIS_FDINFO, 0, FTS, 0, 0, 1, "info",
		"Embedded info record", 0, 0, 0, 0 },
	{ OPENISIS_FDCHLD, 0, FTX, 1, 0, 1, "children",
		"Subfield childs", 0, 0, 0, 0 }
};
static Fdt _fdtfd = {
	sizeof (_fdfd) / sizeof (_fdfd[0]),
	_fdfd,
	0
};

#define _FD_FDT \
	{ OPENISIS_FDT_LEN, 0, FTN, 0, 0, 3, "flen", \
		"Length of fdt", 0, 0, 0, 0 }, \
	{ OPENISIS_FDT_FD, 0, FTS, 1, 0, 1, "fd", \
		"Field description", 0, 0, 0, 0 }, \
	{ OPENISIS_FDT_REC, 0, FTS, 0, 0, 1, "frec", \
		"Embedded info record", 0, 0, 0, 0 }
	
static Fd _fdfdt[] = {
	_FD_FDT
};
static Fdt _fdtfdt = {
	sizeof (_fdfdt) / sizeof (_fdfdt[0]),
	_fdfdt,
	0
};

#define _FD_COM \
	{ OPENISIS_COM_SID, 0, FTN, 0, 0, 2, "sid", \
		"Client Session Id", 0, 0, 0, 0 }, \
	{ OPENISIS_COM_SER, 0, FTN, 0, 0, 5, "ser", \
		"Request Serial No.", 0, 0, 0, 0 }, \
	{ OPENISIS_COM_DBN, 0, FTX, 0, 0, DBNLEN, "db", \
		"DB Identification", 0, 0, 0, 0 }, \
	{ OPENISIS_COM_TMS, 0, FTN, 0, 0, 10, "tms", \
		"Server Db Timestamp", 0, 0, 0, 0 }, \
	{ OPENISIS_COM_ROW, 0, FTN, 0, 0, 10, "rowid", \
		"RowId", 0, 0, 0, 0 }, \
	_FD_FDT, \
	{ OPENISIS_COM_CFG, 0, FTS, 0, 0, 0, "config", \
		"Config", 0, 0, 0, 0 }, \
	{ OPENISIS_COM_REC, 0, FTS, 1, 0, 0, "rec", \
		"Data", 0, 0, 0, 0 }

static Fd _fdrqs[] = {
	_FD_COM,
	{ OPENISIS_RQS_TYPE, 0, FTE, 0, 0, 32, "type",
		"Request type", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_OPEN, "open",
		"open db", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_CLOS, "close",
		"close db", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_MNT, "mount",
		"mount db", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_LSDB, "ls",
		"list dbs", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_MROW, "maxrow",
		"get maxrowid", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_QRY,  "query",
		"exec query", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_READ, "read",
		"fetch row", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_INS,  "insert",
		"insert rec", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_UPD,  "update",
		"update rec", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_DEL,  "delete",
		"delete row", 0, 0, 0, 0 },
	{ OPENISIS_RQS_TYPE, 0, FTV, 0, 0, OPENISIS_RQST_EVAL,  "eval",
		"evaluate command", 0, 0, 0, 0 },
	{ OPENISIS_RQS_FLG, 0, FTN, 0, 0, 4, "flags",
		"Request flags", 0, 0, 0, 0 },
	{ OPENISIS_RQS_QMOD, 0, FTN, 0, 0, 5, "mode",
		"Query Mode", 0, 0, 0, 0 },
	{ OPENISIS_RQS_SKIP, 0, FTN, 0, 0, 5, "skip",
		"Query Skip", 0, 0, 0, 0 },
	{ OPENISIS_RQS_SIZE, 0, FTN, 0, 0, 5, "size",
		"Result Length", 0, 0, 0, 0 },
	{ OPENISIS_RQS_KEY, 0, FTX, 0, 0, OPENISIS_QRY_KEYLEN, "key",
		"Query Key", 0, 0, 0, 0 },
	{ OPENISIS_RQS_IDX, 0, FTS, 0, 0, 0, "idx",
		"Index to Update", 0, 0, 0, 0 }
};
static Fdt _fdtrqs = {
	sizeof (_fdrqs) / sizeof (_fdrqs[0]),
	_fdrqs,
	0
};

static Fd _fdrsp[] = {
	_FD_COM,
	{ OPENISIS_RSP_DBID, 0, FTN, 0, 0, 2, "dbid",
		"Id of local db", 0, 0, 0, 0 },
	{ OPENISIS_RSP_ERR, 0, FTN, 0, 0, 5, "error",
		"Error Code", 0, 0, 0, 0 },
	{ OPENISIS_RSP_MSG, 0, FTX, 0, 0, OPENISIS_ERRMSGLEN, "msg",
		"Error Message", 0, 0, 0, 0 },
	{ OPENISIS_RSP_NUMT, 0, FTN, 0, 0, 5, "total",
		"Total No. of Records", 0, 0, 0, 0 },
	{ OPENISIS_RSP_NUMR, 0, FTN, 0, 0, 5, "size",
		"Number of Records", 0, 0, 0, 0 },
	{ OPENISIS_RSP_CERR,  0, FTN, 0, 0, 5, "error2",
		"Client Side Error", 0, 0, 0, 0 }
};
static Fdt _fdtrsp = {
	sizeof (_fdrsp) / sizeof (_fdrsp[0]),
	_fdrsp,
	0
};

/* ************************************************************
	public data
*/

const Fdt *openIsisFdtSyspar = &_fdtsys;
const Fdt *openIsisFdtScheme = &_fdtsch;
const Fdt *openIsisFdtDbpar  = &_fdtdb;
const Fdt *openIsisFdtFd     = &_fdtfd;
const Fdt *openIsisFdtFdt    = &_fdtfdt;
const Fdt *openIsisFdtRqs    = &_fdtrqs;
const Fdt *openIsisFdtRsp    = &_fdtrsp;


/* ************************************************************
	private functions
*/

static char *fFileGets (FILE *fp) {
	static char buf[4096];
	char *res;
	int len;
	while (1) {
nxtl:
		if (0 == (res = fgets (buf, sizeof(buf) - 1, fp))) {
			break;
		}
		buf[sizeof(buf) - 1] = 0;
		if (!(len = strlen (res))) {
			continue;
		}
		--len;
		while (0 <= len) {
			if ('\n' != res[len] && '\r' != res[len]) {
				break;
			}
			res[len] = 0;
			if (0 > --len) {
				goto nxtl;
			}
		}
		break;
	}
	return res;
}

static void strrtrim (char *str) {
	char *p = str + strlen (str) - 1;
	while (p >= str && (' ' == *p || '\t' == *p)) {
		*p-- = 0;
	}
}

static void fDesc2Name (char *d, char *n) {
	while (1) {
		if (!(*n = *d)) {
			return;
		}
		if ('A' <= *n && 'Z' >= *n) {
			*n += 'a' - 'A';
			++n;
			++d;
			continue;
		}
		if ('_' == *n ||
			('a' <= *n && 'z' >= *n) ||
			('0' <= *n && '9' >= *n)) {
			++n;
			++d;
			continue;
		}
		while (1) {
			++d;
			if (!*d) {
				*n = 0;
				return;
			}
			if (('A' <= *d && 'Z' >= *d) ||
				('a' <= *d && 'z' >= *d) ||
				('0' <= *d && '9' >= *d)) {
				break;
			}
		}
		*n++ = '_';
	}
}

static int fLine2Fd (char *line, Fd **fd, int *num) {
	char  name[FD_NAMELEN];
	char  pat[1 + FDF_PATLEN];
	Fd    buff, bufs;
	char *P;
	char *L  =  line;
	int   tag, len, typ, rep, idx;
	if (FDF_NAMLEN + FDF_PATLEN >= strlen (L)) {
		return sMsg (ERR_INVAL, "fFileFd: illegal line <%s>", line);
	}
	strncpy (name, L, FDF_NAMLEN) [FDF_NAMLEN] = 0;
	strrtrim (name);
	if (! *name) {
		return sMsg (ERR_INVAL, "fFileFd: no descr in line <%s>", line);
	}
	L += FDF_NAMLEN;
	strncpy (pat, L, FDF_PATLEN) [FDF_PATLEN] = 0;
	strrtrim (pat);
	L += FDF_PATLEN;
	if (4 != sscanf (L, "%d %d %d %d", &tag, &len, &typ, &rep)) {
		return sMsg (ERR_INVAL, "fFileFd: no type in line <%s>", line);
	}
	switch (typ) {
	case 0: typ = FTX; break;
	case 1: typ = FTA; break;
	case 2: typ = FTN; break;
	case 3: typ = FTP; break;
	default: return sMsg (ERR_INVAL,
		"fFileFd: unrecognized type %d in line <%s>", typ, line);
	}
	memset (&buff, 0, sizeof (Fd));
	buff.id = tag;
	buff.type = typ;
	buff.rep = 0 != rep;
	buff.len = len;
	strcpy (buff.desc, name);
	fDesc2Name (name, buff.name);
	if (! *buff.name) {
		return sMsg (ERR_INVAL, "fFileFd: illegal name in line <%s>", line);
	}
	if (FTP == typ) {
		if (! *pat) {
			return sMsg (ERR_INVAL, "fFileFd: illegal pat in line <%s>", line);
		}
		buff.pat = mDup (pat, -1);
		if (! buff.pat) {
			return sMsg (ERR_NOMEM, "fFileFd: cannot allocate pat");
		}
	}
	idx = luti_ptrincr (fd, num, 1, sizeof(Fd), -1);
	if (0 > idx) {
		return sMsg (ERR_NOMEM,
			"fFileFd: cannot extend fd array %d", *num);
	}
	memcpy (*fd + idx, &buff, sizeof(Fd));
	if (FTP != typ) {
		for (P = pat; *P; ++P) {
			memcpy (&bufs, &buff, sizeof(Fd));
			bufs.subf = *P;
			bufs.type = FTX;
			idx = luti_ptrincr (fd, num, 1, sizeof(Fd), -1);
			if (0 > idx) {
				return sMsg (ERR_NOMEM,
					"fFileFd: cannot extend fd array %d", *num);
			}
			memcpy (*fd + idx, &bufs, sizeof(Fd));
		}
	}
	return 0;
}

static int fLine2Tool (char *line, Rec **rec) {
	char  tool[1 + FDF_TOOLEN];
	char *L   =  line;
	int   len, typ;
	switch (*L) {
	case 'w':
	case 'W':
		typ = OPENISIS_DFMT;
		break;
	case 'f':
	case 'F':
		typ = OPENISIS_DPFT;
		break;
	case 's':
	case 'S':
		typ = OPENISIS_DFST;
		break;
	default:
		return sMsg (ERR_INVAL, "fLine2Tool: illegal line <%s>", line);
	}
	L += 2;
	len = strlen (L);
	while (0 < len) {
		strncpy (tool, L, FDF_TOOLEN) [FDF_TOOLEN] = 0;
		strrtrim (tool);
		RADDS (*rec, typ, tool, !0);
		if (! *rec) {
			return sMsg (ERR_NOMEM, "fLine2Tool: cannot extend rec");
		}
		L += FDF_TOOLEN;
		len -= FDF_TOOLEN;
	}
	return 0;
}

static int fResolveChilds (Fd *fd, int len) {
	char  msg[256]  =  { 0 };
	Fd   *buf[CHAR_MAX];
	Fd  **C;
	Fd   *F, *G;
	int   numc;
	int   err  =  0;
	for (F = fd + len; --F >= fd;  ) {
		if (! F->subf) {
			numc = 0;
			C = buf;
			for (G = fd + len; --G >= fd;  ) {
				if (G->subf && (! G->id || G->id == F->id)) {
					if (CHAR_MAX == numc) {
						err = ERR_INVAL;
						sprintf (msg,
							"fResolveChilds: too many childs for %s",
							F->name);
						break;
					}
					*C++ = G;
					++numc;
				}
			}
			if (numc) {
				C = mAlloc (numc * sizeof(Fd));
				if (! C) {
					return sMsg (ERR_NOMEM, "fResolveChilds");
				}
				F->subs = (Fd**) memcpy (C, buf, numc * sizeof(Fd));
				F->slen = numc;
			}
		}
	}
	if (err) {
		return sMsg (err, msg);
	}
	return 0;
}

static void fFreeFd (Fd *fd) {
	if (fd) {
		if (fd->pat) {
			mFree (fd->pat);
		}
		if (fd->dflt) {
			mFree (fd->dflt);
		}
		if (fd->info) {
			mFree (fd->info);
		}
		if (fd->subs) {
			mFree (fd->subs);
		}
	}
}

static Fdt* fCleanupArr (Fdt *fdt, Fd *arr, int len) {
	Fd *F;
	for (F = arr + len; --F >= arr;  ) {
		fFreeFd (F);
	}
	mFree (arr);
	mFree (fdt);
	return 0;
}

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

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

Fd* fById ( const Fdt *fdt, int id, int subf )
{
	Fd *f, *e;
	if (! fdt) {
		return 0;
	}
	for (e = (f = fdt->fd) + fdt->len; --e >= f;  ) {
		if (id == e->id && subf == e->subf && ! (0xf0 & e->type)) {
			return e;
		}
	}
	return 0;
}

Fd* fByName ( const Fdt *fdt, const char *name )
{
	Fd *f, *e, *g;
	const char *p;
	int l, cnt;
	if (! fdt || ! name) {
		return 0;
	}
	f = fdt->fd;
	e = f + fdt->len;
	if ( '-' == *name ) /* ignore leading dash */
		name++;
	if (! *name) {
		return 0;
	}
	if ( '0' <= *name && *name <= '9' )
		return fById( fdt, a2i( name, -1 ), 0 );
	p = name;
	while ( 'a' <= *p ? *p <= 'z' : '9' >= *p ? *p >= '0' : '_' == *p )
		p++;
	l = p - name;
	if ( ! l || l > FD_NAMELEN - 1 )
		return 0;
	for ( cnt = 0, g = 0; f < e; f++ ) {
		if ( *name == *f->name
			&& !(0xf0 & f->type) /* is field */
			&& !memcmp( name, f->name, l ) ) {
			if (!f->name[l]) {
				return f;
			}
			g = f;
			++cnt;
		}
	}
	return 1 == cnt ? g : 0;
}

/**
	lookup enum:
	if name is numeric, the value is returned, if legal
	considered are value entries with given id or id 0
	- if there is an exact match with same id, this is used
	- if there is an exact match with id 0, this is used
	-	if name is a unique prefix on given id, this is used
	-	if name is no prefix on given id, but a unique prefix on 0, this is used
*/
int fEnum ( Fdt *fdt, int id, const char *name )
{
	Fd *f = fdt->fd, *e = f + fdt->len,
		*x0 = 0, *pi = 0, *p0 = 0;
	int ui = 1, u0 = 1; /* unique */
	int l = strlen( name );
	if ( ! l || l > FD_NAMELEN - 1 )
		return NOENUM;
	if ( ('0' <= *name && *name <= '9')
		|| ('-' == *name && '0' <= name[1] && name[1] <= '9')
	) {
		int v = a2i( name, l );
		for ( ; f < e; f++ ) {
			if ( FTV == f->type && v == f->len && (f->id == id || !f->id) )
				return v;
		}
		return NOENUM;
	}
	for ( ; f < e; f++ ) {
		if ( FTV != f->type
			|| *name != *f->name
			|| memcmp( name, f->name, l )
			|| (f->id && f->id != id)
		)
			continue;
		if ( !f->name[l] ) { /* exact match */
			if ( f->id == id )
				return f->len;
			x0 = f; /* f->id is 0 */
			continue;
		}
		/* prefix match */
		if ( f->id == id ) {
			if ( pi )
				ui = 0;
			else
				pi = f;
		} else
			if ( p0 )
				u0 = 0;
			else
				p0 = f;
	}
	return x0 ? x0->len
		: pi ? (ui ? pi->len : NOENUM)
		: p0 && u0 ? p0->len : NOENUM;
}

Fdt* fFree ( Fdt *fdt ) {
	Fd *F, *E;
	if (fdt) {
		if (fdt->fd) {
			for (E = (F = fdt->fd) + fdt->len; --E >= F;  ) {
				fFreeFd (E);
			}
			mFree (fdt->fd);
		}
		if (fdt->rec) {
			mFree (fdt->rec);
		}
		mFree (fdt);
	}
	return 0;
}

Fdt* fFromFile (char *path) {
	FILE   *fp;
	char   *line;
	Fdt    *res;
	Fd     *fd;
	Rec    *rec;
	int     len, err;

	/* ldb::setext */
	len = strlen (path) - 4;
	memcpy (path + len, FDF_EXT, 4);
	if ( 'A'<=path[len-1] && path[len-1]<= 'Z' ) {
		char *p = path + len;
		for ( ;*p; p++ ) /* use uppercase extensions */
			if ( 'a' <= *p && *p <= 'z' )
				*p -= 'a'-'A';
	}

	fp = fopen (path, "r");
	if (! fp) {
		sMsg (LOG_INFO | ERR_BADF, "no such fdt: %s", path);
		return 0;
	}
	res = mAlloc (sizeof(Fdt));
	if (! res) {
		fclose (fp);
		sMsg (ERR_NOMEM, "fFromFile");
		return 0;
	}
	sMsg (LOG_VERBOSE, "> reading fdt: %s", path);

	fd = 0;
	rec = 0;
	err = len = 0;
	while ((line = fFileGets (fp))) {
		if ('*' == *line) {
			continue;
		}
		if (':' == line[1]) {
			if (-ERR_NOMEM == (err = fLine2Tool (line, &rec))) {
				break;
			}
			continue;
		}
		if (-ERR_NOMEM == (err = fLine2Fd (line, &fd, &len))) {
			break;
		}
	}

	fclose (fp);
	res->fd  = fd;
	res->rec = rec;
	res->len = len;

	if (-ERR_NOMEM == err) {
		return fFree (res);
	}

	sMsg (LOG_VERBOSE, "< %d entries in fdt", len);
	return res;
}

#define ADDFDS(tag,val) \
	RADDS (rec, tag, val, !0); \
	if (! rec) { return 0; }

Rec* fFd2Rec (const Fd *fd, Rec *rec, int embchld) {
	char   buf[16];
	Fd   **E, **F;
	Rec   *child;
	if (! fd) {
		return rec;
	}
	i2a (buf, fd->id);
	ADDFDS (OPENISIS_FDID, buf);
	if ((*buf = fd->subf)) {
		buf[1] = 0;
		ADDFDS (OPENISIS_FDSUB, buf);
	}
	i2a (buf, fd->type);
	ADDFDS (OPENISIS_FDTYPE, buf);
	i2a (buf, fd->rep);
	ADDFDS (OPENISIS_FDREP, buf);
	i2a (buf, fd->len);
	ADDFDS (OPENISIS_FDLEN, buf);
	ADDFDS (OPENISIS_FDNAME, fd->name);
	ADDFDS (OPENISIS_FDDESC, fd->desc);
	if (fd->pat) {
		ADDFDS (OPENISIS_FDPAT, fd->pat);
	}
	if (fd->dflt) {
		ADDFDS (OPENISIS_FDDFLT, fd->dflt);
	}
	if (fd->info) {
		rec = luti_wrap (rec, fd->info, OPENISIS_FDINFO);
		if (! rec) {
			return 0;
		}
	}
	if (embchld && fd->slen) {
		for (E = (F = fd->subs) + fd->slen; rec && F < E; ++F) {
			child = fFd2Rec (*F, 0, 0);
			if (! child) {
				return rec;
			}
			rec = luti_wrap (rec, child, OPENISIS_FDCHLD);
		}
		if (rec) {
			rec = rAddI (rec, OPENISIS_FDNUMC, fd->slen, !0);
		}
	}
	return rec;
}

Rec* fFdt2Rec (const Fdt *fdt, Rec *rec, int embchld) {
	Fd   *E, *F;
	Rec  *R;
	int   len;
	if (! fdt) {
		return rec;
	}
	if (fdt->rec) {
		rec = luti_wrap (rec, fdt->rec, OPENISIS_FDT_REC);
		if (! rec) { return 0; }
	}
	for (E = (F = fdt->fd) + fdt->len, len = 0; F < E; ++F) {
		if (! F->subf || ! embchld) {
			R = fFd2Rec (F, 0, embchld);
			if (! R) {
				return rec;
			}
			rec = luti_wrap (rec, R, OPENISIS_FDT_FD);
			mFree (R);
			if (! rec) {
				return 0;
			}
			++len;
		}
	}
	rec = rAddI (rec, OPENISIS_FDT_LEN, len, !0);
	return rec;
}

Fd *fRec2Fd (Rec *rec, Fd *buf) {
	char     name[FD_NAMELEN];
	Field   *F, *E;
	Fd      *fd;
	int      got = 0;
	if (! rec) {
		return 0;
	}
	if (! (fd = buf)) {
		fd = mAlloc (sizeof(Fd));
		if (! fd) {
			return 0;
		}
	}
	*name = 0;
	for (E = (F = rec->field) + rec->len; F < E; ++F) {
		switch (F->tag) {
		case OPENISIS_FDID:
			got |= 0x01;
			fd->id = a2id (F->val, F->len, 0);
			break;
		case OPENISIS_FDSUB:
			if (! (1 == F->len || (2 == F->len && ! F->val[1]))) {
				sMsg (ERR_INVAL,
					"fRec2Fd: ignoring illegal subfield id (%s)", name);
			}
			else {
				fd->subf = F->val[0];
			}
			break;
		case OPENISIS_FDTYPE:
			got |= 0x02;
			fd->type = (char) a2id (F->val, F->len, 0);
			break;
		case OPENISIS_FDREP:
			if (! (1 == F->len || (2 == F->len && ! F->val[1]))) {
				sMsg (ERR_INVAL,
					"fRec2Fd: ignoring illegal repeatable flag (%s)", name);
			}
			else {
				fd->rep = F->val[0] && '0' != F->val[0];
			}
			break;
		case OPENISIS_FDLEN:
			fd->len = (short) a2id (F->val, F->len, 0);
			break;
		case OPENISIS_FDNAME:
			got |= 0x04;
			if (FD_NAMELEN <= F->len) {
				memcpy (name, F->val, FD_NAMELEN - 1);
				name[FD_NAMELEN - 1] = 0;
				sMsg (ERR_INVAL,
					"fRec2Fd: name too long (%d) - truncated to %s",
					F->len, name);
			}
			else {
				memcpy (name, F->val, F->len);
				name[F->len] = 0;
			}
			if (! *fd->desc) {
				strcpy (fd->desc, name);
			}
			fDesc2Name (name, fd->name);
			if (!*(fd->name)) {
				sMsg (ERR_INVAL,
					"fRec2Fd: illegal name (%s)", name);
				got &= ~0x04;
			}
			break;
		case OPENISIS_FDDESC:
			if (FD_NAMELEN <= F->len) {
				memcpy (fd->desc, F->val, FD_NAMELEN - 1);
				fd->desc[FD_NAMELEN - 1] = 0;
				sMsg (ERR_INVAL,
					"fRec2Fd: descr too long (%d) - truncated to %s (%s)",
					F->len, fd->desc, name);
			}
			else {
				memcpy (fd->desc, F->val, F->len);
				fd->desc[F->len] = 0;
			}
			break;
		case OPENISIS_FDPAT:
			if (fd->pat) {
				sMsg (ERR_INVAL,
					"fRec2Fd: ignoring multiple occurences of pattern (%s)",
					name);
			}
			else {
				fd->pat = (char*) mAlloc (1 + F->len);
				if (! fd->pat) {
					goto err;
				}
				memcpy (fd->pat, F->val, F->len);
				fd->pat[F->len] = 0;
			}
			break;
		case OPENISIS_FDDFLT:
			if (fd->dflt) {
				sMsg (ERR_INVAL,
					"fRec2Fd: ignoring multiple occurences of dflt (%s)",
					name);
			}
			else {
				fd->dflt = (char*) mAlloc (1 + F->len);
				if (! fd->dflt) {
					goto err;
				}
				memcpy (fd->dflt, F->val, F->len);
				fd->dflt[F->len] = 0;
			}
			break;
		case OPENISIS_FDINFO:
			if (fd->info) {
				sMsg (ERR_INVAL,
					"fRec2Fd: ignoring multiple occurences of info (%s)",
					name);
			}
			else {
				int pos = F - rec->field;
				fd->info = luti_unwrap (rec, &pos, OPENISIS_FDINFO, -1);
				if (! fd->info) {
					goto err;
				}
				F = rec->field + pos - 1;
			}
			break;
		default:
			sMsg (ERR_INVAL,
				"fRec2Fd: ignoring unexpected tag %d (%s)",
				F->tag, name);
		}
	}
	if (0x07 != got) {
		sMsg (ERR_TRASH,
			"fRec2Fd: incomplete field description [%x] (%s)", got, name);
err:
		if (fd->pat) { mFree (fd->pat); }
		if (fd->dflt) { mFree (fd->dflt); }
		if (fd->info) { mFree (fd->info); }
		if (fd != buf) { mFree (fd); }
		return 0;
	}
	return fd;
}

Fdt *fRec2Fdt (Rec *rec) {
	Rec     *R, *cfg;
	Fdt     *fdt;
	Fd       fdbuf;
	Fd      *F, *arr;
	int      err, num, len, pos;
	if (!rec) {
		return 0;
	}
	num = rInt (rec, OPENISIS_FDT_LEN, 0, 0);
	if (0 >= num) {
		return 0;
	}
	arr = (Fd*) mAlloc (num * sizeof (Fd));
	if (!arr) {
		return 0;
	}
	fdt = (Fdt*) mAlloc (sizeof(Fdt));
	if (! fdt) {
		mFree (arr);
		return 0;
	}
	for (F = arr, len = pos = 0; num; --num) {
		R = luti_unwrap (rec, &pos, OPENISIS_FDT_FD, -1);
		if (!R) {
			return fCleanupArr (fdt, arr, len);
		}
		memset (&fdbuf, 0, sizeof(Fd));
		if (fRec2Fd (R, &fdbuf)) {
			memcpy (F, &fdbuf, sizeof(Fd));
			++len;
			++F;
		}
		mFree (R);
	}
	if (len != num) {
		num = len * sizeof(Fd);
		F = (Fd*) mAlloc (num);
		if (! F) {
			return fCleanupArr (fdt, arr, len);
		}
		memcpy (F, arr, num);
		mFree (arr);
		arr = F;
	}
	cfg = luti_unwrap (rec, 0, OPENISIS_FDT_REC, -1);
	fdt->len = len;
	fdt->fd  = arr;
	fdt->rec = cfg;
	err = fResolveChilds (arr, len);
	if (-ERR_NOMEM == err) {
		return fFree (fdt);
	}
	return fdt;
}