diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 20:52:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 20:52:35 +0000 |
| commit | 46439007cf417cbd9ac8049bb4122c890097a0fa (patch) | |
| tree | 6fdb25e5f3a2b6d5657eb23b35774b631d4d97e4 /tools/odbc | |
| parent | 37da2899f40661e3e9631e497da8dc59b971cbd0 (diff) | |
20060303-partial
Diffstat (limited to 'tools/odbc')
| -rwxr-xr-x | tools/odbc/mkfile | 19 | ||||
| -rw-r--r-- | tools/odbc/mkfile-Linux | 1 | ||||
| -rw-r--r-- | tools/odbc/mkfile-MacOSX | 1 | ||||
| -rw-r--r-- | tools/odbc/mkfile-Nt | 2 | ||||
| -rw-r--r-- | tools/odbc/mkfile-Plan9 | 0 | ||||
| -rw-r--r-- | tools/odbc/mkfile-Solaris | 1 | ||||
| -rwxr-xr-x | tools/odbc/odbc.c | 1146 |
7 files changed, 1170 insertions, 0 deletions
diff --git a/tools/odbc/mkfile b/tools/odbc/mkfile new file mode 100755 index 00000000..917c5f94 --- /dev/null +++ b/tools/odbc/mkfile @@ -0,0 +1,19 @@ +<../../mkconfig + +TARG=odbc + +OFILES=\ + odbc.$O\ + +HFILES=\ + ../libstyx/styxserver.h\ + +LIBS=styx 9 + +BIN=$ROOT/$OBJDIR/bin + +<mkfile-$SYSTARG + +<$ROOT/mkfiles/mkone-$SHELLTYPE + +CFLAGS= $CFLAGS -I../libstyx diff --git a/tools/odbc/mkfile-Linux b/tools/odbc/mkfile-Linux new file mode 100644 index 00000000..f5564dd8 --- /dev/null +++ b/tools/odbc/mkfile-Linux @@ -0,0 +1 @@ +SYSLIBS=-lodbc diff --git a/tools/odbc/mkfile-MacOSX b/tools/odbc/mkfile-MacOSX new file mode 100644 index 00000000..468303c5 --- /dev/null +++ b/tools/odbc/mkfile-MacOSX @@ -0,0 +1 @@ +SYSLIBS= -liodbc diff --git a/tools/odbc/mkfile-Nt b/tools/odbc/mkfile-Nt new file mode 100644 index 00000000..228d292d --- /dev/null +++ b/tools/odbc/mkfile-Nt @@ -0,0 +1,2 @@ +SYSLIBS= odbc32.lib wsock32.lib $SYSLIBS +CFLAGS= $CFLAGS -DWINDOWSNT diff --git a/tools/odbc/mkfile-Plan9 b/tools/odbc/mkfile-Plan9 new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tools/odbc/mkfile-Plan9 diff --git a/tools/odbc/mkfile-Solaris b/tools/odbc/mkfile-Solaris new file mode 100644 index 00000000..818482a8 --- /dev/null +++ b/tools/odbc/mkfile-Solaris @@ -0,0 +1 @@ +SYSLIBS=$EMULIBS diff --git a/tools/odbc/odbc.c b/tools/odbc/odbc.c new file mode 100755 index 00000000..7b8194d9 --- /dev/null +++ b/tools/odbc/odbc.c @@ -0,0 +1,1146 @@ +#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; + sprint(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) + strcat(errp, 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 -1; + 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 = sprint(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); + sprint(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->aname); + 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->aname; + 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[128]; + +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: + sprint(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() +{ + 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", 0660, inferno); + newproto("db", 100); + for (;;) { + styxwait(&s); + styxprocess(&s); + } + styxend(&s); +} |
