summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 20:52:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 20:52:35 +0000
commit46439007cf417cbd9ac8049bb4122c890097a0fa (patch)
tree6fdb25e5f3a2b6d5657eb23b35774b631d4d97e4 /tools
parent37da2899f40661e3e9631e497da8dc59b971cbd0 (diff)
20060303-partial
Diffstat (limited to 'tools')
-rw-r--r--tools/NOTICE23
-rw-r--r--tools/db/infdb.c998
-rw-r--r--tools/db/mkfile13
-rw-r--r--tools/libstyx/Nt.c172
-rw-r--r--tools/libstyx/Plan9.c314
-rw-r--r--tools/libstyx/Posix.c164
-rw-r--r--tools/libstyx/mkfile11
-rw-r--r--tools/libstyx/styxaux.h14
-rw-r--r--tools/libstyx/styxserver.c1067
-rw-r--r--tools/libstyx/styxserver.h97
-rw-r--r--tools/mkfile7
-rwxr-xr-xtools/odbc/mkfile19
-rw-r--r--tools/odbc/mkfile-Linux1
-rw-r--r--tools/odbc/mkfile-MacOSX1
-rw-r--r--tools/odbc/mkfile-Nt2
-rw-r--r--tools/odbc/mkfile-Plan90
-rw-r--r--tools/odbc/mkfile-Solaris1
-rwxr-xr-xtools/odbc/odbc.c1146
-rwxr-xr-xtools/styxtest/mkfile23
-rw-r--r--tools/styxtest/mkfile-FreeBSD0
-rw-r--r--tools/styxtest/mkfile-Irix0
-rw-r--r--tools/styxtest/mkfile-Linux0
-rw-r--r--tools/styxtest/mkfile-MacOSX0
-rw-r--r--tools/styxtest/mkfile-Nt1
-rw-r--r--tools/styxtest/mkfile-Plan90
-rw-r--r--tools/styxtest/mkfile-Solaris1
-rw-r--r--tools/styxtest/styxtest.c198
-rw-r--r--tools/styxtest/styxtest0.c95
28 files changed, 4368 insertions, 0 deletions
diff --git a/tools/NOTICE b/tools/NOTICE
new file mode 100644
index 00000000..4138c8e7
--- /dev/null
+++ b/tools/NOTICE
@@ -0,0 +1,23 @@
+This copyright NOTICE applies to all files in this directory and
+subdirectories, unless another copyright notice appears in a given
+file or subdirectory. If you take substantial code from this software to use in
+other programs, you must somehow include with it an appropriate
+copyright notice that includes the copyright notice and the other
+notices below. It is fine (and often tidier) to do that in a separate
+file such as NOTICE, LICENCE or COPYING.
+
+Copyright © 2000-2006 Vita Nuova Holdings Limited
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License (`LGPL') as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/tools/db/infdb.c b/tools/db/infdb.c
new file mode 100644
index 00000000..3cc64ad6
--- /dev/null
+++ b/tools/db/infdb.c
@@ -0,0 +1,998 @@
+//
+// infdb - NT data base daemon for Inferno
+//
+// Copyright 1997 Lucent Technologies
+//
+// May 1997
+//
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#ifdef WIN32
+#include <winsock.h>
+#include <SQL.h>
+#include <SQLEXT.h>
+#else
+#include <unistd.h>
+#include <sql.h>
+#include <sqlext.h>
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define strnicmp strncasecmp
+#endif
+
+#define MAXCOLS 100
+#define BUFSIZE 8192
+#define REQ_HEADER_SIZE 18
+#define RES_HEADER_SIZE 22
+#define OFFSET_LENGTH 2
+#define OFFSET_STREAM 14
+#define OFFSET_REQ_DATA 18
+#define OFFSET_RETURN 18
+#define OFFSET_RES_DATA 22
+
+#define CONN_ALLOC_FAIL 1
+#define STREAM_ALLOC_FAIL 2
+#define STREAM_BAD_ID 3
+#define LAST_ERROR_NO 4
+
+
+//
+// Deal with one connection. Use stdin and stdout to read and write messages.
+// Each incoming message is answered before reading the next incoming message.
+//
+
+typedef int STATUS;
+#define OK 0
+#define WARN -1
+#define ERR -2
+
+typedef struct {
+ int state;
+#define SQLC_FREE 0
+#define SQLC_INUSE 1
+ int connid;
+ int refcount;
+ UCHAR user[48];
+ UCHAR passwd[48];
+ UCHAR dbname[48];
+ UCHAR errmsg[256];
+ HDBC hdbc;
+} SQLConn;
+
+
+typedef struct {
+ int state;
+#define SQLS_FREE 0
+#define SQLS_INUSE 1
+ int streamid;
+ int connid;
+ HSTMT hstmt;
+ UCHAR errmsg[256];
+ UCHAR colname[MAXCOLS][32];
+ SWORD coltype[MAXCOLS];
+ SWORD colnamelen;
+ SWORD nullable;
+ UDWORD collen[MAXCOLS];
+ SWORD scale;
+ SDWORD outlen[MAXCOLS];
+ UCHAR *data[MAXCOLS];
+ SWORD nresultcols;
+ SDWORD rowcount;
+ SWORD rownum;
+ RETCODE rc;
+ UCHAR *setdata[MAXCOLS];
+ SDWORD setdatalen[MAXCOLS];
+} SQLStream;
+
+
+typedef struct {
+ HENV henv;
+ int maxconn;
+ int numconn;
+ SQLConn **scarray;
+ int maxstream;
+ int numstream;
+ SQLStream **ssarray;
+} SQLEnv;
+
+
+typedef struct {
+ char mtype;
+ char version;
+ int nbytes;
+ int sstream;
+ int retcode;
+ int bytesNotRead;
+ char *data;
+} DBMSG, *DBMSGP;
+
+
+int getCommand (DBMSGP msgp, UCHAR *buf, int bufsiz);
+void sendResponse (char type, int lendata, int sstream, int retcode, char *data);
+void sendError (char *errmsg, int sstream);
+void print_err (SQLEnv *sqle, int connid, int streamid, UCHAR * buf, int bufsiz);
+UDWORD display_size (SWORD coltype, UDWORD collen, UCHAR *colname);
+
+STATUS newSqlEnv (SQLEnv **sqle);
+STATUS freeSqlEnv (SQLEnv **sqle);
+
+STATUS newSqlConn (SQLEnv *sqle, char *info, int *connid);
+STATUS mapSqlConn (SQLEnv *sqle, int connid, SQLConn **sqlc);
+STATUS freeSqlConn (SQLEnv *sqle, int connid);
+
+STATUS newSqlStream (SQLEnv *sqle, int connid, int *streamid);
+STATUS mapSqlStream (SQLEnv *sqle, int streamid, SQLStream **sqls);
+STATUS freeSqlStream (SQLEnv *sqle, int streamid);
+
+STATUS parseConnInfo (SQLConn *sqlc, char *info);
+
+char *iError[] = {
+ "INFDB: DB connection allocation failed",
+ "INFDB: couldn't allocate SQL stream",
+ "INFDB: bad SQL stream identifier"
+};
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int notdone = 1;
+ int infErrno;
+ DBMSG msg;
+ SQLEnv *sqle = NULL;
+ SQLStream *sqls;
+ char buf[BUFSIZE];
+ char outbuf[BUFSIZE];
+ char errmsg[256];
+ STATUS rc;
+
+ // We just have to talk to stdin and stdout. However, stdout may be open
+ // in text mode, which is bad for data. Set it to binary mode.
+
+#ifdef WIN32
+ _setmode(0, _O_BINARY);
+ _setmode(1, _O_BINARY);
+#endif
+
+ rc = newSqlEnv(&sqle);
+ if ( rc != OK ) {
+ sendError("INFDB: Failed to allocate SQL environment.", -1);
+ return -1;
+ }
+
+ while ( notdone ) {
+ int bytesRead;
+
+ bytesRead = 0;
+ if ( (bytesRead = getCommand(&msg, buf, sizeof(buf))) <= 0 ) {
+ continue;
+ }
+ msg.retcode = 0;
+ infErrno = 0;
+
+ switch ( msg.mtype ) {
+ // Initiate a new connection.
+ case 'I':
+ {
+ int connid;
+
+ rc = newSqlConn(sqle, msg.data, &connid);
+ if ( rc != OK ) {
+ infErrno = CONN_ALLOC_FAIL;
+ break;
+ }
+ /*
+ // Need a new SQLStream to make subsequent requests.
+ rc = newSqlStream(sqle, connid, &streamid);
+ if ( rc != OK ) {
+ infErrno = STREAM_ALLOC_FAIL;
+ break;
+ }
+
+ sprintf(outbuf, "%d", streamid);
+ */
+ sprintf(outbuf, "%d", connid);
+ sendResponse('i', strlen(outbuf), msg.sstream, 0, outbuf);
+ break;
+ }
+
+
+ case 'O':
+ {
+ // open an SQL stream.
+ int connid, streamid;
+
+ connid = atoi(msg.data);
+ rc = newSqlStream(sqle, connid, &streamid);
+ if (rc != OK) {
+ infErrno = STREAM_ALLOC_FAIL;
+ break;
+ }
+
+ sprintf(outbuf, "%d", streamid);
+ sendResponse('o', strlen(outbuf), msg.sstream, 0, outbuf);
+ break;
+ }
+
+
+ case 'K':
+ // klose an SQL stream
+ rc = freeSqlStream(sqle, msg.sstream);
+ sendResponse('k', 0, msg.sstream, 0, "");
+ break;
+
+
+ case 'C':
+ // request number of columns
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ sprintf(outbuf, "%d", sqls->nresultcols);
+ sendResponse('c', strlen(outbuf), msg.sstream, 0, outbuf);
+ break;
+
+
+ case 'N':
+ // fetch next row
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ sqls->errmsg[0] = '\0';
+ rc = SQLFetch(sqls->hstmt);
+ if ( rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO ) {
+ sqls->rownum++;
+ // if ( rc == SQL_SUCCESS_WITH_INFO ) {
+ // fprintf(stderr, "SQLFetch got SQL_SUCCESS_WITH_INFO\n");
+ // }
+ /* Get the data for all columns */
+ for ( i = 0; i < sqls->nresultcols; i++ ) {
+ rc = SQLGetData(sqls->hstmt, (UWORD)(i+1),
+ (sqls->coltype[i] == SQL_LONGVARBINARY ||
+ sqls->coltype[i] == SQL_LONGVARCHAR) ? SQL_C_DEFAULT :
+ SQL_C_CHAR,
+ sqls->data[i], sqls->collen[i], &sqls->outlen[i]);
+ if ( rc == SQL_SUCCESS_WITH_INFO &&
+ (UDWORD) sqls->outlen[i] > sqls->collen[i] ) {
+ UCHAR *tmp;
+
+ tmp = (UCHAR *) realloc(sqls->data[i], sqls->outlen[i]+1);
+ if ( tmp != NULL ) {
+ SDWORD dummy;
+ sqls->data[i] = tmp;
+ rc = SQLGetData(sqls->hstmt, (UWORD)(i+1), SQL_C_DEFAULT,
+ &tmp[sqls->collen[i]], sqls->outlen[i], &dummy);
+ sqls->collen[i] = sqls->outlen[i];
+ sqls->data[i][sqls->outlen[i]] = 0;
+ }
+ }
+ else if ( rc != SQL_SUCCESS ) {
+ sprintf(sqls->errmsg, "Problem retrieving data from data base, col %d", i+1);
+ msg.retcode = 2;
+ }
+ }
+ }
+ else if ( rc == SQL_NO_DATA_FOUND ) {
+ sqls->rownum = 0;
+ msg.retcode = 1;
+ }
+ else {
+ sqls->rownum = -1;
+ // Probably should get some status from ODBC for message
+ sprintf(sqls->errmsg, "Error occurred in fetching data");
+ }
+ if ( sqls->rownum < 0 ) {
+ sendError(errmsg, msg.sstream);
+ }
+ else {
+ sprintf(outbuf, "%d", sqls->rownum);
+ // rownum should be <= rowcount
+ sendResponse('n', strlen(outbuf), msg.sstream, msg.retcode, outbuf);
+ }
+ break;
+
+
+ case 'H':
+ // request an error message, if any
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ sendError(sqls->errmsg, msg.sstream);
+ sqls->errmsg[0] = 0;
+ break;
+
+
+ case 'P':
+ // request write data
+ // in Inferno, param nums start at 0; in ODBC/SQL, they start at 1
+ // This leaves outdatalen[0] unused, hence available as final SQLBindParameter arg.
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ sqls->errmsg[0] = 0;
+ if ( (i = atoi(msg.data) + 1) < 1 || i >= MAXCOLS ) {
+ sendError("Illegal param number", msg.sstream);
+ }
+ else {
+ int len;
+ char *p = msg.data + 4; // data points to param number
+
+ len = msg.nbytes - 4; // number of data chars
+ if ( len < 0 ) {
+ sendError("Write phase error II", msg.sstream);
+ break;
+ }
+ if ( sqls->setdata[i] != NULL ) {
+ free(sqls->setdata[i]);
+ }
+ sqls->setdata[i] = (char *) malloc(len + 1);
+ if ( sqls->setdata[i] == NULL ) {
+ sendError("Allocation error in server", msg.sstream);
+ break;
+ }
+ // Copy data we have into buffer, and if we don't have it all yet,
+ // try to get the rest.
+ sqls->setdatalen[i] = len++; // adjust len for trailing \n
+ bytesRead = &buf[bytesRead] - p; // number data bytes we have read
+ memcpy(sqls->setdata[i], p, bytesRead);
+ len -= bytesRead; // number bytes still to read,
+ while ( len > 0 ) {
+ int n;
+
+ if ( (n = read(0, sqls->setdata[i] + bytesRead, len)) <= 0 ) {
+ break;
+ }
+ bytesRead += n;
+ len -= n;
+ }
+ if ( len > 0 ) {
+ sendError("Couldn't read all of parameter", msg.sstream);
+ break;
+ }
+ rc = SQLBindParameter(sqls->hstmt, (UWORD)i, SQL_PARAM_INPUT,
+ SQL_C_BINARY, SQL_LONGVARBINARY,
+ sqls->setdatalen[i], 0, (PTR) i, 0, sqls->setdatalen);
+ if ( rc != SQL_SUCCESS ) {
+ sendError("BindParameter failed: maybe not supported", msg.sstream);
+ break;
+ }
+ sqls->setdatalen[0] = SQL_LEN_DATA_AT_EXEC(0);
+ sprintf(outbuf, "%d", bytesRead - 1);
+ sendResponse('p', strlen(outbuf), msg.sstream, 0, outbuf);
+ }
+ break;
+
+
+ case 'R':
+ // request read data
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ if ( (i = atoi(msg.data)) < 0 || i >= sqls->nresultcols || sqls->rownum <= 0 ) {
+ sendError(sqls->rownum <= 0 ? "No current row" : "Illegal column number", msg.sstream);
+ }
+ else if ( sqls->outlen[i] == SQL_NULL_DATA || sqls->outlen[i] == SQL_NO_TOTAL ) {
+ sendResponse('r', 0, msg.sstream, 0, "");
+ }
+ else {
+ if ( sqls->coltype[i] == SQL_VARCHAR ) {
+ sqls->outlen[i] = strlen(sqls->data[i]);
+ }
+ sendResponse('r', sqls->outlen[i], msg.sstream, 0, sqls->data[i]);
+ }
+ break;
+
+
+ case 'T':
+ // request column title
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ if ( (i = atoi(msg.data)) < 0 || i >= sqls->nresultcols ) {
+ sendError("Illegal column number", msg.sstream);
+ }
+ else {
+ sendResponse('t', strlen(sqls->colname[i]), msg.sstream, 0, sqls->colname[i]);
+ }
+ break;
+
+
+ case 'W':
+ // execute command
+ rc = mapSqlStream(sqle, msg.sstream, &sqls);
+ if ( rc != OK ) {
+ infErrno = STREAM_BAD_ID;
+ break;
+ }
+
+ if ( sqls->hstmt ) {
+ SQLFreeStmt(sqls->hstmt, SQL_CLOSE);
+ SQLFreeStmt(sqls->hstmt, SQL_UNBIND);
+ }
+ // Look for special extensions
+ if ( strnicmp(msg.data, "commit", 6) == 0 ) {
+ SQLConn *sqlc;
+
+ rc = mapSqlConn(sqle, sqls->connid, &sqlc);
+ rc = SQLTransact(SQL_NULL_HENV, sqlc->hdbc, SQL_COMMIT);
+ }
+ else if ( strnicmp(msg.data, "rollback", 8) == 0 ) {
+ SQLConn *sqlc;
+
+ rc = mapSqlConn(sqle, sqls->connid, &sqlc);
+ rc = SQLTransact(SQL_NULL_HENV, sqlc->hdbc, SQL_ROLLBACK);
+ }
+
+ else if ( strnicmp(msg.data, "tables", 6) == 0 ) {
+ rc = SQLTables(sqls->hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
+ }
+ else if ( strnicmp(msg.data, "columns", 7) == 0 ) {
+ UCHAR *tbl;
+
+ for ( tbl = msg.data+8; *tbl == ' ' || *tbl == '\t'; tbl++ ) { }
+
+ rc = SQLColumns(sqls->hstmt, NULL, 0, NULL, 0, tbl, SQL_NTS, NULL, 0);
+ }
+ else {
+ rc = SQLExecDirect(sqls->hstmt, msg.data, SQL_NTS);
+ }
+ outbuf[0] = '\0';
+ while ( rc == SQL_NEED_DATA ) {
+ PTR pToken;
+// SDWORD pnum;
+
+ rc = SQLParamData(sqls->hstmt, &pToken);
+// pnum = (SDWORD) pToken;
+#define pnum (int)pToken
+ if ( rc == SQL_NEED_DATA ) {
+ int retcode;
+
+ if ( sqls->setdata[pnum] == NULL || sqls->setdatalen[pnum] <= 0 ) {
+ sprintf(outbuf, "Parameter %d not set\n", pnum);
+ break;
+ }
+ for ( i = 0; i < sqls->setdatalen[pnum]; ) {
+ int togo;
+
+ togo = 1024;
+ if ( sqls->setdatalen[pnum] - i < 1024 ) {
+ togo = sqls->setdatalen[pnum] - i;
+ }
+ retcode = SQLPutData(sqls->hstmt,
+ sqls->setdata[pnum] + i, togo);
+ i += togo;
+ if ( retcode != SQL_SUCCESS ) {
+ print_err(sqle, -1, msg.sstream, &outbuf[strlen(outbuf)], sizeof (outbuf) - strlen(outbuf));
+ break;
+ }
+ }
+ if ( retcode != SQL_SUCCESS /* && retcode != SQL_SUCCESS_WITH_INFO */) {
+ break;
+ }
+ }
+ }
+ if ( rc != SQL_SUCCESS ) {
+ strcat(outbuf, "Command execution failed\n");
+ switch ( rc ) {
+ case SQL_SUCCESS_WITH_INFO:
+ strcat(outbuf, ": SQL_SUCCESS_WITH_INFO");
+ print_err(sqle, -1, msg.sstream, &outbuf[strlen(outbuf)], sizeof (outbuf) - strlen(outbuf));
+ break;
+ case SQL_ERROR:
+ strcat(outbuf, ": SQL_ERROR");
+ print_err(sqle, -1, msg.sstream, &outbuf[strlen(outbuf)], sizeof (outbuf) - strlen(outbuf));
+ break;
+ case SQL_NEED_DATA:
+ strcat(outbuf, ": SQL_NEED_DATA");
+ break;
+ case SQL_STILL_EXECUTING:
+ strcat(outbuf, ": SQL_STILL_EXECUTING");
+ break;
+ case SQL_INVALID_HANDLE:
+ strcat(outbuf, ": SQL_INVALID_HANDLE");
+ break;
+ }
+ sendError(outbuf, msg.sstream);
+ break;
+ }
+ SQLNumResultCols(sqls->hstmt, &sqls->nresultcols);
+ if ( sqls->nresultcols == 0 ) { // was not 'select' command
+ SQLRowCount(sqls->hstmt, &sqls->rowcount); // we don't use this, do we?
+ }
+ else { // get the column labels, save for later
+ for ( i = 0; i < sqls->nresultcols; i++ ) {
+ int newlen;
+
+ SQLDescribeCol(sqls->hstmt, (UWORD) (i+1), sqls->colname[i],
+ (SWORD)sizeof(sqls->colname[i]),
+ &sqls->colnamelen, &sqls->coltype[i], &sqls->collen[i],
+ &sqls->scale, &sqls->nullable);
+ sqls->colname[i][sqls->colnamelen] = 0;
+ // Adjust the length, since we are converting everything to strings.
+ if ( (newlen = display_size(sqls->coltype[i], sqls->collen[i],
+ sqls->colname[i])) != 0 ) {
+ sqls->collen[i] = newlen;
+ }
+ if ( sqls->collen[i] == 0 ) {
+ sqls->collen[i] = BUFSIZE;
+ }
+ sqls->data[i] = (UCHAR *) malloc(sqls->collen[i] + 1);
+ /*
+ SQLBindCol(sqls->hstmt, (UWORD) (i+1), newlen > 0 ? SQL_C_CHAR : SQL_C_DEFAULT,
+ sqls->data[i], sqls->collen[i], &sqls->outlen[i]);
+ */
+ }
+ sqls->rownum = 0;
+ }
+ sendResponse('w', 0, msg.sstream, 0, "");
+ break;
+
+
+ case 'X':
+ notdone = 0;
+ break;
+
+
+ default:
+ sprintf(sqls->errmsg, "Unknown command: %c", msg.mtype);
+ sendError(sqls->errmsg, msg.sstream);
+ sqls->errmsg[0] = '\0';
+ break;
+ } // end of switch (msg.mtype)
+ if ( infErrno > 0 && infErrno < LAST_ERROR_NO ) {
+ sendError(iError[infErrno - 1], msg.sstream);
+ }
+ } // end of while (notdone)
+ rc = freeSqlEnv(&sqle);
+
+ return 0;
+}
+
+//
+// All the incoming commands should end with a newline character.
+// We read until we get one. Then we verify that we have read as
+// many bytes as the count in message says we should.
+//
+int
+getCommand(DBMSGP msgp, UCHAR *buf, int bufsiz)
+{
+ int bytesRead = 0;
+ int rc = 0;
+
+ msgp->mtype = '\0';
+ while ( bufsiz > 0 && (rc = read(0, &buf[bytesRead], bufsiz)) > 0 ) {
+ bytesRead += rc;
+ bufsiz -= rc;
+ msgp->bytesNotRead -= rc;
+ if ( msgp->mtype == '\0' && bytesRead >= REQ_HEADER_SIZE ) {
+ if ( (msgp->version = buf[1]) != '1' ) { // wrong version, give up
+ char *wrong_version = "Message has wrong version number";
+ sendResponse('h', strlen(wrong_version), 0, 0, wrong_version);
+ return -1;
+ }
+ msgp->mtype = buf[0];
+ msgp->nbytes = atoi(buf+OFFSET_LENGTH);
+ msgp->sstream = atoi(buf+OFFSET_STREAM);
+ msgp->data = buf+OFFSET_REQ_DATA;
+ msgp->bytesNotRead = REQ_HEADER_SIZE + msgp->nbytes + 1 - bytesRead;
+ if ( bufsiz > msgp->bytesNotRead ) {
+ bufsiz = msgp->bytesNotRead;
+ }
+ }
+ }
+ if ( rc < 0 ) {
+ // log a problem
+ // fprintf(stderr, "Problem reading from client\n");
+ return rc;
+ }
+ if ( msgp->bytesNotRead == 0 ) {
+ msgp->data[msgp->nbytes] = 0; // discard final newline
+ }
+ return bytesRead;
+}
+
+
+void
+sendResponse(char type, int lendata, int sstream, int retcode, char *data)
+{
+ char hdr[RES_HEADER_SIZE+2];
+
+ sprintf(hdr, "%c1%11d %3d %3d ", type, lendata, sstream, retcode);
+ write(1, hdr, RES_HEADER_SIZE);
+ write(1, data, lendata);
+ write(1, "\n", 1);
+}
+
+
+void
+sendError(char *errmsg, int sstream)
+{
+ sendResponse('h', strlen(errmsg), sstream, 0, errmsg);
+}
+
+
+void
+print_err(SQLEnv *sqle, int connid, int streamid, UCHAR * buf, int bufsiz)
+{
+ RETCODE rc;
+ UCHAR stateString[40];
+ SDWORD native;
+ SWORD msglen;
+ SQLConn *sqlc;
+ SQLStream *sqls;
+ HENV *henv;
+ HDBC *hdbc;
+ HSTMT *hstmt;
+
+ henv = sqle->henv;
+
+ rc = mapSqlConn(sqle, connid, &sqlc);
+ hdbc = rc == OK ? sqlc->hdbc : SQL_NULL_HDBC;
+
+ rc = mapSqlStream(sqle, streamid, &sqls);
+ hstmt = rc == OK ? sqls->hstmt : SQL_NULL_HSTMT;
+
+ rc = SQLError(henv, hdbc, hstmt, stateString, &native, buf, (SWORD) bufsiz, &msglen);
+}
+
+
+#define MAX_NUM_PRECISION 15
+
+/* Define max length of char string representation of number as: */
+/* = max(precision) + leading sign + E + exp sign + max exp length */
+/* = 15 + 1 + 1 + 1 + 2 */
+/* = 15 + 5 */
+
+#define MAX_NUM_STRING_SIZE (MAX_NUM_PRECISION + 5)
+
+UDWORD
+display_size(SWORD coltype, UDWORD collen, UCHAR *colname)
+{
+switch (coltype) {
+
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ return max(collen, strlen(colname));
+
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ case SQL_BIT:
+ return max(6, strlen(colname));
+
+ case SQL_INTEGER:
+ return max(11, strlen(colname));
+
+ case SQL_BIGINT:
+ return max(30, strlen(colname));
+
+ case SQL_DATE:
+ case SQL_TIME:
+ case SQL_TIMESTAMP:
+ return max(50, strlen(colname));
+
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ case SQL_REAL:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ return(max(MAX_NUM_STRING_SIZE, strlen(colname)));
+
+ case SQL_LONGVARBINARY:
+ case SQL_LONGVARCHAR:
+ return BUFSIZE;
+
+ /* Note that this function only supports the core data types. */
+ /* For unknown data types, the caller should assume binary data */
+ default:
+ /* fprintf(stderr, "Unknown datatype, %d\n", coltype); */
+ return 0;
+ }
+}
+
+
+STATUS
+newSqlEnv(SQLEnv **sqle)
+{
+ SQLEnv *newenv;
+ STATUS rc;
+
+ newenv = (SQLEnv *) calloc(1, sizeof(SQLEnv));
+ if (newenv == NULL) {
+ return ERR;
+ }
+
+ rc = SQLAllocEnv(&newenv->henv);
+ if ( rc != SQL_SUCCESS) {
+ free (newenv);
+ return ERR;
+ }
+
+ *sqle = newenv;
+ return OK;
+}
+
+
+STATUS
+freeSqlEnv(SQLEnv **sqle)
+{
+ int i;
+ STATUS rc;
+
+ for (i = 0; i < (*sqle)->maxstream; i++) {
+ // Free this stream.
+ // Connection will be freed automatically.
+ rc = freeSqlStream(*sqle, i);
+ }
+ // dealloc the stream structures
+ // dealloc the connect structures
+ SQLFreeEnv((*sqle)->henv);
+ // dealloc the env structure
+
+ return OK;
+}
+
+
+STATUS
+mapSqlConn(SQLEnv *sqle, int connid, SQLConn **sqlc)
+{
+ if ( connid >= 0 && connid < sqle->maxconn ) {
+ *sqlc = sqle->scarray[connid];
+ if ( (*sqlc)->state == SQLC_INUSE )
+ return OK;
+ }
+ return ERR;
+}
+
+
+STATUS
+newSqlConn(SQLEnv *sqle, char *info, int *connid)
+{
+ SQLConn **newarray, *sqlc;
+ int newid = -1, i;
+ STATUS rc;
+
+ *connid = -1;
+
+ // Connect to the database.
+ // Search for an available connection structure to reuse
+ for ( i = 0; i < sqle->maxconn; i++ ) {
+ sqlc = sqle->scarray[i];
+ if ( sqlc != NULL && sqlc->state == SQLC_FREE ) {
+ newid = i;
+ break;
+ }
+ }
+
+ if ( newid == -1 ) {
+ // Assign a new connection id
+ newid = sqle->maxconn++;
+
+ // Extend the connection pointer array
+ newarray = (SQLConn **) realloc((char *) sqle->scarray,
+ sqle->maxconn * sizeof(SQLConn*));
+ if ( newarray == NULL ) {
+ return ERR;
+ }
+ sqle->scarray = newarray;
+
+ // Allocate a new connection structure
+ sqlc = (SQLConn *) calloc(1, sizeof(SQLConn));
+ if ( sqlc == NULL ) {
+ return ERR;
+ }
+ sqle->scarray[newid] = sqlc;
+ }
+
+ // Ask ODBC for a new connection handle
+ rc = SQLAllocConnect(sqle->henv, &sqlc->hdbc);
+ if (rc == SQL_ERROR) {
+ return ERR;
+ }
+
+ sqlc->refcount = 0;
+ sqlc->state = SQLC_INUSE;
+
+ // Extract the username, password, and database name
+ rc = parseConnInfo(sqlc, info);
+ if ( rc != OK ) {
+ return ERR;
+ }
+
+ // Request an ODBC connection to the database
+ rc = SQLConnect(sqlc->hdbc, sqlc->dbname, SQL_NTS, sqlc->user, SQL_NTS,
+ sqlc->passwd, SQL_NTS);
+ if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) {
+ // log error?
+ // Should try to get something more specific from ODBC
+ sprintf(sqlc->errmsg, "Connect failed: user = %s, passwd = %s, dbname = %s",
+ sqlc->user, sqlc->passwd, sqlc->dbname);
+
+ SQLDisconnect(sqlc->hdbc);
+ SQLFreeConnect(sqlc->hdbc);
+ return ERR;
+ }
+ *connid = newid;
+
+ // Set connect option to disable auto commit
+ rc = SQLSetConnectOption(sqlc->hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
+ if ( rc != SQL_SUCCESS ) {
+ return WARN;
+ }
+
+ return OK;
+}
+
+
+STATUS
+freeSqlConn(SQLEnv *sqle, int connid)
+{
+ SQLConn *sqlc;
+ STATUS rc;
+
+ rc = mapSqlConn(sqle, connid, &sqlc);
+ if ( rc != OK ) {
+ return WARN;
+ }
+
+ SQLDisconnect(sqlc->hdbc);
+ SQLFreeConnect(sqlc->hdbc);
+ sqlc->state = SQLC_FREE;
+ return OK;
+}
+
+
+STATUS
+mapSqlStream(SQLEnv *sqle, int streamid, SQLStream **sqls)
+{
+ if ( streamid >= 0 && streamid < sqle->maxstream ) {
+ *sqls = sqle->ssarray[streamid];
+ if ( (*sqls)->state == SQLS_INUSE )
+ return OK;
+ }
+ return ERR;
+}
+
+
+STATUS
+newSqlStream(SQLEnv *sqle, int connid, int *streamid)
+{
+ HSTMT hstmt;
+ SQLConn *sqlc;
+ SQLStream **newarray, *sqls;
+ int newid = -1, i;
+ STATUS rc;
+
+ rc = mapSqlConn(sqle, connid, &sqlc);
+ if (rc != OK) {
+ return ERR;
+ }
+
+ // Search for an available stream structure to reuse
+ for ( i = 0; i < sqle->maxstream; i++ ) {
+ sqls = sqle->ssarray[i];
+ if ( sqls != NULL && sqls->state == SQLS_FREE ) {
+ newid = i;
+ break;
+ }
+ }
+
+ if ( newid == -1 ) {
+ // Assign a new stream id
+ newid = sqle->maxstream++;
+
+ // Extend the stream pointer array
+ newarray = (SQLStream **) realloc((char *) sqle->ssarray,
+ sqle->maxstream * sizeof(SQLStream*));
+ if ( newarray == NULL ) {
+ return ERR;
+ }
+ sqle->ssarray = newarray;
+
+ // Allocate a new stream structure
+ sqls = (SQLStream *) calloc(1, sizeof(SQLStream));
+ if ( sqls == NULL ) {
+ return ERR;
+ }
+ sqle->ssarray[newid] = sqls;
+ }
+
+ // Associate new stream with specified connection
+ sqls->connid = connid;
+ sqlc->refcount++;
+
+ // Ask ODBC to allocate a new statement handle
+ rc = SQLAllocStmt(sqlc->hdbc, &hstmt);
+ if (rc == SQL_ERROR) {
+ return ERR;
+ }
+ sqls->hstmt = hstmt;
+ sqls->state = SQLS_INUSE;
+
+ *streamid = newid;
+ return OK;
+}
+
+
+STATUS
+freeSqlStream(SQLEnv *sqle, int streamid)
+{
+ SQLConn *sqlc;
+ SQLStream *sqls;
+ STATUS rc;
+
+ rc = mapSqlStream(sqle, streamid, &sqls);
+ if ( rc != OK ) {
+ return WARN;
+ }
+
+ sqls->state = SQLS_FREE;
+
+ rc = SQLFreeStmt(sqls->hstmt, SQL_DROP);
+
+ rc = mapSqlConn(sqle, sqls->connid, &sqlc);
+ if ( rc != OK ) {
+ return WARN;
+ }
+
+ if ( --sqlc->refcount == 0 )
+ {
+ rc = freeSqlConn(sqle, sqls->connid);
+ if (rc != OK) {
+ return WARN;
+ }
+ }
+ return OK;
+}
+
+
+STATUS
+parseConnInfo(SQLConn *sqlc, char *info)
+{
+ UCHAR *temp;
+
+ // The argument 'info' points to a buffer containing a string
+ // of the form "username/password/dbname\n". We will use 'strtok'
+ // to tokenize the string into the parts we need, keeping
+ // copies in the 'sqlc' structure.
+
+ temp = strtok(info, "/\n");
+ if ( temp == NULL ) {
+ return ERR;
+ }
+ strncpy(sqlc->user, temp, 48);
+
+ temp = strtok(NULL, "/\n");
+ if ( temp == NULL ) {
+ return ERR;
+ }
+ strncpy(sqlc->passwd, temp, 48);
+
+ temp = strtok(NULL, "/\n");
+ if ( temp == NULL ) {
+ return ERR;
+ }
+ strncpy(sqlc->dbname, temp, 48);
+
+ return OK;
+}
+
+
+
diff --git a/tools/db/mkfile b/tools/db/mkfile
new file mode 100644
index 00000000..4f7c1b40
--- /dev/null
+++ b/tools/db/mkfile
@@ -0,0 +1,13 @@
+<../../mkconfig
+
+TARG=infdb
+
+OFILES= infdb.$O
+
+LIBS= 9\
+
+BIN=$ROOT/$OBJDIR/bin
+
+SYSLIBS= -liodbc
+
+<$ROOT/mkfiles/mkone-$SHELLTYPE
diff --git a/tools/libstyx/Nt.c b/tools/libstyx/Nt.c
new file mode 100644
index 00000000..682eca6b
--- /dev/null
+++ b/tools/libstyx/Nt.c
@@ -0,0 +1,172 @@
+#include <windows.h>
+#include <lib9.h>
+#include "styxserver.h"
+#include "styxaux.h"
+
+typedef struct Fdset Fdset;
+
+struct Fdset
+{
+ fd_set infds, outfds, excfds, r_infds, r_outfds, r_excfds;
+};
+
+int
+styxinitsocket(void)
+{
+ WSADATA wsaData;
+ WORD wVersionRequired=MAKEWORD(1,1);
+
+ int rv = WSAStartup(wVersionRequired, &wsaData);
+
+ if(rv != 0){
+ fprint(2, "Unable to Find winsock.dll");
+ return -1;
+ }
+ if(LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1 ){
+ fprint(2, "Unable to find winsock.dll V1.1 or later");
+ return -1;
+ }
+ return 0;
+}
+
+void
+styxendsocket(void)
+{
+ WSACleanup( );
+}
+
+void
+styxclosesocket(int fd)
+{
+ closesocket(fd);
+}
+
+int
+styxannounce(Styxserver *server, char *port)
+{
+ struct sockaddr_in sin;
+ int s, one;
+
+ USED(server);
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if(s < 0)
+ return s;
+ one = 1;
+ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0)
+ fprint(2, "setsockopt failed\n");
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = 0;
+ sin.sin_port = htons(atoi(port));
+ if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0){
+ close(s);
+ return -1;
+ }
+ if(listen(s, 20) < 0){
+ close(s);
+ return -1;
+ }
+ return s;
+}
+
+int
+styxaccept(Styxserver *server)
+{
+ struct sockaddr_in sin;
+ int len, s;
+
+ len = sizeof(sin);
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ s = accept(server->connfd, (struct sockaddr *)&sin, &len);
+ if(s < 0){
+ if(errno != EINTR)
+ fprint(2, "error in accept: %s\n", strerror(errno));
+ }
+ return s;
+}
+
+void
+styxinitwait(Styxserver *server)
+{
+ Fdset *fs;
+
+ server->priv = fs = malloc(sizeof(Fdset));
+ FD_ZERO(&fs->infds);
+ FD_ZERO(&fs->outfds);
+ FD_ZERO(&fs->excfds);
+ FD_SET(server->connfd, &fs->infds);
+}
+
+int
+styxnewcall(Styxserver *server)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ return FD_ISSET(server->connfd, &fs->r_infds);
+}
+
+void
+styxnewclient(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ FD_SET(s, &fs->infds);
+}
+
+void
+styxfreeclient(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ FD_CLR(s, &fs->infds);
+}
+
+int
+styxnewmsg(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ return FD_ISSET(s, &fs->r_infds) || FD_ISSET(s, &fs->r_excfds);
+}
+
+char*
+styxwaitmsg(Styxserver *server)
+{
+ struct timeval seltime;
+ int nfds;
+ Fdset *fs;
+
+ fs = server->priv;
+ fs->r_infds = fs->infds;
+ fs->r_outfds = fs->outfds;
+ fs->r_excfds = fs->excfds;
+ seltime.tv_sec = 10;
+ seltime.tv_usec = 0L;
+ nfds = select(sizeof(fd_set)*8, &fs->r_infds, &fs->r_outfds, &fs->r_excfds, &seltime);
+ if(nfds < 0 && errno != EINTR)
+ return"error in select";
+ return nil;
+}
+
+int
+styxrecv(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ return recv(fd, buf, n, m);
+}
+
+int
+styxsend(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ return send(fd, buf, n, m);
+}
+
+void
+styxexit(int n)
+{
+ exit(n);
+}
diff --git a/tools/libstyx/Plan9.c b/tools/libstyx/Plan9.c
new file mode 100644
index 00000000..3e92d443
--- /dev/null
+++ b/tools/libstyx/Plan9.c
@@ -0,0 +1,314 @@
+#include <lib9.h>
+#include "styxserver.h"
+#include "styxaux.h"
+
+typedef struct Listener Listener;
+typedef struct Reader Reader;
+typedef struct Union Union;
+
+struct Listener
+{
+ int fd;
+ char *dir;
+ Listener *next;
+};
+
+struct Reader
+{
+ int pid;
+ int fd;
+ int n;
+ char buf[MSGMAX];
+ char rbuf[MSGMAX];
+ Reader *next;
+};
+
+struct Union
+{
+ int pid;
+ Lock lock;
+ Listener *lr;
+ Reader *rr;
+};
+
+void xlock(Lock *l){ lock(l); }
+void xunlock(Lock *l){ unlock(l); }
+
+static Reader*
+findr(Styxserver *server, int fd)
+{
+ Reader *r;
+ Union *u;
+
+ u = server->priv;
+ xlock(&u->lock);
+ for(r = u->rr; r != nil; r = r->next)
+ if(r->fd == fd)
+ break;
+ xunlock(&u->lock);
+ return r;
+}
+
+int
+styxinitsocket(void)
+{
+ return 0;
+}
+
+void
+styxendsocket(void)
+{
+}
+
+void
+styxclosesocket(int fd)
+{
+ close(fd);
+}
+
+static void
+listener(Styxserver *server, int afd, char *adir)
+{
+ int s;
+ Listener *l;
+ Union *u;
+ char ld[40];
+
+ USED(afd);
+ u = server->priv;
+ for(;;){
+ s = listen(adir, ld);
+ /* fprint(2, "listen %d %s %s\n", s, adir, ld); */
+ if(s < 0){
+ u->pid = -1;
+ break;
+ }
+ l = malloc(sizeof(Listener));
+ l->fd = s;
+ l->dir = strdup(ld);
+ xlock(&u->lock);
+ l->next = u->lr;
+ u->lr = l;
+ xunlock(&u->lock);
+ }
+}
+
+int
+styxannounce(Styxserver *server, char *port)
+{
+ int s, pid;
+ Union *u;
+ char adr[32], adir[40];
+
+ server->priv = u = malloc(sizeof(Union));
+ u->lock.val = 0;
+ u->lr = nil;
+ u->rr = nil;
+ sprint(adr, "tcp!*!%s", port);
+ s = announce(adr, adir);
+ /* fprint(2, "announce %d %s %s\n", s, adr, adir); */
+ if(s < 0)
+ return s;
+ switch((pid = rfork(RFPROC|RFREND|RFMEM))){
+ case 0:
+ listener(server, s, strdup(adir));
+ break;
+ default:
+ u->pid = pid;
+ break;
+ }
+ return s;
+}
+
+static void
+reader(Styxserver *server, Reader *r)
+{
+ int m, n, s;
+ Union *u;
+
+ u = server->priv;
+ s = r->fd;
+ for(;;){
+ n = r->n;
+ if(n < 0){
+ r->pid = -1;
+ exits(nil);
+ }
+ m = read(s, r->rbuf, MSGMAX-n);
+ xlock(&u->lock);
+ n = r->n;
+ if(m < 0)
+ r->n = n == 0 ? m : n;
+ else{
+ memmove(r->buf+n, r->rbuf, m);
+ r->n = m+n;
+ }
+ xunlock(&u->lock);
+ }
+}
+
+int
+styxaccept(Styxserver *server)
+{
+ int s, fd, pid;
+ Reader *r;
+ Listener *l;
+ Union *u;
+ char *dir;
+
+ u = server->priv;
+ xlock(&u->lock);
+ if((l = u->lr) == nil){
+ xunlock(&u->lock);
+ return -1;
+ }
+ u->lr = l->next;
+ xunlock(&u->lock);
+ fd = l->fd;
+ dir = l->dir;
+ free(l);
+ s = accept(fd, dir);
+ /* fprint(2, "accept %d\n", s); */
+ free(dir);
+ if(s < 0)
+ return s;
+ r = malloc(sizeof(struct Reader));
+ r->fd = s;
+ r->n = 0;
+ xlock(&u->lock);
+ r->next = u->rr;
+ u->rr = r;
+ xunlock(&u->lock);
+ switch((pid = rfork(RFPROC|RFREND|RFMEM))){
+ case 0:
+ reader(server, r);
+ break;
+ case 1:
+ r->pid = pid;
+ break;
+ }
+ return s;
+}
+
+void
+styxinitwait(Styxserver *server)
+{
+ USED(server);
+}
+
+int
+styxnewcall(Styxserver *server)
+{
+ Union *u;
+
+ u = server->priv;
+ return u->lr != nil;
+}
+
+int
+styxnewmsg(Styxserver *server, int fd)
+{
+ Reader *r;
+
+ r = findr(server, fd);
+ return r != nil && r->n != 0;
+}
+
+void
+styxnewclient(Styxserver *server, int fd)
+{
+ USED(server);
+ USED(fd);
+}
+
+void
+styxfreeclient(Styxserver *server, int fd)
+{
+ Reader *r, **rp;
+ Union *u;
+
+ u = server->priv;
+ r = findr(server, fd);
+ if(r == nil)
+ return;
+ xlock(&u->lock);
+ for(rp = &u->rr; *rp != nil; rp = &(*rp)->next)
+ if(r == *rp){
+ *rp = r->next;
+ break;
+ }
+ xunlock(&u->lock);
+ if(r->pid >= 0)
+ postnote(PNPROC, r->pid, "kill");
+ free(r);
+}
+
+char*
+styxwaitmsg(Styxserver *server)
+{
+ int i;
+ Reader *r;
+ Union *u;
+
+ u = server->priv;
+ for(i = 0; i < 100; i++){
+ if(u->lr != nil)
+ return nil;
+ xlock(&u->lock);
+ for(r = u->rr; r != nil; r = r->next)
+ if(r->n != 0){
+ xunlock(&u->lock);
+ return nil;
+ }
+ xunlock(&u->lock);
+ sleep(100);
+ }
+ return nil;
+}
+
+int
+styxrecv(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ Reader *r;
+ Union *u;
+ int rn;
+ char *rbuf;
+
+ USED(m);
+ r = findr(server, fd);
+ if(r == nil)
+ return -1;
+ u = server->priv;
+ xlock(&u->lock);
+ rn = r->n;
+ rbuf = r->buf;
+ if(rn < 0){
+ xunlock(&u->lock);
+ return rn;
+ }
+ if(n > rn)
+ n = rn;
+ memmove(buf, rbuf, n);
+ rn -= n;
+ memmove(rbuf, rbuf+n, rn);
+ r->n = rn;
+ xunlock(&u->lock);
+ return n;
+}
+
+int
+styxsend(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ USED(server);
+ USED(m);
+ return write(fd, buf, n);
+}
+
+void
+styxexit(int n)
+{
+ if(n)
+ exits("error");
+ else
+ exits(nil);
+}
diff --git a/tools/libstyx/Posix.c b/tools/libstyx/Posix.c
new file mode 100644
index 00000000..dbadfd9f
--- /dev/null
+++ b/tools/libstyx/Posix.c
@@ -0,0 +1,164 @@
+#define __EXTENSIONS__
+#define _BSD_COMPAT
+#include <lib9.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "styxserver.h"
+#include "styxaux.h"
+
+typedef struct Fdset Fdset;
+
+struct Fdset
+{
+ fd_set infds, outfds, excfds, r_infds, r_outfds, r_excfds;
+};
+
+int
+styxinitsocket(void)
+{
+ return 0;
+}
+
+void
+styxendsocket(void)
+{
+}
+
+void
+styxclosesocket(int fd)
+{
+ close(fd);
+}
+
+int
+styxannounce(Styxserver *server, char *port)
+{
+ struct sockaddr_in sin;
+ int s, one;
+
+ USED(server);
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if(s < 0)
+ return s;
+ one = 1;
+ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0)
+ fprint(2, "setsockopt failed\n");
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = 0;
+ sin.sin_port = htons(atoi(port));
+ if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0){
+ close(s);
+ return -1;
+ }
+ if(listen(s, 20) < 0){
+ close(s);
+ return -1;
+ }
+ return s;
+}
+
+int
+styxaccept(Styxserver *server)
+{
+ struct sockaddr_in sin;
+ int len, s;
+
+ len = sizeof(sin);
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ s = accept(server->connfd, (struct sockaddr *)&sin, &len);
+ if(s < 0){
+ if(errno != EINTR)
+ fprint(2, "error in accept: %s\n", strerror(errno));
+ }
+ return s;
+}
+
+void
+styxinitwait(Styxserver *server)
+{
+ Fdset *fs;
+
+ server->priv = fs = malloc(sizeof(Fdset));
+ FD_ZERO(&fs->infds);
+ FD_ZERO(&fs->outfds);
+ FD_ZERO(&fs->excfds);
+ FD_SET(server->connfd, &fs->infds);
+}
+
+int
+styxnewcall(Styxserver *server)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ return FD_ISSET(server->connfd, &fs->r_infds);
+}
+
+void
+styxnewclient(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ FD_SET(s, &fs->infds);
+}
+
+void
+styxfreeclient(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ FD_CLR(s, &fs->infds);
+}
+
+int
+styxnewmsg(Styxserver *server, int s)
+{
+ Fdset *fs;
+
+ fs = server->priv;
+ return FD_ISSET(s, &fs->r_infds) || FD_ISSET(s, &fs->r_excfds);
+}
+
+char*
+styxwaitmsg(Styxserver *server)
+{
+ struct timeval seltime;
+ int nfds;
+ Fdset *fs;
+
+ fs = server->priv;
+ fs->r_infds = fs->infds;
+ fs->r_outfds = fs->outfds;
+ fs->r_excfds = fs->excfds;
+ seltime.tv_sec = 10;
+ seltime.tv_usec = 0L;
+ nfds = select(sizeof(fd_set)*8, &fs->r_infds, &fs->r_outfds, &fs->r_excfds, &seltime);
+ if(nfds < 0 && errno != EINTR)
+ return"error in select";
+ return nil;
+}
+
+int
+styxrecv(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ return recv(fd, buf, n, m);
+}
+
+int
+styxsend(Styxserver *server, int fd, char *buf, int n, int m)
+{
+ return send(fd, buf, n, m);
+}
+
+void
+styxexit(int n)
+{
+ exit(n);
+}
diff --git a/tools/libstyx/mkfile b/tools/libstyx/mkfile
new file mode 100644
index 00000000..1840bd2f
--- /dev/null
+++ b/tools/libstyx/mkfile
@@ -0,0 +1,11 @@
+<../../mkconfig
+
+LIB=libstyx.a
+
+OFILES=\
+ styxserver.$O\
+ $TARGMODEL.$O\
+
+HFILES=\
+
+<$ROOT/mkfiles/mksyslib-$SHELLTYPE
diff --git a/tools/libstyx/styxaux.h b/tools/libstyx/styxaux.h
new file mode 100644
index 00000000..030a49fb
--- /dev/null
+++ b/tools/libstyx/styxaux.h
@@ -0,0 +1,14 @@
+int styxinitsocket(void);
+void styxendsocket(void);
+void styxclosesocket(int);
+int styxannounce(Styxserver*, char *);
+void styxinitwait(Styxserver*);
+int styxnewcall(Styxserver*);
+int styxnewmsg(Styxserver*, int);
+int styxaccept(Styxserver *server);
+void styxnewclient(Styxserver*, int);
+void styxfreeclient(Styxserver*, int);
+char* styxwaitmsg(Styxserver*);
+int styxrecv(Styxserver*, int, char*, int, int);
+int styxsend(Styxserver*, int, char*, int, int);
+void styxexit(int);
diff --git a/tools/libstyx/styxserver.c b/tools/libstyx/styxserver.c
new file mode 100644
index 00000000..d0bf1c2c
--- /dev/null
+++ b/tools/libstyx/styxserver.c
@@ -0,0 +1,1067 @@
+#include <lib9.h>
+#include <styx.h>
+#include "styxserver.h"
+#include "styxaux.h"
+
+#define MAXSTAT 512
+#define EMSGLEN 256 /* %r */
+
+#define TABSZ 32 /* power of 2 */
+
+static unsigned long boottime;
+static char* eve = "inferno";
+static int Debug = 0;
+
+char Enomem[] = "out of memory";
+char Eperm[] = "permission denied";
+char Enodev[] = "no free devices";
+char Ehungup[] = "write to hungup channel";
+char Eexist[] = "file exists";
+char Enonexist[] = "file does not exist";
+char Ebadcmd[] = "bad command";
+char Ebadarg[] = "bad arg in system call";
+char Enofid[] = "no such fid";
+char Enotdir[] = "not a directory";
+char Eopen[] = "already open";
+char Ebadfid[] = "bad fid";
+
+/* client state */
+enum{
+ CDISC = 01,
+ CNREAD = 02,
+ CRECV = 04,
+};
+
+typedef struct Walkqid Walkqid;
+
+struct Fid
+{
+ Client *client;
+ Fid *next;
+ short fid;
+ ushort open;
+ ushort mode; /* read/write */
+ ulong offset; /* in file */
+ int dri; /* dirread index */
+ Qid qid;
+};
+
+struct Walkqid
+{
+ Fid *clone;
+ int nqid;
+ Qid qid[1];
+};
+
+#define ASSERT(A,B) styxassert((int)A,B)
+
+static int hash(Path);
+static void deletefids(Client *);
+
+static void
+styxfatal(char *fmt, ...)
+{
+ char buf[1024], *out;
+ va_list arg;
+ out = seprint(buf, buf+sizeof(buf), "Fatal error: ");
+ va_start(arg, fmt);
+ out = vseprint(out, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ write(2, buf, out-buf);
+ styxexit(1);
+}
+
+static void
+styxassert(int true, char *reason)
+{
+ if(!true)
+ styxfatal("assertion failed: %s\n", reason);
+}
+
+void *
+styxmalloc(int bytes)
+{
+ char *m = malloc(bytes);
+ if(m == nil)
+ styxfatal(Enomem);
+ memset(m, 0, bytes);
+ return m;
+}
+
+void
+styxfree(void *p)
+{
+ free(p);
+}
+
+void
+styxdebug()
+{
+ Debug = 1;
+}
+
+static Client *
+newclient(Styxserver *server, int fd)
+{
+ Client *c = (Client *)styxmalloc(sizeof(Client));
+
+ if(Debug)
+ fprint(2, "New client at %lux\n", (ulong)c);
+ c->server = server;
+ c->fd = fd;
+ c->nread = 0;
+ c->nc = 0;
+ c->state = 0;
+ c->fids = nil;
+ c->uname = strdup(eve);
+ c->aname = strdup(eve);
+ c->next = server->clients;
+ server->clients = c;
+ if(server->ops->newclient)
+ server->ops->newclient(c);
+ return c;
+}
+
+static void
+freeclient(Client *c)
+{
+ Client **p;
+ Styxserver *server;
+
+ if(Debug)
+ fprint(2, "Freeing client at %lux\n", (ulong)c);
+ server = c->server;
+ if(server->ops->freeclient)
+ server->ops->freeclient(c);
+ for(p = &server->clients; *p; p = &(*p)->next)
+ if(*p == c){
+ styxclosesocket(c->fd);
+ *p = c->next;
+ deletefids(c);
+ free(c->uname);
+ free(c->aname);
+ styxfree(c);
+ return;
+ }
+}
+
+static int
+nbread(Client *c, int nr)
+{
+ int nb;
+
+ if(c->state&CDISC)
+ return -1;
+ nb = styxrecv(c->server, c->fd, c->msg + c->nread, nr, 0);
+ if(nb <= 0){
+ c->nread = 0;
+ c->state |= CDISC;
+ return -1;
+ }
+ c->nread += nb;
+ return 0;
+}
+
+static int
+rd(Client *c, Fcall *r)
+{
+ if(c->nc > 0){ /* last convM2S consumed nc bytes */
+ c->nread -= c->nc;
+ if(c->nread < 0){
+ r->ename = "negative size in rd";
+ return -1;
+ }
+ memmove(c->msg, c->msg+c->nc, c->nread);
+ c->nc = 0;
+ }
+ if(c->state&CRECV){
+ if(nbread(c, MSGMAX - c->nread) != 0){
+ r->ename = "unexpected EOF";
+ return -1;
+ }
+ c->state &= ~CRECV;
+ }
+ c->nc = convM2S((uchar*)(c->msg), c->nread, r);
+ if(c->nc < 0){
+ r->ename = "bad message format";
+ return -1;
+ }
+ if(c->nc == 0 && c->nread > 0){
+ c->nread = 0;
+ c->state &= ~CNREAD;
+ return 0;
+ }
+ if(c->nread > c->nc)
+ c->state |= CNREAD;
+ else
+ c->state &= ~CNREAD;
+ if(c->nc == 0)
+ return 0;
+ /* fprint(2, "rd: %F\n", r); */
+ return 1;
+}
+
+static int
+wr(Client *c, Fcall *r)
+{
+ int n;
+ char buf[MSGMAX];
+
+ n = convS2M(r, (uchar*)buf, sizeof(buf));
+ if(n < 0){
+ r->ename = "bad message type in wr";
+ return -1;
+ }
+ /* fprint(2, "wr: %F\n", r); */
+ return styxsend(c->server, c->fd, buf, n, 0);
+}
+
+static void
+sremove(Styxserver *server, Styxfile *f)
+{
+ Styxfile *s, *next, **p;
+
+ if(f == nil)
+ return;
+ if(Debug)
+ fprint(2, "Remove file %s Qid=%llx\n", f->d.name, f->d.qid.path);
+ if(f->d.qid.type&QTDIR)
+ for(s = f->child; s != nil; s = next){
+ next = s->sibling;
+ sremove(server, s);
+ }
+ for(p = &server->ftab[hash(f->d.qid.path)]; *p; p = &(*p)->next)
+ if(*p == f){
+ *p = f->next;
+ break;
+ }
+ for(p = &f->parent->child; *p; p = &(*p)->sibling)
+ if(*p == f){
+ *p = f->sibling;
+ break;
+ }
+ styxfree(f->d.name);
+ styxfree(f->d.uid);
+ styxfree(f->d.gid);
+ styxfree(f);
+}
+
+int
+styxrmfile(Styxserver *server, Path qid)
+{
+ Styxfile *f;
+
+ f = styxfindfile(server, qid);
+ if(f != nil){
+ if(f->parent == nil)
+ return -1;
+ sremove(server, f);
+ return 0;
+ }
+ return -1;
+}
+
+static void
+incref(Styxfile *f)
+{
+ if(f != nil)
+ f->ref++;
+}
+
+static void
+decref(Styxfile *f)
+{
+ if(f != nil)
+ --f->ref;
+}
+
+static void
+increff(Fid *f)
+{
+ incref(styxfindfile(f->client->server, f->qid.path));
+}
+
+static void
+decreff(Fid *f)
+{
+ decref(styxfindfile(f->client->server, f->qid.path));
+}
+
+static void
+incopen(Fid *f)
+{
+ Styxfile *file;
+
+ if(f->open && (file = styxfindfile(f->client->server, f->qid.path)) != nil)
+ file->open++;
+}
+
+static void
+decopen(Fid *f)
+{
+ Styxfile *file;
+
+ if(f->open && (file = styxfindfile(f->client->server, f->qid.path)) != nil)
+ file->open--;
+}
+
+int
+styxperm(Styxfile *f, char *uid, int mode)
+{
+ int m, p;
+
+ p = 0;
+ switch(mode&3){
+ case OREAD: p = AREAD; break;
+ case OWRITE: p = AWRITE; break;
+ case ORDWR: p = AREAD+AWRITE; break;
+ case OEXEC: p = AEXEC; break;
+ }
+ if(mode&OTRUNC)
+ p |= AWRITE;
+ m = f->d.mode&7;
+ if((p&m) == p)
+ return 1;
+ if(strcmp(f->d.uid, uid) == 0){
+ m |= (f->d.mode>>6)&7;
+ if((p&m) == p)
+ return 1;
+ }
+ if(strcmp(f->d.gid, uid) == 0){
+ m |= (f->d.mode>>3)&7;
+ if((p&m) == p)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+hash(Path path)
+{
+ return path&(TABSZ-1);
+}
+
+Styxfile *
+styxfindfile(Styxserver *server, Path path)
+{
+ Styxfile *f;
+
+ for(f = server->ftab[hash(path)]; f != nil; f = f->next){
+ if(f->d.qid.path == path)
+ return f;
+ }
+ return nil;
+}
+
+static Fid *
+findfid(Client *c, short fid)
+{
+ Fid *f;
+ for(f = c->fids; f && f->fid != fid; f = f->next)
+ ;
+ return f;
+}
+
+static void
+deletefid(Client *c, Fid *d)
+{
+ /* TODO: end any outstanding reads on this fid */
+ Fid **f;
+
+ for(f = &c->fids; *f; f = &(*f)->next)
+ if(*f == d){
+ decreff(d);
+ decopen(d);
+ *f = d->next;
+ styxfree(d);
+ return;
+ }
+}
+
+static void
+deletefids(Client *c)
+{
+ Fid *f, *g;
+
+ for(f = c->fids; f; f = g){
+ decreff(f);
+ decopen(f);
+ g = f->next;
+ styxfree(f);
+ }
+}
+
+Fid *
+newfid(Client *c, short fid, Qid qid){
+ Fid *f;
+
+ f = styxmalloc(sizeof(Fid));
+ ASSERT(f, "newfid");
+ f->client = c;
+ f->fid = fid;
+ f->open = 0;
+ f->dri = 0;
+ f->qid = qid;
+ f->next = c->fids;
+ c->fids = f;
+ increff(f);
+ return f;
+}
+
+static void
+flushtag(int oldtag)
+{
+ USED(oldtag);
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+ return a.path == b.path && a.vers == b.vers;
+}
+
+static Fid *
+fidclone(Fid *old, short fid)
+{
+ Fid *new;
+
+ new = newfid(old->client, fid, old->qid);
+ return new;
+}
+
+static Walkqid*
+devwalk(Client *c, Styxfile *file, Fid *fp, Fid *nfp, char **name, int nname, char **err)
+{
+ Styxserver *server;
+ long j;
+ Walkqid *wq;
+ char *n;
+ Styxfile *p, *f;
+ Styxops *ops;
+ Qid qid;
+
+ *err = nil;
+ server = c->server;
+ ops = server->ops;
+
+ wq = styxmalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ wq->nqid = 0;
+
+ p = file;
+ qid = p != nil ? p->d.qid : fp->qid;
+ for(j = 0; j < nname; j++){
+ if(!(qid.type&QTDIR)){
+ if(j == 0)
+ styxfatal("devwalk error");
+ *err = Enotdir;
+ goto Done;
+ }
+ if(p != nil && !styxperm(p, c->uname, OEXEC)){
+ *err = Eperm;
+ goto Done;
+ }
+ n = name[j];
+ if(strcmp(n, ".") == 0){
+ Accept:
+ wq->qid[wq->nqid++] = nfp->qid;
+ continue;
+ }
+ if(p != nil && strcmp(n, "..") == 0 && p->parent){
+ decref(p);
+ nfp->qid.path = p->parent->d.qid.path;
+ nfp->qid.type = p->parent->d.qid.type;
+ nfp->qid.vers = 0;
+ incref(p->parent);
+ p = p->parent;
+ qid = p->d.qid;
+ goto Accept;
+ }
+
+ if(ops->walk != nil){
+ char *e;
+
+ e = ops->walk(&qid, n);
+ if(e == nil){
+ decreff(nfp);
+ nfp->qid = qid;
+ increff(nfp);
+ p = styxfindfile(server, qid.path);
+ if(server->needfile && p == nil)
+ goto Done;
+ qid = p != nil ? p->d.qid : nfp->qid;
+ goto Accept;
+ }
+ }
+
+ if(p != nil)
+ for(f = p->child; f != nil; f = f->sibling){
+ if(strcmp(n, f->d.name) == 0){
+ decref(p);
+ nfp->qid.path = f->d.qid.path;
+ nfp->qid.type = f->d.qid.type;
+ nfp->qid.vers = 0;
+ incref(f);
+ p = f;
+ qid = p->d.qid;
+ goto Accept;
+ }
+ }
+ if(j == 0 && *err == nil)
+ *err = Enonexist;
+ goto Done;
+ }
+Done:
+ if(*err != nil){
+ styxfree(wq);
+ return nil;
+ }
+ return wq;
+}
+
+static long
+devdirread(Fid *fp, Styxfile *file, char *d, long n)
+{
+ long dsz, m;
+ Styxfile *f;
+ int i;
+
+ struct{
+ Dir d;
+ char slop[100]; /* TO DO */
+ }dir;
+
+ f = file->child;
+ for(i = 0; i < fp->dri; i++)
+ if(f == 0)
+ return 0;
+ else
+ f = f->sibling;
+ for(m = 0; m < n; fp->dri++){
+ if(f == nil)
+ break;
+ dir.d = f->d;
+ dsz = convD2M(&dir.d, (uchar*)d, n-m);
+ m += dsz;
+ d += dsz;
+ f = f->sibling;
+ }
+
+ return m;
+}
+
+static char*
+nilconv(char *s)
+{
+ if(s != nil && s[0] == '\0')
+ return nil;
+ return s;
+}
+
+static Styxfile *
+newfile(Styxserver *server, Styxfile *parent, int isdir, Path qid, char *name, int mode, char *owner)
+{
+ Styxfile *file;
+ Dir d;
+ int h;
+
+ if(qid == -1)
+ qid = server->qidgen++;
+ file = styxfindfile(server, qid);
+ if(file != nil)
+ return nil;
+ if(parent != nil){
+ for(file = parent->child; file != nil; file = file->sibling)
+ if(strcmp(name, file->d.name) == 0)
+ return nil;
+ }
+ file = (Styxfile *)styxmalloc(sizeof(Styxfile));
+ file->parent = parent;
+ file->child = nil;
+ h = hash(qid);
+ file->next = server->ftab[h];
+ server->ftab[h] = file;
+ if(parent){
+ file->sibling = parent->child;
+ parent->child = file;
+ }else
+ file->sibling = nil;
+
+ d.type = 'X';
+ d.dev = 'x';
+ d.qid.path = qid;
+ d.qid.type = 0;
+ d.qid.vers = 0;
+ d.mode = mode;
+ d.atime = time(0);
+ d.mtime = boottime;
+ d.length = 0;
+ d.name = strdup(name);
+ d.uid = strdup(owner);
+ d.gid = strdup(eve);
+ d.muid = "";
+
+ if(isdir){
+ d.qid.type |= QTDIR;
+ d.mode |= DMDIR;
+ }
+ else{
+ d.qid.type &= ~QTDIR;
+ d.mode &= ~DMDIR;
+ }
+
+ file->d = d;
+ file->ref = 0;
+ file->open = 0;
+ if(Debug)
+ fprint(2, "New file %s Qid=%llx\n", name, qid);
+ return file;
+}
+
+static void
+run(Client *c)
+{
+ Fcall f;
+ Fid *fp, *nfp;
+ int i, open, mode;
+ char ebuf[EMSGLEN];
+ Walkqid *wq;
+ Styxfile *file;
+ Dir dir;
+ Qid qid;
+ Styxops *ops;
+ char strs[128];
+
+ ebuf[0] = 0;
+ if(rd(c, &f) <= 0)
+ return;
+ if(f.type == Tflush){
+ flushtag(f.oldtag);
+ f.type = Rflush;
+ wr(c, &f);
+ return;
+ }
+ ops = c->server->ops;
+ file = nil;
+ fp = findfid(c, f.fid);
+ if(f.type != Tversion && f.type != Tauth && f.type != Tattach){
+ if(fp == nil){
+ f.type = Rerror;
+ f.ename = Enofid;
+ wr(c, &f);
+ return;
+ }
+ else{
+ file = styxfindfile(c->server, fp->qid.path);
+ if(c->server->needfile && file == nil){
+ f.type = Rerror;
+ f.ename = Enonexist;
+ wr(c, &f);
+ return;
+ }
+ }
+ }
+ /* if(fp == nil) fprint(2, "fid not found for %d\n", f.fid); */
+ switch(f.type){
+ case Twalk:
+ if(Debug){
+ fprint(2, "Twalk %d %d", f.fid, f.newfid);
+ for(i = 0; i < f.nwname; i++)
+ fprint(2, " %s", f.wname[i]);
+ fprint(2, "\n");
+ }
+ nfp = findfid(c, f.newfid);
+ f.type = Rerror;
+ if(nfp){
+ deletefid(c, nfp);
+ nfp = nil;
+ }
+ if(nfp){
+ f.ename = "fid in use";
+ if(Debug) fprint(2, "walk: %s\n", f.ename);
+ wr(c, &f);
+ break;
+ }else if(fp->open){
+ f.ename = "can't clone";
+ wr(c, &f);
+ break;
+ }
+ if(f.newfid != f.fid)
+ nfp = fidclone(fp, f.newfid);
+ else
+ nfp = fp;
+ if((wq = devwalk(c, file, fp, nfp, f.wname, f.nwname, &f.ename)) == nil){
+ if(nfp != fp)
+ deletefid(c, nfp);
+ f.type = Rerror;
+ }else{
+ if(nfp != fp){
+ if(wq->nqid != f.nwname)
+ deletefid(c, nfp);
+ }
+ f.type = Rwalk;
+ f.nwqid = wq->nqid;
+ for(i = 0; i < wq->nqid; i++)
+ f.wqid[i] = wq->qid[i];
+ styxfree(wq);
+ }
+ wr(c, &f);
+ break;
+ case Topen:
+ if(Debug)
+ fprint(2, "Topen %d\n", f.fid);
+ f.ename = nil;
+ if(fp->open)
+ f.ename = Eopen;
+ else if((fp->qid.type&QTDIR) && (f.mode&(OWRITE|OTRUNC|ORCLOSE)))
+ f.ename = Eperm;
+ else if(file != nil && !styxperm(file, c->uname, f.mode))
+ f.ename = Eperm;
+ else if((f.mode&ORCLOSE) && file != nil && file->parent != nil && !styxperm(file->parent, c->uname, OWRITE))
+ f.ename = Eperm;
+ if(f.ename != nil){
+ f.type = Rerror;
+ wr(c, &f);
+ break;
+ }
+ f.ename = Enonexist;
+ decreff(fp);
+ if(ops->open == nil || (f.ename = ops->open(&fp->qid, f.mode)) == nil){
+ f.type = Ropen;
+ f.qid = fp->qid;
+ fp->mode = f.mode;
+ fp->open = 1;
+ fp->offset = 0;
+ incopen(fp);
+ }
+ else
+ f.type = Rerror;
+ increff(fp);
+ wr(c, &f);
+ break;
+ case Tcreate:
+ if(Debug)
+ fprint(2, "Tcreate %d %s\n", f.fid, f.name);
+ f.ename = nil;
+ if(fp->open)
+ f.ename = Eopen;
+ else if(!(fp->qid.type&QTDIR))
+ f.ename = Enotdir;
+ else if((f.perm&DMDIR) && (f.mode&(OWRITE|OTRUNC|ORCLOSE)))
+ f.ename = Eperm;
+ else if(file != nil && !styxperm(file, c->uname, OWRITE))
+ f.ename = Eperm;
+ if(f.ename != nil){
+ f.type = Rerror;
+ wr(c, &f);
+ break;
+ }
+ f.ename = Eperm;
+ decreff(fp);
+ if(file != nil){
+ if(f.perm&DMDIR)
+ f.perm = (f.perm&~0777) | (file->d.mode&f.perm&0777) | DMDIR;
+ else
+ f.perm = (f.perm&(~0777|0111)) | (file->d.mode&f.perm&0666);
+ }
+ if(ops->create && (f.ename = ops->create(&fp->qid, f.name, f.perm, f.mode)) == nil){
+ f.type = Rcreate;
+ f.qid = fp->qid;
+ fp->mode = f.mode;
+ fp->open = 1;
+ fp->offset = 0;
+ incopen(fp);
+ }
+ else
+ f.type = Rerror;
+ increff(fp);
+ wr(c, &f);
+ break;
+ case Tread:
+ if(Debug)
+ fprint(2, "Tread %d\n", f.fid);
+ if(!fp->open){
+ f.type = Rerror;
+ f.ename = Ebadfid;
+ wr(c, &f);
+ break;
+ }
+ if(fp->qid.type&QTDIR || (file != nil && file->d.qid.type&QTDIR)){
+ f.type = Rread;
+ if(file == nil){
+ f.ename = Eperm;
+ if(ops->read && (f.ename = ops->read(fp->qid, c->data, (ulong*)(&f.count), fp->dri)) == nil){
+ f.data = c->data;
+ }
+ else
+ f.type = Rerror;
+ }
+ else{
+ f.count = devdirread(fp, file, c->data, f.count);
+ f.data = c->data;
+ }
+ }else{
+ f.ename = Eperm;
+ f.type = Rerror;
+ if(ops->read && (f.ename = ops->read(fp->qid, c->data, (ulong*)(&f.count), f.offset)) == nil){
+ f.type = Rread;
+ f.data = c->data;
+ }
+ }
+ wr(c, &f);
+ break;
+ case Twrite:
+ if(Debug)
+ fprint(2, "Twrite %d\n", f.fid);
+ if(!fp->open){
+ f.type = Rerror;
+ f.ename = Ebadfid;
+ wr(c, &f);
+ break;
+ }
+ f.ename = Eperm;
+ f.type = Rerror;
+ if(ops->write && (f.ename = ops->write(fp->qid, f.data, (ulong*)(&f.count), f.offset)) == nil){
+ f.type = Rwrite;
+ }
+ wr(c, &f);
+ break;
+ case Tclunk:
+ if(Debug)
+ fprint(2, "Tclunk %d\n", f.fid);
+ open = fp->open;
+ mode = fp->mode;
+ qid = fp->qid;
+ deletefid(c, fp);
+ f.type = Rclunk;
+ if(open && ops->close && (f.ename = ops->close(qid, mode)) != nil)
+ f.type = Rerror;
+ wr(c, &f);
+ break;
+ case Tremove:
+ if(Debug)
+ fprint(2, "Tremove %d\n", f.fid);
+ if(file != nil && file->parent != nil && !styxperm(file->parent, c->uname, OWRITE)){
+ f.type = Rerror;
+ f.ename = Eperm;
+ deletefid(c, fp);
+ wr(c, &f);
+ break;
+ }
+ f.ename = Eperm;
+ if(ops->remove && (f.ename = ops->remove(fp->qid)) == nil)
+ f.type = Rremove;
+ else
+ f.type = Rerror;
+ deletefid(c, fp);
+ wr(c, &f);
+ break;
+ case Tstat:
+ if(Debug)
+ fprint(2, "Tstat %d qid=%llx\n", f.fid, fp->qid.path);
+ f.stat = styxmalloc(MAXSTAT);
+ f.ename = "stat error";
+ if(ops->stat == nil && file != nil){
+ f.type = Rstat;
+ f.nstat = convD2M(&file->d, f.stat, MAXSTAT);
+ }
+ else if(ops->stat && (f.ename = ops->stat(fp->qid, &dir)) == nil){
+ f.type = Rstat;
+ f.nstat = convD2M(&dir, f.stat, MAXSTAT);
+ }
+ else
+ f.type = Rerror;
+ wr(c, &f);
+ styxfree(f.stat);
+ break;
+ case Twstat:
+ if(Debug)
+ fprint(2, "Twstat %d\n", f.fid);
+ f.ename = Eperm;
+ convM2D(f.stat, f.nstat, &dir, strs);
+ dir.name = nilconv(dir.name);
+ dir.uid = nilconv(dir.uid);
+ dir.gid = nilconv(dir.gid);
+ dir.muid = nilconv(dir.muid);
+ if(ops->wstat && (f.ename = ops->wstat(fp->qid, &dir)) == nil)
+ f.type = Rwstat;
+ else
+ f.type = Rerror;
+ wr(c, &f);
+ break;
+ case Tversion:
+ if(Debug)
+ fprint(2, "Tversion\n");
+ f.type = Rversion;
+ f.tag = NOTAG;
+ wr(c, &f);
+ break;
+ case Tauth:
+ if(Debug)
+ fprint(2, "Tauth\n");
+ f.type = Rauth;
+ wr(c, &f);
+ break;
+ case Tattach:
+ if(Debug)
+ fprint(2, "Tattach %d %s\n", f.fid, f.uname[0] ? f.uname : c->uname);
+ if(fp){
+ f.type = Rerror;
+ f.ename = "fid in use";
+ }else{
+ Qid q;
+
+ if(f.uname[0]){
+ free(c->uname);
+ c->uname = strdup(f.uname);
+ }
+ if(f.aname[0]){
+ free(c->aname);
+ c->aname = strdup(f.aname);
+ }
+ q.path = Qroot;
+ q.type = QTDIR;
+ q.vers = 0;
+ fp = newfid(c, f.fid, q);
+ f.type = Rattach;
+ f.fid = fp->fid;
+ f.qid = q;
+ if(ops->attach && (f.ename = ops->attach(c->uname, c->aname)) != nil)
+ f.type = Rerror;
+ }
+ wr(c, &f);
+ break;
+ }
+}
+
+char *
+styxinit(Styxserver *server, Styxops *ops, char *port, int perm, int needfile)
+{
+ int i;
+
+ if(Debug)
+ fprint(2, "Initialising Styx server on port %s\n", port);
+ if(perm == -1)
+ perm = 0555;
+ server->ops = ops;
+ server->clients = nil;
+ server->root = nil;
+ server->ftab = (Styxfile**)malloc(TABSZ*sizeof(Styxfile*));
+ for(i = 0; i < TABSZ; i++)
+ server->ftab[i] = nil;
+ server->qidgen = Qroot+1;
+ if(styxinitsocket() < 0)
+ return "styxinitsocket failed";
+ server->connfd = styxannounce(server, port);
+ if(server->connfd < 0)
+ return "can't announce on network port";
+ styxinitwait(server);
+ server->root = newfile(server, nil, 1, Qroot, "/", perm|DMDIR, eve);
+ server->needfile = needfile;
+ return nil;
+}
+
+char*
+styxend(Styxserver *server)
+{
+ USED(server);
+ styxendsocket();
+ return nil;
+}
+
+char *
+styxwait(Styxserver *server)
+{
+ return styxwaitmsg(server);
+}
+
+char *
+styxprocess(Styxserver *server)
+{
+ Client *c;
+ int s;
+
+ if(styxnewcall(server)){
+ s = styxaccept(server);
+ if(s >= 0){
+ newclient(server, s);
+ styxnewclient(server, s);
+ }
+ }
+ for(c = server->clients; c != nil; ){
+ Client *next = c->next;
+
+ server->curc = c;
+ if(c->fd >= 0 && styxnewmsg(server, c->fd))
+ c->state |= CRECV;
+ if(c->state&(CNREAD|CRECV)){
+ if(c->state&CDISC){
+ styxfreeclient(server, c->fd);
+ freeclient(c);
+ }else
+ do
+ run(c);
+ while(c->state&CNREAD);
+ }
+ c = next;
+ }
+
+ return nil;
+}
+
+Client*
+styxclient(Styxserver *server)
+{
+ return server->curc;
+}
+
+Styxfile*
+styxaddfile(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner)
+{
+ Styxfile *f, *parent;
+
+ parent = styxfindfile(server, pqid);
+ if(parent == nil || (parent->d.qid.type&QTDIR) == 0)
+ return nil;
+ f = newfile(server, parent, 0, qid, name, mode, owner);
+ return f;
+}
+
+Styxfile*
+styxadddir(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner)
+{
+ Styxfile *f, *parent;
+
+ parent = styxfindfile(server, pqid);
+ if(parent == nil || (parent->d.qid.type&QTDIR) == 0)
+ return nil;
+ f = newfile(server, parent, 1, qid, name, mode|DMDIR, owner);
+ return f;
+}
+
+long
+styxreadstr(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;
+}
+
+Qid
+styxqid(int path, int isdir)
+{
+ Qid q;
+
+ q.path = path;
+ q.vers = 0;
+ if(isdir)
+ q.type = QTDIR;
+ else
+ q.type = 0;
+ return q;
+}
diff --git a/tools/libstyx/styxserver.h b/tools/libstyx/styxserver.h
new file mode 100644
index 00000000..3aac9eee
--- /dev/null
+++ b/tools/libstyx/styxserver.h
@@ -0,0 +1,97 @@
+
+#define Qroot 0
+
+#define MSGMAX ((((8192+128)*2)+3) & ~3)
+
+extern char Enomem[]; /* out of memory */
+extern char Eperm[]; /* permission denied */
+extern char Enodev[]; /* no free devices */
+extern char Ehungup[]; /* i/o on hungup channel */
+extern char Eexist[]; /* file exists */
+extern char Enonexist[]; /* file does not exist */
+extern char Ebadcmd[]; /* bad command */
+extern char Ebadarg[]; /* bad arguments */
+
+typedef uvlong Path;
+typedef struct Styxserver Styxserver;
+typedef struct Styxops Styxops;
+typedef struct Styxfile Styxfile;
+typedef struct Client Client;
+typedef struct Fid Fid;
+
+struct Styxserver
+{
+ Styxops *ops;
+ Path qidgen;
+ int connfd;
+ int needfile;
+ Client *clients;
+ Client *curc;
+ Styxfile *root;
+ Styxfile **ftab;
+ void *priv; /* private */
+};
+
+struct Client
+{
+ Styxserver *server;
+ Client *next;
+ int fd;
+ char msg[MSGMAX];
+ uint nread; /* valid bytes in msg (including nc)*/
+ int nc; /* bytes consumed from front of msg by convM2S */
+ char data[MSGMAX]; /* Tread/Rread data */
+ int state;
+ Fid *fids;
+ char *uname; /* uid */
+ char *aname; /* attach name */
+ void *u;
+};
+
+struct Styxops
+{
+ char *(*newclient)(Client *c);
+ char *(*freeclient)(Client *c);
+
+ char *(*attach)(char *uname, char *aname);
+ char *(*walk)(Qid *qid, char *name);
+ char *(*open)(Qid *qid, int mode);
+ char *(*create)(Qid *qid, char *name, int perm, int mode);
+ char *(*read)(Qid qid, char *buf, ulong *n, vlong offset);
+ char *(*write)(Qid qid, char *buf, ulong *n, vlong offset);
+ char *(*close)(Qid qid, int mode);
+ char *(*remove)(Qid qid);
+ char *(*stat)(Qid qid, Dir *d);
+ char *(*wstat)(Qid qid, Dir *d);
+};
+
+struct Styxfile
+{
+ Dir d;
+ Styxfile *parent;
+ Styxfile *child;
+ Styxfile *sibling;
+ Styxfile *next;
+ int ref;
+ int open;
+ void *u;
+};
+
+char *styxinit(Styxserver *server, Styxops *ops, char *port, int perm, int needfile);
+char *styxwait(Styxserver *server);
+char *styxprocess(Styxserver *server);
+char *styxend(Styxserver *server);
+
+Client *styxclient(Styxserver *server);
+
+Styxfile *styxaddfile(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner);
+Styxfile *styxadddir(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner);
+int styxrmfile(Styxserver *server, Path qid);
+Styxfile *styxfindfile(Styxserver *server, Path qid);
+
+int styxperm(Styxfile *file, char *uid, int mode);
+long styxreadstr(ulong off, char *buf, ulong n, char *str);
+Qid styxqid(int path, int isdir);
+void *styxmalloc(int n);
+void styxfree(void *p);
+void styxdebug(void);
diff --git a/tools/mkfile b/tools/mkfile
new file mode 100644
index 00000000..3cfcf45a
--- /dev/null
+++ b/tools/mkfile
@@ -0,0 +1,7 @@
+<../mkconfig
+
+DIRS=\
+ libstyx\
+ styxtest\
+
+<$ROOT/mkfiles/mksubdirs
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);
+}
diff --git a/tools/styxtest/mkfile b/tools/styxtest/mkfile
new file mode 100755
index 00000000..360439a6
--- /dev/null
+++ b/tools/styxtest/mkfile
@@ -0,0 +1,23 @@
+<../../mkconfig
+
+TARG=styxtest
+
+OFILES=\
+ styxtest.$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/styxtest/mkfile-FreeBSD b/tools/styxtest/mkfile-FreeBSD
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/styxtest/mkfile-FreeBSD
diff --git a/tools/styxtest/mkfile-Irix b/tools/styxtest/mkfile-Irix
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/styxtest/mkfile-Irix
diff --git a/tools/styxtest/mkfile-Linux b/tools/styxtest/mkfile-Linux
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/styxtest/mkfile-Linux
diff --git a/tools/styxtest/mkfile-MacOSX b/tools/styxtest/mkfile-MacOSX
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/styxtest/mkfile-MacOSX
diff --git a/tools/styxtest/mkfile-Nt b/tools/styxtest/mkfile-Nt
new file mode 100644
index 00000000..bfa815e6
--- /dev/null
+++ b/tools/styxtest/mkfile-Nt
@@ -0,0 +1 @@
+SYSLIBS= wsock32.lib $SYSLIBS
diff --git a/tools/styxtest/mkfile-Plan9 b/tools/styxtest/mkfile-Plan9
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/styxtest/mkfile-Plan9
diff --git a/tools/styxtest/mkfile-Solaris b/tools/styxtest/mkfile-Solaris
new file mode 100644
index 00000000..818482a8
--- /dev/null
+++ b/tools/styxtest/mkfile-Solaris
@@ -0,0 +1 @@
+SYSLIBS=$EMULIBS
diff --git a/tools/styxtest/styxtest.c b/tools/styxtest/styxtest.c
new file mode 100644
index 00000000..a6cca89f
--- /dev/null
+++ b/tools/styxtest/styxtest.c
@@ -0,0 +1,198 @@
+#include <lib9.h>
+#include "styxserver.h"
+
+/*
+ * An in-memory file server
+ * allowing truncation, removal on closure, wstat and
+ * all other file operations
+ */
+
+char *fsremove(Qid);
+
+Styxserver *server;
+
+char*
+fsopen(Qid *qid, int mode)
+{
+ Styxfile *f;
+
+ f = styxfindfile(server, qid->path);
+ if(mode&OTRUNC){ /* truncate on open */
+ styxfree(f->u);
+ f->u = nil;
+ f->d.length = 0;
+ }
+ return nil;
+}
+
+char*
+fsclose(Qid qid, int mode)
+{
+ if(mode&ORCLOSE) /* remove on close */
+ return fsremove(qid);
+ return nil;
+}
+
+char *
+fscreate(Qid *qid, char *name, int perm, int mode)
+{
+ int isdir;
+ Styxfile *f;
+
+ USED(mode);
+ isdir = perm&DMDIR;
+ if(isdir)
+ f = styxadddir(server, qid->path, -1, name, perm, "inferno");
+ else
+ f = styxaddfile(server, qid->path, -1, name, perm, "inferno");
+ if(f == nil)
+ return Eexist;
+ f->u = nil;
+ f->d.length = 0;
+ *qid = f->d.qid;
+ return nil;
+}
+
+char *
+fsremove(Qid qid)
+{
+ Styxfile *f;
+
+ f = styxfindfile(server, qid.path);
+ if((f->d.qid.type&QTDIR) && f->child != nil)
+ return "directory not empty";
+ styxfree(f->u);
+ styxrmfile(server, qid.path);
+ return nil;
+}
+
+char *
+fsread(Qid qid, char *buf, ulong *n, vlong off)
+{
+ int m;
+ Styxfile *f;
+
+ f = styxfindfile(server, qid.path);
+ m = f->d.length;
+ if(off >= m)
+ *n = 0;
+ else{
+ if(off + *n > m)
+ *n = m-off;
+ memmove(buf, (char*)f->u+off, *n);
+ }
+ return nil;
+}
+
+char*
+fswrite(Qid qid, char *buf, ulong *n, vlong off)
+{
+ Styxfile *f;
+ vlong m, p;
+ char *u;
+
+ f = styxfindfile(server, qid.path);
+ m = f->d.length;
+ p = off + *n;
+ if(p > m){ /* just grab a larger piece of memory */
+ u = styxmalloc(p);
+ if(u == nil)
+ return "out of memory";
+ memset(u, 0, p);
+ memmove(u, f->u, m);
+ styxfree(f->u);
+ f->u = u;
+ f->d.length = p;
+ }
+ memmove((char*)f->u+off, buf, *n);
+ return nil;
+}
+
+char*
+fswstat(Qid qid, Dir *d)
+{
+ Styxfile *f, *tf;
+ Client *c;
+ int owner;
+
+ /* the most complicated operation when fully allowed */
+
+ c = styxclient(server);
+ f = styxfindfile(server, qid.path);
+ owner = strcmp(c->uname, f->d.uid) == 0;
+ if(d->name != nil && strcmp(d->name, f->d.name) != 0){
+ /* need write permission in parent directory */
+ if(!styxperm(f->parent, c->uname, OWRITE))
+ return Eperm;
+ if((tf = styxaddfile(server, f->parent->d.qid.path, -1, d->name, 0, "")) == nil){
+ /* file with same name exists */
+ return Eexist;
+ }
+ else{
+ /* undo above addfile */
+ styxrmfile(server, tf->d.qid.path);
+ }
+ /* ok to change name now */
+ styxfree(f->d.name);
+ f->d.name = strdup(d->name);
+ }
+ if(d->uid != nil && strcmp(d->uid, f->d.uid) != 0){
+ if(!owner)
+ return Eperm;
+ styxfree(f->d.uid);
+ f->d.uid = strdup(d->uid);
+ }
+ if(d->gid != nil && strcmp(d->gid, f->d.gid) != 0){
+ if(!owner)
+ return Eperm;
+ styxfree(f->d.gid);
+ f->d.gid = strdup(d->gid);
+ }
+ if(d->mode != ~0 && d->mode != f->d.mode){
+ if(!owner)
+ return Eperm;
+ if(d->mode&DMDIR != f->d.mode&DMDIR)
+ return Eperm; /* cannot change file->directory or vice-verse */
+ f->d.mode = d->mode;
+ }
+ if(d->mtime != ~0 && d->mtime != f->d.mtime){
+ if(!owner)
+ return Eperm;
+ f->d.mtime = d->mtime;
+ }
+ /* all other file attributes cannot be changed by wstat */
+ return nil;
+}
+
+Styxops ops = {
+ nil, /* newclient */
+ nil, /* freeclient */
+
+ nil, /* attach */
+ nil, /* walk */
+ fsopen, /* open */
+ fscreate, /* create */
+ fsread, /* read */
+ fswrite, /* write */
+ fsclose, /* close */
+ fsremove, /* remove */
+ nil, /* stat */
+ fswstat, /* wstat */
+};
+
+main(int argc, char **argv)
+{
+ Styxserver s;
+
+ USED(argc);
+ USED(argv);
+ server = &s;
+ styxdebug();
+ styxinit(&s, &ops, "6701", 0777, 1);
+ for(;;){
+ styxwait(&s);
+ styxprocess(&s);
+ }
+ return 0;
+}
+
diff --git a/tools/styxtest/styxtest0.c b/tools/styxtest/styxtest0.c
new file mode 100644
index 00000000..e26faf4b
--- /dev/null
+++ b/tools/styxtest/styxtest0.c
@@ -0,0 +1,95 @@
+#include <lib9.h>
+#include "styxserver.h"
+
+int nq;
+Styxserver *server;
+
+void
+myinit(Styxserver *s)
+{
+ styxaddfile(s, Qroot, 1, "fred", 0664, "inferno");
+ styxaddfile(s, Qroot, 2, "joe", 0664, "inferno");
+ styxadddir(s, Qroot, 3, "adir", 0775, "inferno");
+ styxaddfile(s, 3, 4, "bill", 0664, "inferno");
+ styxadddir(s, Qroot, 5, "new", 0775, "inferno");
+ styxadddir(s, 5, 6, "cdir", 0775, "inferno");
+ styxaddfile(s, 6, 7, "cfile", 0664, "inferno");
+ nq = 8;
+}
+
+char *
+mycreate(Qid *qid, char *name, int perm, int mode)
+{
+ int isdir;
+ Styxfile *f;
+
+ USED(mode);
+ isdir = perm&DMDIR;
+ if(isdir)
+ f = styxadddir(server, qid->path, nq++, name , perm, "inferno");
+ else
+ f = styxaddfile(server, qid->path, nq++, name, perm, "inferno");
+ if(f == nil)
+ return Eexist;
+ *qid = f->d.qid;
+ return nil;
+}
+
+char *
+myremove(Qid qid)
+{
+ Styxfile *f;
+
+ f = styxfindfile(server, qid.path);
+ if(f != nil && (f->d.qid.type&QTDIR) && f->child != nil)
+ return "directory not empty";
+
+ if(styxrmfile(server, qid.path) < 0)
+ return Enonexist;
+ return nil;
+}
+
+char *
+myread(Qid qid, char *d, ulong *n, vlong offset)
+{
+ if(qid.path != 1){
+ *n = 0;
+ return nil;
+ }
+ *n = styxreadstr(offset, d, *n, "abcdefghijklmn");
+ return nil;
+}
+
+Styxops ops = {
+ nil, /* newclient */
+ nil, /* freeclient */
+
+ nil, /* attach */
+ nil, /* walk */
+ nil, /* open */
+ mycreate, /* create */
+ myread, /* read */
+ nil, /* write */
+ nil, /* close */
+ myremove, /* remove */
+ nil, /* stat */
+ nil, /* wstat */
+};
+
+main(int argc, char **argv)
+{
+ Styxserver s;
+
+ USED(argc);
+ USED(argv);
+ server = &s;
+ styxdebug();
+ styxinit(&s, &ops, "6701", 0555, 0);
+ myinit(&s);
+ for(;;){
+ styxwait(&s);
+ styxprocess(&s);
+ }
+ return 0;
+}
+