shithub: purgatorio

ref: 482e874528c6c86e661a3e4a1b9fda12f2346385
dir: /tools/odbc/odbc.c/

View raw version
#ifdef WINDOWSNT
#include <windows.h>
#endif
#include <lib9.h>
#include <styx.h>
#include "styxserver.h"
/* #include <winsock.h> */

#define DEFCOLSIZE			10000

static int ODebug;

char Eodbcalloc[] =	"no free ODBC handles";
char Enoconnect[] =	"no ODBC connection";

static char	*netport = "6700";
static char	*inferno = "inferno";

Styxserver *iserver;

/* ----- */
#include <sql.h>
#include <sqlext.h>

int nclients = 0;

typedef struct Env Env;
struct Env
{
	SQLHENV h;			/* ODBC environment handle */
};

typedef struct Conn Conn;
struct Conn
{
	SQLHDBC	h;			/* ODBC connection handle */
	int		connected;
};

typedef struct Coltype Coltype;
struct Coltype
{
	char		name[255];
	ushort	type;
	SQLUINTEGER		size;
	ushort	digits;
	ushort	nulls;
};

typedef struct Column Column;
struct Column
{
	char 	*data;
	SQLINTEGER	len;
};

typedef struct Stmt Stmt;
struct Stmt
{
	SQLHSTMT h;			/* ODBC statement handle */
	ushort	ncols;		/* number of columns in result */
	ulong	nrows;		/* number of rows affected by update, insert, delete */
	Coltype	*cols;		/* column descriptions */
	Column	*rec;			/* data record */
	char		*headstr;		/* column headings if requested */
};

/* ----- */
enum
{
	Qtopdir		= 0,	/* top level directory */
	Qnclients,
	Qprotodir,
	Qclonus,
	Qconvdir,
	Qdata,
	Qcmd,
	Qctl,
	Qstatus,
	Qformat,
	Qsources,
	Qerror,

	MAXPROTO	= 1
};
#define TYPE(x) 	((x).path & 0xf)
#define CONV(x) 	(((x).path >> 4)&0xfff)
#define PROTO(x) 	(((x).path >> 16)&0xff)
#define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))

typedef struct Proto	Proto;
typedef struct Conv	Conv;
typedef struct Output Output;

struct Output {
	enum {Fixed, Float} style;			/* output style */
	uchar	fs;					/* float: field separator */
	uchar	rs;					/* float: record separator */
};
Output defoutput = {Float, '|', '\n'};

struct Conv
{
	int	x;
	int	ref;
	int	perm;
	char	*owner;
	char*	state;
	Proto*	p;

	/*-----*/
	Conn	c;
	Stmt		s;
	Output	out;
	int		headings;
	char		errmsg[400];	/* odbc error messages can be big */
};

struct Proto
{
	int	x;
	char	*name;
	uint	nc;
	int	maxconv;
	Conv**	conv;
	Qid	qid;
};

typedef struct Dirtab	Dirtab;
struct Dirtab
{
	char	name[255];
	Qid	qid;
	long	length;
	long	perm;
};

static	int		np;
static	Proto	proto[MAXPROTO];
static	Conv*	protoclone(Proto*, char*);

typedef int    Devgen(Fid*, char *, Dirtab*, int, int, Dir*);

struct xClient {
	/* ---- */
	Env		e;
};

#define H(c)	((Env*)(c->u))->h

void
fatal(char *fmt, ...)
{
	char buf[1024], *out;
	va_list arg;
	out = vseprint(buf, buf+sizeof(buf), "Fatal error: ", 0);
	va_start(arg, fmt);
	out = vseprint(out, buf+sizeof(buf), fmt, arg);
	va_end(arg);
	write(2, buf, out-buf);
	exit(1);
}

#define ASSERT(A,B) xassert((int)A,B)

void
xassert(int true, char *reason)
{
	if(!true)
		fatal("assertion failed: %s\n", reason);
}

void *
xmalloc(int bytes)
{
	char *m = malloc(bytes);
	if(m)
		memset(m, 0, bytes);
//	print("xmalloc: %lux (%d)\n", m, bytes);
	return m;
}

void
xfree(void *p, char *from)
{
//	print("xfree: %lux [%s]\n", p, from);
	free(p);
}

char *
odbcerror(Conv *cv, int lasterr)
{
	char sqlstate[6];
	long native;
	char *mp;
	short msglen;

	if(cv == 0)
		return "";
	if(lasterr)
		return cv->errmsg;
	if(cv->c.connected)
		SQLGetDiagRec(SQL_HANDLE_STMT, cv->s.h, 1, sqlstate, &native, cv->errmsg, sizeof(cv->errmsg), &msglen);
	else
		SQLGetDiagRec(SQL_HANDLE_DBC, cv->c.h, 1, sqlstate, &native, cv->errmsg, sizeof(cv->errmsg), &msglen);
	cv->errmsg[msglen]=0;
	/* fprint(2, "c: sqlstate: %s, msg %s\n", sqlstate, cv->errmsg); */
	if((mp=strrchr(cv->errmsg, ']')) != 0)
		return mp+1;
	return cv->errmsg;
}

char*
odbcsources(Client *c)
{
	int ss, i;
	char server[SQL_MAX_DSN_LENGTH+1];
	char source[1024];
	char buff[1024+SQL_MAX_DSN_LENGTH+1];
	short serverlen, sourcelen;
	char *all = nil, *p;

	for (i=0;; i++) {
		ss = SQLDataSources(H(c), (i==0 ? SQL_FETCH_FIRST : SQL_FETCH_NEXT),
				server, sizeof(server), &serverlen,
				source, sizeof(source), &sourcelen);
		if (ss != SQL_SUCCESS)
			break;
		snprint(buff, sizeof(buff), "%s:%s\n", server, source);
		if (i == 0)
			all = strdup(buff);
		else {
			p = all;
			all = malloc(strlen(all)+strlen(buff)+1);
			strcpy(all, p);
			strcat(all, buff);
			free(p);
		}
	}
	return all;
}


int
sqlerr(Conv *c, int sqlstatus, char *errp, char *func, char *sqlcall)
{
	char *e;

	errp[0] = 0;
	e = "failed";
	if (sqlstatus == SQL_ERROR || sqlstatus == SQL_SUCCESS_WITH_INFO)
		strecpy(errp, errp+ERRMAX, odbcerror(c, 0));
	if (sqlstatus == SQL_SUCCESS_WITH_INFO)
		e = "info";
	if (sqlstatus != SQL_SUCCESS)
		fprint(2, "%s: %s %s - %s\n", func, sqlcall, e, errp);
	if (sqlstatus != SQL_SUCCESS && sqlstatus != SQL_SUCCESS_WITH_INFO)
		return 1;
	return 0;
}

char*
odbcnewclient(Client *c)
{
	int ss;

	/* ---- */
	c->u = styxmalloc(sizeof(Env));
	ss = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H(c));
	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
		fprint(2, "newclient: SQLAllocHandle failed\n");
		return "SQLAllocHandle failed";
	}
	ss = SQLSetEnvAttr(H(c), SQL_ATTR_ODBC_VERSION, (char*)SQL_OV_ODBC2, 0);
	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
		fprint(2, "newclient: SQLSetEnvAttr failed\n");
		return "SQLSetEnvAttr failed";
	}
	nclients++;
	return nil;
}

char*
odbcfreeclient(Client *c)
{
	int ss;

	ss = SQLFreeHandle(SQL_HANDLE_ENV, H(c));
	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
		fprint(2, "freeclient: SQLFreeHandle failed\n");
	styxfree(c->u);
	nclients--;
	return nil;
}

int
parsefields(char *lp, char **fields, int n, char *sep)
{
	int i;

	for(i=0; lp && *lp && i<n; i++){
		while(*lp && strchr(sep, *lp) != 0)
			*lp++=0;
		if(*lp == 0)
			break;
		fields[i]=lp;
		while(*lp && strchr(sep, *lp) == 0)
			lp++;
	}
	return i;
}

void
odbcdisconnect(Conv *c)
{
	int ss;

	if(c->c.connected){
		ss = SQLFreeHandle(SQL_HANDLE_STMT, c->s.h);
		if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
			fprint(2, "odbcdisconnect: SQLFreeHandle failed\n");
		ss = SQLDisconnect(c->c.h);
		if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
			fprint(2, "odbcdisconnect: SQLDisconnect failed\n");
		c->c.connected = 0;
	}
}

int
odbcconnect(Conv *c, char *server, char *user, char *auth, char *ename)
{
	int ss;

	odbcdisconnect(c);
	ss = SQLConnect(c->c.h, server, SQL_NTS, user, strlen(user), auth, strlen(auth));
	if (sqlerr(c, ss, ename, "odbcconnect", "SQLConnect"))
		return -1;
	c->c.connected = 1;
	ss = SQLAllocHandle(SQL_HANDLE_STMT, c->c.h, &c->s.h);
	if (sqlerr(c, ss, ename, "odbcconnect", "SQLAllocHandle"))
		return -1;
	return 0;
}

int
odbcnewconv(Client *c, Conv *cv)
{
	int ss;

	ss = SQLAllocHandle(SQL_HANDLE_DBC, H(c), &cv->c.h);
	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
		fprint(2, "odbcnewconv: SQLAllocHandle failed\n");
		return -1;
	}
	return 0;
}

void
odbcfreeconv(Conv *c)
{
	int ss;

	odbcdisconnect(c);
	ss = SQLFreeHandle(SQL_HANDLE_DBC, c->c.h);
	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
		fprint(2, "odbcfreeconv: SQLFreeHandle failed\n");
}

/* Free up all memory used in the last query */
void
freestat(Stmt *s)
{
	int i;
	if (s->ncols == 0)
		return;
	for(i=0; i<s->ncols; i++){
		Column *d = &s->rec[i];
		xfree(d->data, "freestat - data");
	}
	xfree(s->cols, "freestat - cols");
	s->cols = 0;
	xfree(s->rec, "freestat - rec");
	s->rec = 0;
	xfree(s->headstr, "freestat - headstr");
	s->headstr = 0;
	s->ncols = 0;
}


/* build an array describing the columns */
int
mkcols(Conv *c, char *ename)
{
	Stmt *s;
	int rv, i, err, hsize;
	ushort ignore;
	char *p;

	s = &c->s;
	s->ncols = 0;
	rv = SQLNumResultCols(s->h, &s->ncols);
	if (sqlerr(c, rv, ename, "mkcols", "SQLNumResultCols"))
		return -1;
	s->cols = xmalloc(s->ncols*sizeof(Coltype));
	err = 0;
	hsize = 0;
	for(i=0; i<s->ncols; i++){
		Coltype *t = &s->cols[i];
		rv = SQLDescribeCol(s->h, i+1, t->name, sizeof(t->name), &ignore, &t->type, &t->size, &t->digits, &t->nulls);
		if (sqlerr(c, rv, ename, "mkcols", "SQLDescribeCol"))
			err++;
		if(t->size == 0 || t->size > MSGMAX) /* odbc should return 0 if size not available, not -1 */
			t->size = DEFCOLSIZE;
		hsize += strlen(t->name) + 1;
	}
	if (c->headings) {
		hsize += 2;
		s->headstr = xmalloc(hsize);
		p = s->headstr;
		for(i=0; i<s->ncols; i++) {
			Coltype *t = &s->cols[i];
			p += sprint(p, "%s%c", t->name, c->out.fs);
		}
		p[-1] = c->out.rs;
	} else 
		s->headstr = 0;
	return (err ? -1 : 0);
}

/* build a record to hold `fetched' results */
int
mkrec(Conv *c, char *ename)
{
	Stmt *s;
	int rv, i;

	s = &c->s;
	s->rec = xmalloc(s->ncols*sizeof(Column));
	for(i=0; i<s->ncols; i++){
		Coltype *t = &s->cols[i];
		Column *d = &s->rec[i];
		if (ODebug)
			print("Column %d size=%ud type=%hd\n", i, t->size, t->type);
		d->data = xmalloc(t->size+1);		/* expects to zero terminate */
		rv = SQLBindCol(s->h, i+1, SQL_C_CHAR, d->data, t->size+1, &d->len);
		if (sqlerr(c, rv, ename, "mkrec", "SQLBindCol"))
			return -1;
	}
	return 0;
}

int
rowcount(Conv *c, char *ename)
{
	Stmt *s;
	int rv;

	s = &c->s;
	s->nrows = 0;
	rv = SQLRowCount(s->h, &s->nrows);
	if (sqlerr(c, rv, ename, "rowcount", "SQLRowCount"))
		return -1;
	return 0;
}

int
odbcfetch(Conv *c, char *ename)
{
	Stmt *s = &c->s;
	int rv;

	rv = SQLFetch(s->h);
	if(rv == SQL_NO_DATA) {
		freestat(s);
		return 0;
	}
	if (sqlerr(c, rv, ename, "odbcfetch", "SQLFetch")) {
		freestat(s);
		return -1;
	}
	return 1;
}

int
odbcresults(Conv *c, char *ename)
{
	if(mkcols(c, ename))
		return -1;
	if(mkrec(c, ename))
		return -1;
	if(rowcount(c, ename))
		return -1;
	return 0;
}

void
struncate(char *s, long *len)
{
	long i;
	for (i=0; i<*len; i++)
		if (s[i] == 0) {
			*len = i;
			return;
		}
}

void
fix_delim(char *p, int len, char delim)
{
	int i;
	for (i=0; i<len; i++)
		if (p[i] == delim)
			p[i] = '\\';
}

long
odbcdataread(Conv *c, void *a, long n, ulong offset, char *ename)
{
	Stmt *s = &c->s;
	int i, r;
	long left;
	char *p, *lastp;

	if(c->c.connected == 0){
		strcpy(ename, Enoconnect);
		return -1;
	}
	if (s->cols == 0 || s->rec == 0)
		return 0;
	p = a;
	left = n;
	if (c->headings) {
		r = strlen(s->headstr);
		if (r && offset < r) {
			memcpy(p, s->headstr+offset, r-offset);
			p +=  r-offset;
			left -=  r-offset;
			return n-left;
		}
	}
	if((r=odbcfetch(c, ename)) < 0)
		return -1;	
	if(r == 0)
		return 0;
	for(i=0; i<s->ncols; i++){
		Coltype *t = &s->cols[i];
		Column *d = &s->rec[i];
		if (ODebug)
			fprint(2, "Col %d Returned data len=%d\n", i, d->len);
		if(d->len <= 0)			/* SQL_NULL_DATA or strange error! */
			d->len = 0;
		if (d->len > t->size+1)
			d->len = t->size+1;
		if(left <= d->len+1)		/* whole fields */
			break;
		struncate(d->data, &d->len);	/* assume string data and stop on an embedded null */
		memcpy(p, d->data, d->len);
		lastp = p;
		left -= d->len;
		p += d->len;
		fix_delim(lastp, d->len, '\n');
		switch(c->out.style){
		case Float:
			fix_delim(lastp, d->len, c->out.fs);
			*p++ = (i==s->ncols-1)? c->out.rs: c->out.fs;
			left--;
			break;
		case Fixed:
			r = t->size - d->len;
			if(r < 0)
				r = 0;
			if(left < r)
				r = left;
			memset(p, ' ', r);
			left -= r;
			p += r;
			break;
		}
	}
	if (left < 0)
		fprint(2, "*** left<0 n=%d left=%d\n", n, left);
	return n-left;
}

/*
 * Returns a description of the format of a fixed width output
 * record. `start' is the offset of the first character in the field.
 * `end' is one greater than the offset of the last character of the field.
 * `name' is the column name (which may contain spaces).
 * `start' and `end' are terminated with a space, `name' with a newline.
 * return 1 record containing one line for each field in the output:
 *    start1 end1 name1\n
 *    start2 end2 name2\n
 *    ....
 */
long
odbcfmtread(Conv *c, void *a, long n, ulong offset, char *ename)
{
	Stmt *s = &c->s;
	int i, len;
	long left, off;
	char *p;
	char buf[100];

	if(offset > 0)
		return 0;
	p = a;
	left = n;
	off = 0;
	for(i=0; i<s->ncols; i++){
		Coltype *t = &s->cols[i];

		len = snprint(buf, sizeof(buf), "%ld %ld %s\n", off, off+t->size, t->name);
		off += t->size;
		if(left < len)
			break;
		memcpy(p, buf, len);
		left -= len;
		p += len;

	}
	return n-left;
}

int
odbctables(Conv *c, char *ename)
{
	int rv;

	if(c->c.connected == 0){
		strcpy(ename, Enoconnect);
		return -1;
	}
	rv = SQLCloseCursor(c->s.h);
	rv = SQLTables(c->s.h, 0, 0, 0, 0, 0, 0, 0, 0);
	if (sqlerr(c, rv, ename, "odbctables", "SQLTables"))
		return -1;
	if(odbcresults(c, ename))
		return -1;
	return 0;
}

int
odbccolumns(Conv *c, char *table, char *ename)
{
	int rv;

	if(c->c.connected == 0){
		strcpy(ename, Enoconnect);
		return -1;
	}
	rv = SQLCloseCursor(c->s.h);
	rv = SQLColumns(c->s.h, 0, 0, 0, 0, table, strlen(table), 0, 0);
	if (sqlerr(c, rv, ename, "odbccolumns", "SQLColumns"))
		return -1;
	if(odbcresults(c, ename))
		return -1;
	return 0;
}

int
odbcexec(Conv *c, char *cmd, int cmdlen, char *ename)
{
	int rv;

	if(c->c.connected == 0){
		strcpy(ename, Enoconnect);
		return -1;
	}
	SQLCloseCursor(c->s.h);
	rv = SQLExecDirect(c->s.h, cmd, cmdlen);
	if (sqlerr(c, rv, ename, "odbcexec", "SQLExecDirect"))
		return -1;
	if(odbcresults(c, ename))
		return -1;
	return 0;
}

int
odbctrans(Conv *c, char *cmd, char *ename)
{
	int rv;

	if(strcmp(cmd, "auto") == 0){
		rv = SQLSetConnectAttr(c->c.h, SQL_ATTR_AUTOCOMMIT, (char*)SQL_AUTOCOMMIT_ON, 0);
	} else if(strcmp(cmd, "begin") == 0){
		rv = SQLSetConnectAttr(c->c.h, SQL_ATTR_AUTOCOMMIT, (char*)SQL_AUTOCOMMIT_OFF, 0);
	} else if(strcmp(cmd, "commit") == 0){
		rv = SQLEndTran(SQL_HANDLE_DBC, c->c.h, SQL_COMMIT);
	} else if(strcmp(cmd, "rollback") == 0){
		rv = SQLEndTran(SQL_HANDLE_DBC, c->c.h, SQL_ROLLBACK);
	} else {
		strcpy(ename, Ebadarg);
		return -1;
	}
	if (sqlerr(c, rv, ename, "odbctrans", "SQLSetConnectAttr/SQLEndTran"))
		return -1;
	return 0;
}

int
readstr(ulong off, char *buf, ulong n, char *str)
{
	int size;

	size = strlen(str);
	if(off >= size)
		return 0;
	if(off+n > size)
		n = size-off;
	memmove(buf, str+off, n);
	return n;
}

static void
newproto(char *name, int maxconv)
{
	int l;
	Proto *p;

	if(np >= MAXPROTO) {
		print("no %s: increase MAXPROTO", name);
		return;
	}

	p = &proto[np];
	p->name = strdup(name);
	p->qid.path = QID(np, 0, Qprotodir);
	p->qid.type = QTDIR;
	p->x = np++;
	p->maxconv = maxconv;
	l = sizeof(Conv*)*(p->maxconv+1);
	p->conv = xmalloc(l);
	if(p->conv == 0)
		fatal("no memory");
	memset(p->conv, 0, l);
}

char*
openmode(int *o)
{
	if(*o >= (OTRUNC|OCEXEC|ORCLOSE|OEXEC)){
		return Ebadarg;
	}
	*o &= ~(OTRUNC|OCEXEC|ORCLOSE);
	if(*o > OEXEC){
		return Ebadarg;
	}
	if(*o == OEXEC)
		*o = OREAD;
	return nil;
}

static Conv*
protoclone(Proto *p, char *user)
{
	Conv *c, **pp, **ep;
	uvlong nr;
	char buf[16];

	c = 0;
	ep = &p->conv[p->maxconv];
	for(pp = p->conv; pp < ep; pp++) {
		c = *pp;
		if(c == 0) {
			c = xmalloc(sizeof(Conv));
			if(c == 0)
				return 0;
			c->ref = 1;
			c->p = p;
			c->x = pp - p->conv;
			p->nc++;
			*pp = c;
			break;
		}
		if(c->ref == 0) {
			c->ref++;
			break;
		}
	}
	if(pp >= ep)
		return 0;

	c->owner = strdup(user);
	c->perm = 0660;
	c->state = "Open";
	c->out = defoutput;
	c->headings = 0;
	c->errmsg[0] = 0;

	nr = QID(0, c->x, Qconvdir);
	snprint(buf, sizeof(buf), "%d", c->x);
	styxadddir(iserver, Qprotodir, nr, buf, 0555, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qcmd), "cmd", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qctl), "ctl", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qdata), "data", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qerror), "error", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qformat), "format", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qsources), "sources", c->perm, c->owner);
	styxaddfile(iserver, nr, QID(0, c->x, Qstatus), "status", 0444, c->owner);

	return c;
}

char*
dbopen(Qid *qid, int omode)
{
	Proto *p;
	int perm;
	Conv *cv;
	char *user;
	Qid q;
	Client *c;

	q = *qid;
	c = styxclient(iserver);

	perm = 0;
	omode &= 3;
	switch(omode) {
	case OREAD:
		perm = 4;
		break;
	case OWRITE:
		perm = 2;
		break;
	case ORDWR:
		perm = 6;
		break;
	}

	switch(TYPE(q)) {
	default:
		break;
	case Qtopdir:
	case Qprotodir:
	case Qconvdir:
	case Qstatus:
	case Qformat:
	case Qsources:
	case Qerror:
		if(omode != OREAD){
			return Eperm;
		}
		break;
	case Qclonus:
		p = &proto[PROTO(q)];
		cv = protoclone(p, c->uname);
		if(cv == 0){
			return Enodev;
		}
		qid->path = QID(p->x, cv->x, Qctl);
		qid->type = 0;
		qid->vers = 0;
		if(odbcnewconv(c, cv) != 0){
			return Eodbcalloc;
		}
		break;
	case Qdata:
	case Qcmd:
	case Qctl:
		p = &proto[PROTO(q)];
		cv = p->conv[CONV(q)];
		user = c->uname;
		if((perm & (cv->perm>>6)) != perm) {
			if(strcmp(user, cv->owner) != 0 ||
		 	  (perm & cv->perm) != perm) {
				return Eperm;
			}
		}
		cv->ref++;
		if(cv->ref == 1) {
			cv->state = "Open";
			cv->owner = strdup(user);
			cv->perm = 0660;
			if(odbcnewconv(c, cv) != 0){
				return Eodbcalloc;
			}
		}
		break;
	}
	return openmode(&omode);
}

char*
dbclose(Qid qid, int mode)
{
	Conv *cc;

	USED(mode);
	switch(TYPE(qid)) {
	case Qctl:
	case Qcmd:
	case Qdata:
		cc = proto[PROTO(qid)].conv[CONV(qid)];
		if(--cc->ref != 0)
			break;
		cc->owner = inferno;
		cc->perm = 0666;
		cc->state = "Closed";
		odbcfreeconv(cc);
		styxrmfile(iserver, QID(0, cc->x, Qconvdir));
		break;
	}
	return nil;
}

static char ebuf[ERRMAX];

char*
dbread(Qid qid, char *ba, ulong *n, vlong offset)
{
	uchar *a = ba;
	Conv *c;
	Proto *x;
	char buf[128], *p, *s;
	long r;
	ulong m;

	m = *n;
	ebuf[0] = 0;
	p = a;
	switch(TYPE(qid)) {
	default:
		return Eperm;
	case Qnclients:
		snprint(buf, sizeof(buf), "%d\n", nclients);
		*n = readstr(offset, p, m, buf);
		return nil;
	case Qprotodir:
	case Qtopdir:
	case Qconvdir:
		return "bad read of directory";
	case Qctl:
		snprint(buf, sizeof(buf), "%ld", CONV(qid));
		*n = readstr(offset, p, m, buf);
		return nil;
	case Qstatus:
		x = &proto[PROTO(qid)];
		c = x->conv[CONV(qid)];
		snprint(buf, sizeof(buf), "%s/%d %ld %s %s\n",
			c->p->name, c->x, c->ref, c->state, "");
		*n = readstr(offset, p, m, buf);
		return nil;
	case Qdata:
		c = proto[PROTO(qid)].conv[CONV(qid)];
		*n = odbcdataread(c, a, m, offset, ebuf);
		if(ebuf[0] != 0)
			return ebuf;
		return nil;
	case Qformat:
		c = proto[PROTO(qid)].conv[CONV(qid)];
		*n = odbcfmtread(c, a, m, offset, ebuf);
		if(ebuf[0] != 0)
			return ebuf;
		return nil;
	case Qerror:
		c = proto[PROTO(qid)].conv[CONV(qid)];
		*n = readstr(offset, p, m, odbcerror(c, 1));
		return nil;
	case Qsources:
		c = proto[PROTO(qid)].conv[CONV(qid)];
		s = odbcsources(styxclient(iserver));
		r = readstr(offset, p, m, s);
		free(s);
		*n = r;
		return nil;
	}
	return nil;
}

char*
dbwrite(Qid qid, char *ba, ulong *n, vlong offset)
{
	uchar *a = ba;
	int nf;
	Conv *c;
	Proto *x;
	char *fields[10], buf[512], safebuf[512];
	ulong m;

	m = *n;
	ebuf[0] = 0;
	switch(TYPE(qid)) {
	default:
		return Eperm;
	case Qctl:
		x = &proto[PROTO(qid)];
		c = x->conv[CONV(qid)];
		// 
		if(m > sizeof(buf)-1)
			m = sizeof(buf)-1;
		memmove(buf, a, m);
		buf[m] = '\0';
		if (ODebug)
			fprint(2, "write Qctl: <%s>\n", buf);
		fields[0] = 0;
		nf = parsefields(buf, fields, sizeof(fields)/sizeof(*fields), " \n\t");
		if (nf == 0) {
			return Ebadarg;
		}
		if(strcmp(fields[0], "connect") == 0){ 	/* connect database [user!auth] */
			char *afields[2];
			char *user = "";
			char *auth = "";
			switch(nf){
			default:
				return Ebadarg;
			case 2:
				break;
			case 3:
				nf = parsefields(fields[2], afields, 2, "!");
				switch(nf){
				case 2:
					user = afields[0];
					auth = afields[1];
					break;
				case 1:
					if(fields[2][0] == 0)
						auth = afields[0];
					else
						user = afields[0];
					break;
				default:
					break;
				}
				break;
			}
			if(odbcconnect(c, fields[1], user, auth, ebuf) < 0)
				return ebuf;
			c->state = "Connected";
		} else if(strcmp(fields[0], "disconnect") == 0){
			odbcdisconnect(c);
			c->state = "Disconnected";
		} else if(strcmp(fields[0], "fixed") == 0){
			c->out = defoutput;
			c->out.style = Fixed;
		} else if(strcmp(fields[0], "float") == 0){
			c->out = defoutput;
			c->out.style = Float;
			if(nf > 1)
				c->out.fs = fields[1][0];
			if(nf > 2)
				c->out.rs = fields[2][0];
		} else if(strcmp(fields[0], "headings") == 0){
			c->headings = 1;
		} else if(strcmp(fields[0], "noheadings") == 0){
			c->headings = 0;
		} else if(strcmp(fields[0], "trans") == 0){ /* begin, auto, commit, rollback */
			if(nf < 2){
				return Ebadarg;
			}
			if(odbctrans(c, fields[1], ebuf) < 0)
				return ebuf;
		} else {
			return Ebadcmd;
		}
		*n = m;
		return nil;
	case Qcmd:
		x = &proto[PROTO(qid)];
		c = x->conv[CONV(qid)];
		if(m > sizeof(buf)-1)
			m = sizeof(buf)-1;
		memmove(buf, a, m);
		buf[m] = '\0';
		if (ODebug)
			fprint(2, "write Qcmd: <%s>\n", buf);
		memmove(safebuf, a, m);
		safebuf[m] = '\0';
		fields[0] = 0;
		nf = parsefields(buf, fields, 3, " \n\t");
		if (nf == 0) {
			return Ebadarg;
		}
		if(strcmp(fields[0], "tables") == 0){
			if(odbctables(c, ebuf))
				return ebuf;
		}else if(strcmp(fields[0], "columns") == 0){
			if(nf < 2){
				return Ebadarg;
			}
			if(odbccolumns(c, &safebuf[strlen(fields[0])+1], ebuf))	/* allow for spaces in table name */
				return ebuf;
		} else
			if (odbcexec(c, a, m, ebuf))
				return ebuf;
		*n = m;
		return nil;
	case Qdata:
		return Eperm;
	}
	return nil;
}

void
badusage(void)
{
	fprint(2, "Usage: odbc [-d] [-p port]\n");
	exit(1);
}

Styxops ops = {
	odbcnewclient,			/* newclient */
	odbcfreeclient,			/* freeclient */

	nil,			/* attach */
	nil,			/* walk */
	dbopen,		/* open */
	nil,			/* create */
	dbread,		/* read */
	dbwrite,		/* write */
	dbclose,		/* close */
	nil,			/* remove */
	nil,			/* stat */
	nil,			/* wstat */
};

void
main(int argc, char *argv[])
{
	Styxserver s;

	ARGBEGIN {
	default:
		badusage();
	case 'd':		/* Debug */
		ODebug = 1;
		styxdebug();
		break;
	case 'p':		/* Debug */
		netport = EARGF(badusage());
		break;
	} ARGEND

	iserver = &s;
	styxinit(&s, &ops, netport, -1, 1);
	styxaddfile(&s, Qroot, Qnclients, "nclients", 0444, inferno);
	styxadddir(&s, Qroot, Qprotodir, "db", 0555, inferno);
	styxaddfile(&s, Qprotodir, Qclonus, "new", 0666, inferno);
	newproto("db", 100);
	for (;;) {
		styxwait(&s);
		styxprocess(&s);
	}
	styxend(&s);
}