xref: /inferno-os/tools/odbc/odbc.c (revision a60fa48ce2f27a689f276bea9538b5db2b74ff86)
1 #ifdef WINDOWSNT
2 #include <windows.h>
3 #endif
4 #include <lib9.h>
5 #include <styx.h>
6 #include "styxserver.h"
7 /* #include <winsock.h> */
8 
9 #define DEFCOLSIZE			10000
10 
11 static int ODebug;
12 
13 char Eodbcalloc[] =	"no free ODBC handles";
14 char Enoconnect[] =	"no ODBC connection";
15 
16 static char	*netport = "6700";
17 static char	*inferno = "inferno";
18 
19 Styxserver *iserver;
20 
21 /* ----- */
22 #include <sql.h>
23 #include <sqlext.h>
24 
25 int nclients = 0;
26 
27 typedef struct Env Env;
28 struct Env
29 {
30 	SQLHENV h;			/* ODBC environment handle */
31 };
32 
33 typedef struct Conn Conn;
34 struct Conn
35 {
36 	SQLHDBC	h;			/* ODBC connection handle */
37 	int		connected;
38 };
39 
40 typedef struct Coltype Coltype;
41 struct Coltype
42 {
43 	char		name[255];
44 	ushort	type;
45 	SQLUINTEGER		size;
46 	ushort	digits;
47 	ushort	nulls;
48 };
49 
50 typedef struct Column Column;
51 struct Column
52 {
53 	char 	*data;
54 	SQLINTEGER	len;
55 };
56 
57 typedef struct Stmt Stmt;
58 struct Stmt
59 {
60 	SQLHSTMT h;			/* ODBC statement handle */
61 	ushort	ncols;		/* number of columns in result */
62 	ulong	nrows;		/* number of rows affected by update, insert, delete */
63 	Coltype	*cols;		/* column descriptions */
64 	Column	*rec;			/* data record */
65 	char		*headstr;		/* column headings if requested */
66 };
67 
68 /* ----- */
69 enum
70 {
71 	Qtopdir		= 0,	/* top level directory */
72 	Qnclients,
73 	Qprotodir,
74 	Qclonus,
75 	Qconvdir,
76 	Qdata,
77 	Qcmd,
78 	Qctl,
79 	Qstatus,
80 	Qformat,
81 	Qsources,
82 	Qerror,
83 
84 	MAXPROTO	= 1
85 };
86 #define TYPE(x) 	((x).path & 0xf)
87 #define CONV(x) 	(((x).path >> 4)&0xfff)
88 #define PROTO(x) 	(((x).path >> 16)&0xff)
89 #define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))
90 
91 typedef struct Proto	Proto;
92 typedef struct Conv	Conv;
93 typedef struct Output Output;
94 
95 struct Output {
96 	enum {Fixed, Float} style;			/* output style */
97 	uchar	fs;					/* float: field separator */
98 	uchar	rs;					/* float: record separator */
99 };
100 Output defoutput = {Float, '|', '\n'};
101 
102 struct Conv
103 {
104 	int	x;
105 	int	ref;
106 	int	perm;
107 	char	*owner;
108 	char*	state;
109 	Proto*	p;
110 
111 	/*-----*/
112 	Conn	c;
113 	Stmt		s;
114 	Output	out;
115 	int		headings;
116 	char		errmsg[400];	/* odbc error messages can be big */
117 };
118 
119 struct Proto
120 {
121 	int	x;
122 	char	*name;
123 	uint	nc;
124 	int	maxconv;
125 	Conv**	conv;
126 	Qid	qid;
127 };
128 
129 typedef struct Dirtab	Dirtab;
130 struct Dirtab
131 {
132 	char	name[255];
133 	Qid	qid;
134 	long	length;
135 	long	perm;
136 };
137 
138 static	int		np;
139 static	Proto	proto[MAXPROTO];
140 static	Conv*	protoclone(Proto*, char*);
141 
142 typedef int    Devgen(Fid*, char *, Dirtab*, int, int, Dir*);
143 
144 struct xClient {
145 	/* ---- */
146 	Env		e;
147 };
148 
149 #define H(c)	((Env*)(c->u))->h
150 
151 void
152 fatal(char *fmt, ...)
153 {
154 	char buf[1024], *out;
155 	va_list arg;
156 	out = vseprint(buf, buf+sizeof(buf), "Fatal error: ", 0);
157 	va_start(arg, fmt);
158 	out = vseprint(out, buf+sizeof(buf), fmt, arg);
159 	va_end(arg);
160 	write(2, buf, out-buf);
161 	exit(1);
162 }
163 
164 #define ASSERT(A,B) xassert((int)A,B)
165 
166 void
167 xassert(int true, char *reason)
168 {
169 	if(!true)
170 		fatal("assertion failed: %s\n", reason);
171 }
172 
173 void *
174 xmalloc(int bytes)
175 {
176 	char *m = malloc(bytes);
177 	if(m)
178 		memset(m, 0, bytes);
179 //	print("xmalloc: %lux (%d)\n", m, bytes);
180 	return m;
181 }
182 
183 void
184 xfree(void *p, char *from)
185 {
186 //	print("xfree: %lux [%s]\n", p, from);
187 	free(p);
188 }
189 
190 char *
191 odbcerror(Conv *cv, int lasterr)
192 {
193 	char sqlstate[6];
194 	long native;
195 	char *mp;
196 	short msglen;
197 
198 	if(cv == 0)
199 		return "";
200 	if(lasterr)
201 		return cv->errmsg;
202 	if(cv->c.connected)
203 		SQLGetDiagRec(SQL_HANDLE_STMT, cv->s.h, 1, sqlstate, &native, cv->errmsg, sizeof(cv->errmsg), &msglen);
204 	else
205 		SQLGetDiagRec(SQL_HANDLE_DBC, cv->c.h, 1, sqlstate, &native, cv->errmsg, sizeof(cv->errmsg), &msglen);
206 	cv->errmsg[msglen]=0;
207 	/* fprint(2, "c: sqlstate: %s, msg %s\n", sqlstate, cv->errmsg); */
208 	if((mp=strrchr(cv->errmsg, ']')) != 0)
209 		return mp+1;
210 	return cv->errmsg;
211 }
212 
213 char *odbcsources(Client *c)
214 {
215 	int ss, i;
216 	char server[SQL_MAX_DSN_LENGTH+1];
217 	char source[1024];
218 	char buff[1024+SQL_MAX_DSN_LENGTH+1];
219 	short serverlen, sourcelen;
220 	char *all = nil, *p;
221 
222 	for (i=0;; i++) {
223 		ss = SQLDataSources(H(c), (i==0 ? SQL_FETCH_FIRST : SQL_FETCH_NEXT),
224 				server, sizeof(server), &serverlen,
225 				source, sizeof(source), &sourcelen);
226 		if (ss != SQL_SUCCESS)
227 			break;
228 		sprint(buff, "%s:%s\n", server, source);
229 		if (i == 0)
230 			all = strdup(buff);
231 		else {
232 			p = all;
233 			all = malloc(strlen(all)+strlen(buff)+1);
234 			strcpy(all, p);
235 			strcat(all, buff);
236 			free(p);
237 		}
238 	}
239 	return all;
240 }
241 
242 
243 int
244 sqlerr(Conv *c, int sqlstatus, char *errp, char *func, char *sqlcall)
245 {
246 	char *e;
247 
248 	errp[0] = 0;
249 	e = "failed";
250 	if (sqlstatus == SQL_ERROR || sqlstatus == SQL_SUCCESS_WITH_INFO)
251 		strcat(errp, odbcerror(c, 0));
252 	if (sqlstatus == SQL_SUCCESS_WITH_INFO)
253 		e = "info";
254 	if (sqlstatus != SQL_SUCCESS)
255 		fprint(2, "%s: %s %s - %s\n", func, sqlcall, e, errp);
256 	if (sqlstatus != SQL_SUCCESS && sqlstatus != SQL_SUCCESS_WITH_INFO)
257 		return 1;
258 	return 0;
259 }
260 
261 char*
262 odbcnewclient(Client *c)
263 {
264 	int ss;
265 
266 	/* ---- */
267 	c->u = styxmalloc(sizeof(Env));
268 	ss = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H(c));
269 	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
270 		fprint(2, "newclient: SQLAllocHandle failed\n");
271 		return "SQLAllocHandle failed";
272 	}
273 	ss = SQLSetEnvAttr(H(c), SQL_ATTR_ODBC_VERSION, (char*)SQL_OV_ODBC2, 0);
274 	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
275 		fprint(2, "newclient: SQLSetEnvAttr failed\n");
276 		return "SQLSetEnvAttr failed";
277 	}
278 	nclients++;
279 	return nil;
280 }
281 
282 char*
283 odbcfreeclient(Client *c)
284 {
285 	int ss;
286 
287 	ss = SQLFreeHandle(SQL_HANDLE_ENV, H(c));
288 	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
289 		fprint(2, "freeclient: SQLFreeHandle failed\n");
290 	styxfree(c->u);
291 	nclients--;
292 	return nil;
293 }
294 
295 int
296 parsefields(char *lp, char **fields, int n, char *sep)
297 {
298 	int i;
299 
300 	for(i=0; lp && *lp && i<n; i++){
301 		while(*lp && strchr(sep, *lp) != 0)
302 			*lp++=0;
303 		if(*lp == 0)
304 			break;
305 		fields[i]=lp;
306 		while(*lp && strchr(sep, *lp) == 0)
307 			lp++;
308 	}
309 	return i;
310 }
311 
312 void
313 odbcdisconnect(Conv *c)
314 {
315 	int ss;
316 
317 	if(c->c.connected){
318 		ss = SQLFreeHandle(SQL_HANDLE_STMT, c->s.h);
319 		if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
320 			fprint(2, "odbcdisconnect: SQLFreeHandle failed\n");
321 		ss = SQLDisconnect(c->c.h);
322 		if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
323 			fprint(2, "odbcdisconnect: SQLDisconnect failed\n");
324 		c->c.connected = 0;
325 	}
326 }
327 
328 int
329 odbcconnect(Conv *c, char *server, char *user, char *auth, char *ename)
330 {
331 	int ss;
332 
333 	odbcdisconnect(c);
334 	ss = SQLConnect(c->c.h, server, SQL_NTS, user, strlen(user), auth, strlen(auth));
335 	if (sqlerr(c, ss, ename, "odbcconnect", "SQLConnect"))
336 		return -1;
337 	c->c.connected = 1;
338 	ss = SQLAllocHandle(SQL_HANDLE_STMT, c->c.h, &c->s.h);
339 	if (sqlerr(c, ss, ename, "odbcconnect", "SQLAllocHandle"))
340 		return -1;
341 	return 0;
342 }
343 
344 int
345 odbcnewconv(Client *c, Conv *cv)
346 {
347 	int ss;
348 
349 	ss = SQLAllocHandle(SQL_HANDLE_DBC, H(c), &cv->c.h);
350 	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO) {
351 		fprint(2, "odbcnewconv: SQLAllocHandle failed\n");
352 		return -1;
353 	}
354 	return 0;
355 }
356 
357 void
358 odbcfreeconv(Conv *c)
359 {
360 	int ss;
361 
362 	odbcdisconnect(c);
363 	ss = SQLFreeHandle(SQL_HANDLE_DBC, c->c.h);
364 	if (ss != SQL_SUCCESS && ss != SQL_SUCCESS_WITH_INFO)
365 		fprint(2, "odbcfreeconv: SQLFreeHandle failed\n");
366 }
367 
368 /* Free up all memory used in the last query */
369 void
370 freestat(Stmt *s)
371 {
372 	int i;
373 	if (s->ncols == 0)
374 		return;
375 	for(i=0; i<s->ncols; i++){
376 		Column *d = &s->rec[i];
377 		xfree(d->data, "freestat - data");
378 	}
379 	xfree(s->cols, "freestat - cols");
380 	s->cols = 0;
381 	xfree(s->rec, "freestat - rec");
382 	s->rec = 0;
383 	xfree(s->headstr, "freestat - headstr");
384 	s->headstr = 0;
385 	s->ncols = 0;
386 }
387 
388 
389 /* build an array describing the columns */
390 int
391 mkcols(Conv *c, char *ename)
392 {
393 	Stmt *s;
394 	int rv, i, err, hsize;
395 	ushort ignore;
396 	char *p;
397 
398 	s = &c->s;
399 	s->ncols = 0;
400 	rv = SQLNumResultCols(s->h, &s->ncols);
401 	if (sqlerr(c, rv, ename, "mkcols", "SQLNumResultCols"))
402 		return -1;
403 	s->cols = xmalloc(s->ncols*sizeof(Coltype));
404 	err = 0;
405 	hsize = 0;
406 	for(i=0; i<s->ncols; i++){
407 		Coltype *t = &s->cols[i];
408 		rv = SQLDescribeCol(s->h, i+1, t->name, sizeof(t->name), &ignore, &t->type, &t->size, &t->digits, &t->nulls);
409 		if (sqlerr(c, rv, ename, "mkcols", "SQLDescribeCol"))
410 			err++;
411 		if(t->size == 0 || t->size > MSGMAX) /* odbc should return 0 if size not available, not -1 */
412 			t->size = DEFCOLSIZE;
413 		hsize += strlen(t->name) + 1;
414 	}
415 	if (c->headings) {
416 		hsize += 2;
417 		s->headstr = xmalloc(hsize);
418 		p = s->headstr;
419 		for(i=0; i<s->ncols; i++) {
420 			Coltype *t = &s->cols[i];
421 			p += sprint(p, "%s%c", t->name, c->out.fs);
422 		}
423 		p[-1] = c->out.rs;
424 	} else
425 		s->headstr = 0;
426 	return (err ? -1 : 0);
427 }
428 
429 /* build a record to hold `fetched' results */
430 int
431 mkrec(Conv *c, char *ename)
432 {
433 	Stmt *s;
434 	int rv, i;
435 
436 	s = &c->s;
437 	s->rec = xmalloc(s->ncols*sizeof(Column));
438 	for(i=0; i<s->ncols; i++){
439 		Coltype *t = &s->cols[i];
440 		Column *d = &s->rec[i];
441 		if (ODebug)
442 			print("Column %d size=%ud type=%hd\n", i, t->size, t->type);
443 		d->data = xmalloc(t->size+1);		/* expects to zero terminate */
444 		rv = SQLBindCol(s->h, i+1, SQL_C_CHAR, d->data, t->size+1, &d->len);
445 		if (sqlerr(c, rv, ename, "mkrec", "SQLBindCol"))
446 			return -1;
447 	}
448 	return 0;
449 }
450 
451 int
452 rowcount(Conv *c, char *ename)
453 {
454 	Stmt *s;
455 	int rv;
456 
457 	s = &c->s;
458 	s->nrows = 0;
459 	rv = SQLRowCount(s->h, &s->nrows);
460 	if (sqlerr(c, rv, ename, "rowcount", "SQLRowCount"))
461 		return -1;
462 	return 0;
463 }
464 
465 int
466 odbcfetch(Conv *c, char *ename)
467 {
468 	Stmt *s = &c->s;
469 	int rv;
470 
471 	rv = SQLFetch(s->h);
472 	if(rv == SQL_NO_DATA) {
473 		freestat(s);
474 		return 0;
475 	}
476 	if (sqlerr(c, rv, ename, "odbcfetch", "SQLFetch")) {
477 		freestat(s);
478 		return -1;
479 	}
480 	return 1;
481 }
482 
483 int
484 odbcresults(Conv *c, char *ename)
485 {
486 	if(mkcols(c, ename))
487 		return -1;
488 	if(mkrec(c, ename))
489 		return -1;
490 	if(rowcount(c, ename))
491 		return -1;
492 	return 0;
493 }
494 
495 void
496 struncate(char *s, long *len)
497 {
498 	long i;
499 	for (i=0; i<*len; i++)
500 		if (s[i] == 0) {
501 			*len = i;
502 			return;
503 		}
504 }
505 
506 void
507 fix_delim(char *p, int len, char delim)
508 {
509 	int i;
510 	for (i=0; i<len; i++)
511 		if (p[i] == delim)
512 			p[i] = '\\';
513 }
514 
515 long
516 odbcdataread(Conv *c, void *a, long n, ulong offset, char *ename)
517 {
518 	Stmt *s = &c->s;
519 	int i, r;
520 	long left;
521 	char *p, *lastp;
522 
523 	if(c->c.connected == 0){
524 		strcpy(ename, Enoconnect);
525 		return -1;
526 	}
527 	if (s->cols == 0 || s->rec == 0)
528 		return -1;
529 	p = a;
530 	left = n;
531 	if (c->headings) {
532 		r = strlen(s->headstr);
533 		if (r && offset < r) {
534 			memcpy(p, s->headstr+offset, r-offset);
535 			p +=  r-offset;
536 			left -=  r-offset;
537 			return n-left;
538 		}
539 	}
540 	if((r=odbcfetch(c, ename)) < 0)
541 		return -1;
542 	if(r == 0)
543 		return 0;
544 	for(i=0; i<s->ncols; i++){
545 		Coltype *t = &s->cols[i];
546 		Column *d = &s->rec[i];
547 		if (ODebug)
548 			fprint(2, "Col %d Returned data len=%d\n", i, d->len);
549 		if(d->len <= 0)			/* SQL_NULL_DATA or strange error! */
550 			d->len = 0;
551 		if (d->len > t->size+1)
552 			d->len = t->size+1;
553 		if(left <= d->len+1)		/* whole fields */
554 			break;
555 		struncate(d->data, &d->len);	/* assume string data and stop on an embedded null */
556 		memcpy(p, d->data, d->len);
557 		lastp = p;
558 		left -= d->len;
559 		p += d->len;
560 		fix_delim(lastp, d->len, '\n');
561 		switch(c->out.style){
562 		case Float:
563 			fix_delim(lastp, d->len, c->out.fs);
564 			*p++ = (i==s->ncols-1)? c->out.rs: c->out.fs;
565 			left--;
566 			break;
567 		case Fixed:
568 			r = t->size - d->len;
569 			if(r < 0)
570 				r = 0;
571 			if(left < r)
572 				r = left;
573 			memset(p, ' ', r);
574 			left -= r;
575 			p += r;
576 			break;
577 		}
578 	}
579 	if (left < 0)
580 		fprint(2, "*** left<0 n=%d left=%d\n", n, left);
581 	return n-left;
582 }
583 
584 /*
585  * Returns a description of the format of a fixed width output
586  * record. `start' is the offset of the first character in the field.
587  * `end' is one greater than the offset of the last character of the field.
588  * `name' is the column name (which may contain spaces).
589  * `start' and `end' are terminated with a space, `name' with a newline.
590  * return 1 record containing one line for each field in the output:
591  *    start1 end1 name1\n
592  *    start2 end2 name2\n
593  *    ....
594  */
595 long
596 odbcfmtread(Conv *c, void *a, long n, ulong offset, char *ename)
597 {
598 	Stmt *s = &c->s;
599 	int i, len;
600 	long left, off;
601 	char *p;
602 	char buf[100];
603 
604 	if(offset > 0)
605 		return 0;
606 	p = a;
607 	left = n;
608 	off = 0;
609 	for(i=0; i<s->ncols; i++){
610 		Coltype *t = &s->cols[i];
611 
612 		len = sprint(buf, "%ld %ld %s\n", off, off+t->size, t->name);
613 		off += t->size;
614 		if(left < len)
615 			break;
616 		memcpy(p, buf, len);
617 		left -= len;
618 		p += len;
619 
620 	}
621 	return n-left;
622 }
623 
624 int
625 odbctables(Conv *c, char *ename)
626 {
627 	int rv;
628 
629 	if(c->c.connected == 0){
630 		strcpy(ename, Enoconnect);
631 		return -1;
632 	}
633 	rv = SQLCloseCursor(c->s.h);
634 	rv = SQLTables(c->s.h, 0, 0, 0, 0, 0, 0, 0, 0);
635 	if (sqlerr(c, rv, ename, "odbctables", "SQLTables"))
636 		return -1;
637 	if(odbcresults(c, ename))
638 		return -1;
639 	return 0;
640 }
641 
642 int
643 odbccolumns(Conv *c, char *table, char *ename)
644 {
645 	int rv;
646 
647 	if(c->c.connected == 0){
648 		strcpy(ename, Enoconnect);
649 		return -1;
650 	}
651 	rv = SQLCloseCursor(c->s.h);
652 	rv = SQLColumns(c->s.h, 0, 0, 0, 0, table, strlen(table), 0, 0);
653 	if (sqlerr(c, rv, ename, "odbccolumns", "SQLColumns"))
654 		return -1;
655 	if(odbcresults(c, ename))
656 		return -1;
657 	return 0;
658 }
659 
660 int
661 odbcexec(Conv *c, char *cmd, int cmdlen, char *ename)
662 {
663 	int rv;
664 
665 	if(c->c.connected == 0){
666 		strcpy(ename, Enoconnect);
667 		return -1;
668 	}
669 	SQLCloseCursor(c->s.h);
670 	rv = SQLExecDirect(c->s.h, cmd, cmdlen);
671 	if (sqlerr(c, rv, ename, "odbcexec", "SQLExecDirect"))
672 		return -1;
673 	if(odbcresults(c, ename))
674 		return -1;
675 	return 0;
676 }
677 
678 int
679 odbctrans(Conv *c, char *cmd, char *ename)
680 {
681 	int rv;
682 
683 	if(strcmp(cmd, "auto") == 0){
684 		rv = SQLSetConnectAttr(c->c.h, SQL_ATTR_AUTOCOMMIT, (char*)SQL_AUTOCOMMIT_ON, 0);
685 	} else if(strcmp(cmd, "begin") == 0){
686 		rv = SQLSetConnectAttr(c->c.h, SQL_ATTR_AUTOCOMMIT, (char*)SQL_AUTOCOMMIT_OFF, 0);
687 	} else if(strcmp(cmd, "commit") == 0){
688 		rv = SQLEndTran(SQL_HANDLE_DBC, c->c.h, SQL_COMMIT);
689 	} else if(strcmp(cmd, "rollback") == 0){
690 		rv = SQLEndTran(SQL_HANDLE_DBC, c->c.h, SQL_ROLLBACK);
691 	} else {
692 		strcpy(ename, Ebadarg);
693 		return -1;
694 	}
695 	if (sqlerr(c, rv, ename, "odbctrans", "SQLSetConnectAttr/SQLEndTran"))
696 		return -1;
697 	return 0;
698 }
699 
700 int
701 readstr(ulong off, char *buf, ulong n, char *str)
702 {
703 	int size;
704 
705 	size = strlen(str);
706 	if(off >= size)
707 		return 0;
708 	if(off+n > size)
709 		n = size-off;
710 	memmove(buf, str+off, n);
711 	return n;
712 }
713 
714 static void
715 newproto(char *name, int maxconv)
716 {
717 	int l;
718 	Proto *p;
719 
720 	if(np >= MAXPROTO) {
721 		print("no %s: increase MAXPROTO", name);
722 		return;
723 	}
724 
725 	p = &proto[np];
726 	p->name = strdup(name);
727 	p->qid.path = QID(np, 0, Qprotodir);
728 	p->qid.type = QTDIR;
729 	p->x = np++;
730 	p->maxconv = maxconv;
731 	l = sizeof(Conv*)*(p->maxconv+1);
732 	p->conv = xmalloc(l);
733 	if(p->conv == 0)
734 		fatal("no memory");
735 	memset(p->conv, 0, l);
736 }
737 
738 char*
739 openmode(int *o)
740 {
741 	if(*o >= (OTRUNC|OCEXEC|ORCLOSE|OEXEC)){
742 		return Ebadarg;
743 	}
744 	*o &= ~(OTRUNC|OCEXEC|ORCLOSE);
745 	if(*o > OEXEC){
746 		return Ebadarg;
747 	}
748 	if(*o == OEXEC)
749 		*o = OREAD;
750 	return nil;
751 }
752 
753 static Conv*
754 protoclone(Proto *p, char *user)
755 {
756 	Conv *c, **pp, **ep;
757 	uvlong nr;
758 	char buf[16];
759 
760 	c = 0;
761 	ep = &p->conv[p->maxconv];
762 	for(pp = p->conv; pp < ep; pp++) {
763 		c = *pp;
764 		if(c == 0) {
765 			c = xmalloc(sizeof(Conv));
766 			if(c == 0)
767 				return 0;
768 			c->ref = 1;
769 			c->p = p;
770 			c->x = pp - p->conv;
771 			p->nc++;
772 			*pp = c;
773 			break;
774 		}
775 		if(c->ref == 0) {
776 			c->ref++;
777 			break;
778 		}
779 	}
780 	if(pp >= ep)
781 		return 0;
782 
783 	c->owner = strdup(user);
784 	c->perm = 0660;
785 	c->state = "Open";
786 	c->out = defoutput;
787 	c->headings = 0;
788 	c->errmsg[0] = 0;
789 
790 	nr = QID(0, c->x, Qconvdir);
791 	sprint(buf, "%d", c->x);
792 	styxadddir(iserver, Qprotodir, nr, buf, 0555, c->owner);
793 	styxaddfile(iserver, nr, QID(0, c->x, Qcmd), "cmd", c->perm, c->owner);
794 	styxaddfile(iserver, nr, QID(0, c->x, Qctl), "ctl", c->perm, c->owner);
795 	styxaddfile(iserver, nr, QID(0, c->x, Qdata), "data", c->perm, c->owner);
796 	styxaddfile(iserver, nr, QID(0, c->x, Qerror), "error", c->perm, c->owner);
797 	styxaddfile(iserver, nr, QID(0, c->x, Qformat), "format", c->perm, c->owner);
798 	styxaddfile(iserver, nr, QID(0, c->x, Qsources), "sources", c->perm, c->owner);
799 	styxaddfile(iserver, nr, QID(0, c->x, Qstatus), "status", 0444, c->owner);
800 
801 	return c;
802 }
803 
804 char*
805 dbopen(Qid *qid, int omode)
806 {
807 	Proto *p;
808 	int perm;
809 	Conv *cv;
810 	char *user;
811 	Qid q;
812 	Client *c;
813 
814 	q = *qid;
815 	c = styxclient(iserver);
816 
817 	perm = 0;
818 	omode &= 3;
819 	switch(omode) {
820 	case OREAD:
821 		perm = 4;
822 		break;
823 	case OWRITE:
824 		perm = 2;
825 		break;
826 	case ORDWR:
827 		perm = 6;
828 		break;
829 	}
830 
831 	switch(TYPE(q)) {
832 	default:
833 		break;
834 	case Qtopdir:
835 	case Qprotodir:
836 	case Qconvdir:
837 	case Qstatus:
838 	case Qformat:
839 	case Qsources:
840 	case Qerror:
841 		if(omode != OREAD){
842 			return Eperm;
843 		}
844 		break;
845 	case Qclonus:
846 		p = &proto[PROTO(q)];
847 		cv = protoclone(p, c->uname);
848 		if(cv == 0){
849 			return Enodev;
850 		}
851 		qid->path = QID(p->x, cv->x, Qctl);
852 		qid->type = 0;
853 		qid->vers = 0;
854 		if(odbcnewconv(c, cv) != 0){
855 			return Eodbcalloc;
856 		}
857 		break;
858 	case Qdata:
859 	case Qcmd:
860 	case Qctl:
861 		p = &proto[PROTO(q)];
862 		cv = p->conv[CONV(q)];
863 		user = c->uname;
864 		if((perm & (cv->perm>>6)) != perm) {
865 			if(strcmp(user, cv->owner) != 0 ||
866 		 	  (perm & cv->perm) != perm) {
867 				return Eperm;
868 			}
869 		}
870 		cv->ref++;
871 		if(cv->ref == 1) {
872 			cv->state = "Open";
873 			cv->owner = strdup(user);
874 			cv->perm = 0660;
875 			if(odbcnewconv(c, cv) != 0){
876 				return Eodbcalloc;
877 			}
878 		}
879 		break;
880 	}
881 	return openmode(&omode);
882 }
883 
884 char*
885 dbclose(Qid qid, int mode)
886 {
887 	Conv *cc;
888 
889 	USED(mode);
890 	switch(TYPE(qid)) {
891 	case Qctl:
892 	case Qcmd:
893 	case Qdata:
894 		cc = proto[PROTO(qid)].conv[CONV(qid)];
895 		if(--cc->ref != 0)
896 			break;
897 		cc->owner = inferno;
898 		cc->perm = 0666;
899 		cc->state = "Closed";
900 		odbcfreeconv(cc);
901 		styxrmfile(iserver, QID(0, cc->x, Qconvdir));
902 		break;
903 	}
904 	return nil;
905 }
906 
907 static char ebuf[128];
908 
909 char*
910 dbread(Qid qid, char *ba, ulong *n, vlong offset)
911 {
912 	uchar *a = ba;
913 	Conv *c;
914 	Proto *x;
915 	char buf[128], *p, *s;
916 	long r;
917 	ulong m;
918 
919 	m = *n;
920 	ebuf[0] = 0;
921 	p = a;
922 	switch(TYPE(qid)) {
923 	default:
924 		return Eperm;
925 	case Qnclients:
926 		snprint(buf, sizeof(buf), "%d\n", nclients);
927 		*n = readstr(offset, p, m, buf);
928 		return nil;
929 	case Qprotodir:
930 	case Qtopdir:
931 	case Qconvdir:
932 		return "bad read of directory";
933 	case Qctl:
934 		sprint(buf, "%ld", CONV(qid));
935 		*n = readstr(offset, p, m, buf);
936 		return nil;
937 	case Qstatus:
938 		x = &proto[PROTO(qid)];
939 		c = x->conv[CONV(qid)];
940 		snprint(buf, sizeof(buf), "%s/%d %ld %s %s\n",
941 			c->p->name, c->x, c->ref, c->state, "");
942 		*n = readstr(offset, p, m, buf);
943 		return nil;
944 	case Qdata:
945 		c = proto[PROTO(qid)].conv[CONV(qid)];
946 		*n = odbcdataread(c, a, m, offset, ebuf);
947 		if(ebuf[0] != 0)
948 			return ebuf;
949 		return nil;
950 	case Qformat:
951 		c = proto[PROTO(qid)].conv[CONV(qid)];
952 		*n = odbcfmtread(c, a, m, offset, ebuf);
953 		if(ebuf[0] != 0)
954 			return ebuf;
955 		return nil;
956 	case Qerror:
957 		c = proto[PROTO(qid)].conv[CONV(qid)];
958 		*n = readstr(offset, p, m, odbcerror(c, 1));
959 		return nil;
960 	case Qsources:
961 		c = proto[PROTO(qid)].conv[CONV(qid)];
962 		s = odbcsources(styxclient(iserver));
963 		r = readstr(offset, p, m, s);
964 		free(s);
965 		*n = r;
966 		return nil;
967 	}
968 	return nil;
969 }
970 
971 char*
972 dbwrite(Qid qid, char *ba, ulong *n, vlong offset)
973 {
974 	uchar *a = ba;
975 	int nf;
976 	Conv *c;
977 	Proto *x;
978 	char *fields[10], buf[512], safebuf[512];
979 	ulong m;
980 
981 	m = *n;
982 	ebuf[0] = 0;
983 	switch(TYPE(qid)) {
984 	default:
985 		return Eperm;
986 	case Qctl:
987 		x = &proto[PROTO(qid)];
988 		c = x->conv[CONV(qid)];
989 		//
990 		if(m > sizeof(buf)-1)
991 			m = sizeof(buf)-1;
992 		memmove(buf, a, m);
993 		buf[m] = '\0';
994 		if (ODebug)
995 			fprint(2, "write Qctl: <%s>\n", buf);
996 		fields[0] = 0;
997 		nf = parsefields(buf, fields, sizeof(fields)/sizeof(*fields), " \n\t");
998 		if (nf == 0) {
999 			return Ebadarg;
1000 		}
1001 		if(strcmp(fields[0], "connect") == 0){ 	/* connect database [user!auth] */
1002 			char *afields[2];
1003 			char *user = "";
1004 			char *auth = "";
1005 			switch(nf){
1006 			default:
1007 				return Ebadarg;
1008 			case 2:
1009 				break;
1010 			case 3:
1011 				nf = parsefields(fields[2], afields, 2, "!");
1012 				switch(nf){
1013 				case 2:
1014 					user = afields[0];
1015 					auth = afields[1];
1016 					break;
1017 				case 1:
1018 					if(fields[2][0] == 0)
1019 						auth = afields[0];
1020 					else
1021 						user = afields[0];
1022 					break;
1023 				default:
1024 					break;
1025 				}
1026 				break;
1027 			}
1028 			if(odbcconnect(c, fields[1], user, auth, ebuf) < 0)
1029 				return ebuf;
1030 			c->state = "Connected";
1031 		} else if(strcmp(fields[0], "disconnect") == 0){
1032 			odbcdisconnect(c);
1033 			c->state = "Disconnected";
1034 		} else if(strcmp(fields[0], "fixed") == 0){
1035 			c->out = defoutput;
1036 			c->out.style = Fixed;
1037 		} else if(strcmp(fields[0], "float") == 0){
1038 			c->out = defoutput;
1039 			c->out.style = Float;
1040 			if(nf > 1)
1041 				c->out.fs = fields[1][0];
1042 			if(nf > 2)
1043 				c->out.rs = fields[2][0];
1044 		} else if(strcmp(fields[0], "headings") == 0){
1045 			c->headings = 1;
1046 		} else if(strcmp(fields[0], "noheadings") == 0){
1047 			c->headings = 0;
1048 		} else if(strcmp(fields[0], "trans") == 0){ /* begin, auto, commit, rollback */
1049 			if(nf < 2){
1050 				return Ebadarg;
1051 			}
1052 			if(odbctrans(c, fields[1], ebuf) < 0)
1053 				return ebuf;
1054 		} else {
1055 			return Ebadcmd;
1056 		}
1057 		*n = m;
1058 		return nil;
1059 	case Qcmd:
1060 		x = &proto[PROTO(qid)];
1061 		c = x->conv[CONV(qid)];
1062 		if(m > sizeof(buf)-1)
1063 			m = sizeof(buf)-1;
1064 		memmove(buf, a, m);
1065 		buf[m] = '\0';
1066 		if (ODebug)
1067 			fprint(2, "write Qcmd: <%s>\n", buf);
1068 		memmove(safebuf, a, m);
1069 		safebuf[m] = '\0';
1070 		fields[0] = 0;
1071 		nf = parsefields(buf, fields, 3, " \n\t");
1072 		if (nf == 0) {
1073 			return Ebadarg;
1074 		}
1075 		if(strcmp(fields[0], "tables") == 0){
1076 			if(odbctables(c, ebuf))
1077 				return ebuf;
1078 		}else if(strcmp(fields[0], "columns") == 0){
1079 			if(nf < 2){
1080 				return Ebadarg;
1081 			}
1082 			if(odbccolumns(c, &safebuf[strlen(fields[0])+1], ebuf))	/* allow for spaces in table name */
1083 				return ebuf;
1084 		} else
1085 			if (odbcexec(c, a, m, ebuf))
1086 				return ebuf;
1087 		*n = m;
1088 		return nil;
1089 	case Qdata:
1090 		return Eperm;
1091 	}
1092 	return nil;
1093 }
1094 
1095 void
1096 badusage()
1097 {
1098 	fprint(2, "Usage: odbc [-d] [-p port]\n");
1099 	exit(1);
1100 }
1101 
1102 Styxops ops = {
1103 	odbcnewclient,			/* newclient */
1104 	odbcfreeclient,			/* freeclient */
1105 
1106 	nil,			/* attach */
1107 	nil,			/* walk */
1108 	dbopen,		/* open */
1109 	nil,			/* create */
1110 	dbread,		/* read */
1111 	dbwrite,		/* write */
1112 	dbclose,		/* close */
1113 	nil,			/* remove */
1114 	nil,			/* stat */
1115 	nil,			/* wstat */
1116 };
1117 
1118 void
1119 main(int argc, char *argv[])
1120 {
1121 	Styxserver s;
1122 
1123 	ARGBEGIN {
1124 	default:
1125 		badusage();
1126 	case 'd':		/* Debug */
1127 		ODebug = 1;
1128 		styxdebug();
1129 		break;
1130 	case 'p':		/* Debug */
1131 		netport = EARGF(badusage());
1132 		break;
1133 	} ARGEND
1134 
1135 	iserver = &s;
1136 	styxinit(&s, &ops, netport, -1, 1);
1137 	styxaddfile(&s, Qroot, Qnclients, "nclients", 0444, inferno);
1138 	styxadddir(&s, Qroot, Qprotodir, "db", 0555, inferno);
1139 	styxaddfile(&s, Qprotodir, Qclonus, "new", 0660, inferno);
1140 	newproto("db", 100);
1141 	for (;;) {
1142 		styxwait(&s);
1143 		styxprocess(&s);
1144 	}
1145 	styxend(&s);
1146 }
1147