From 46439007cf417cbd9ac8049bb4122c890097a0fa Mon Sep 17 00:00:00 2001 From: "Charles.Forsyth" Date: Fri, 22 Dec 2006 20:52:35 +0000 Subject: 20060303-partial --- tools/NOTICE | 23 + tools/db/infdb.c | 998 +++++++++++++++++++++++++++++++++++ tools/db/mkfile | 13 + tools/libstyx/Nt.c | 172 +++++++ tools/libstyx/Plan9.c | 314 +++++++++++ tools/libstyx/Posix.c | 164 ++++++ tools/libstyx/mkfile | 11 + tools/libstyx/styxaux.h | 14 + tools/libstyx/styxserver.c | 1067 ++++++++++++++++++++++++++++++++++++++ tools/libstyx/styxserver.h | 97 ++++ tools/mkfile | 7 + tools/odbc/mkfile | 19 + tools/odbc/mkfile-Linux | 1 + tools/odbc/mkfile-MacOSX | 1 + tools/odbc/mkfile-Nt | 2 + tools/odbc/mkfile-Plan9 | 0 tools/odbc/mkfile-Solaris | 1 + tools/odbc/odbc.c | 1146 +++++++++++++++++++++++++++++++++++++++++ tools/styxtest/mkfile | 23 + tools/styxtest/mkfile-FreeBSD | 0 tools/styxtest/mkfile-Irix | 0 tools/styxtest/mkfile-Linux | 0 tools/styxtest/mkfile-MacOSX | 0 tools/styxtest/mkfile-Nt | 1 + tools/styxtest/mkfile-Plan9 | 0 tools/styxtest/mkfile-Solaris | 1 + tools/styxtest/styxtest.c | 198 +++++++ tools/styxtest/styxtest0.c | 95 ++++ 28 files changed, 4368 insertions(+) create mode 100644 tools/NOTICE create mode 100644 tools/db/infdb.c create mode 100644 tools/db/mkfile create mode 100644 tools/libstyx/Nt.c create mode 100644 tools/libstyx/Plan9.c create mode 100644 tools/libstyx/Posix.c create mode 100644 tools/libstyx/mkfile create mode 100644 tools/libstyx/styxaux.h create mode 100644 tools/libstyx/styxserver.c create mode 100644 tools/libstyx/styxserver.h create mode 100644 tools/mkfile create mode 100755 tools/odbc/mkfile create mode 100644 tools/odbc/mkfile-Linux create mode 100644 tools/odbc/mkfile-MacOSX create mode 100644 tools/odbc/mkfile-Nt create mode 100644 tools/odbc/mkfile-Plan9 create mode 100644 tools/odbc/mkfile-Solaris create mode 100755 tools/odbc/odbc.c create mode 100755 tools/styxtest/mkfile create mode 100644 tools/styxtest/mkfile-FreeBSD create mode 100644 tools/styxtest/mkfile-Irix create mode 100644 tools/styxtest/mkfile-Linux create mode 100644 tools/styxtest/mkfile-MacOSX create mode 100644 tools/styxtest/mkfile-Nt create mode 100644 tools/styxtest/mkfile-Plan9 create mode 100644 tools/styxtest/mkfile-Solaris create mode 100644 tools/styxtest/styxtest.c create mode 100644 tools/styxtest/styxtest0.c (limited to 'tools') 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 +#include +#include +#include +#ifdef WIN32 +#include +#include +#include +#else +#include +#include +#include +#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 +#include +#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 +#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 +#include +#include +#include +#include + +#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 +#include +#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 + + +#endif +#include +#include +#include "styxserver.h" +/* #include */ + +#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 +#include + +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 && ic.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; incols; 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; incols; 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; incols; 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; incols; 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; is; + 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; incols; 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; incols; 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 + + +#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 +#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; +} + -- cgit v1.2.3