wmii/libixp/request.c

395 lines
8.7 KiB
C

/*
* (C)opyright MMVI Kris Maglione <fbsdaemon at gmail dot com>
* See LICENSE file for license details.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include "ixp.h"
static void ixp_handle_req(P9Req *r);
/* We use string literals rather than arrays here because
* they're allocated in a readonly section */
static char
*Eduptag = "tag in use",
*Edupfid = "fid in use",
*Enofunc = "function not implemented",
*Ebotch = "9P protocol botch",
*Enofile = "file does not exist",
*Enofid = "fid does not exist",
*Enotag = "tag does not exist",
*Enotdir = "not a directory",
*Einterrupted = "interrupted",
*Eisdir = "cannot perform operation on a directory";
enum { TAG_BUCKETS = 64,
FID_BUCKETS = 64 };
struct P9Conn {
Intmap tagmap;
void *taghash[TAG_BUCKETS];
Intmap fidmap;
void *fidhash[FID_BUCKETS];
P9Srv *srv;
IXPConn *conn;
unsigned int msize;
unsigned char *buf;
unsigned int ref;
};
static void
free_p9conn(P9Conn *pc) {
free(pc->buf);
free(pc);
}
static void *
createfid(Intmap *map, int fid, P9Conn *pc) {
Fid *f = cext_emallocz(sizeof(Fid));
f->fid = fid;
f->omode = -1;
f->map = map;
f->conn = pc;
if(caninsertkey(map, fid, f))
return f;
free(f);
return nil;
}
static int
destroyfid(P9Conn *pc, unsigned long fid) {
Fid *f;
if(!(f = deletekey(&pc->fidmap, fid)))
return 0;
if(pc->srv->freefid)
pc->srv->freefid(f);
free(f);
return 1;
}
void
ixp_server_handle_fcall(IXPConn *c)
{
Fcall fcall = {0};
P9Conn *pc = c->aux;
P9Req *req;
unsigned int msize;
char *errstr = nil;
if(!(msize = ixp_recv_message(c->fd, pc->buf, pc->msize, &errstr)))
goto Fail;
if(!(msize = ixp_msg2fcall(&fcall, pc->buf, IXP_MAX_MSG)))
goto Fail;
req = cext_emallocz(sizeof(P9Req));
req->conn = pc;
req->ifcall = fcall;
pc->conn = c;
if(lookupkey(&pc->tagmap, fcall.tag))
return respond(req, Eduptag);
insertkey(&pc->tagmap, fcall.tag, req);
return ixp_handle_req(req);
Fail:
ixp_server_close_conn(c);
}
static void
ixp_handle_req(P9Req *r)
{
P9Conn *pc = r->conn;
P9Srv *srv = pc->srv;
switch(r->ifcall.type) {
default:
respond(r, Enofunc);
break;
case TVERSION:
if(!strncmp(r->ifcall.version, "9P", 3)) {
r->ofcall.version = "9P";
}else
if(!strncmp(r->ifcall.version, "9P2000", 7)) {
r->ofcall.version = "9P2000";
}else{
r->ofcall.version = "unknown";
}
r->ofcall.msize = r->ifcall.msize;
respond(r, nil);
break;
case TATTACH:
if(!(r->fid = createfid(&pc->fidmap, r->ifcall.fid, pc)))
return respond(r, Edupfid);
/* attach is a required function */
srv->attach(r);
break;
case TCLUNK:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(!srv->clunk)
return respond(r, nil);
srv->clunk(r);
break;
case TFLUSH:
if(!(r->oldreq = lookupkey(&pc->tagmap, r->ifcall.oldtag)))
return respond(r, Enotag);
if(!srv->flush)
return respond(r, Enofunc);
srv->flush(r);
break;
case TCREATE:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(r->fid->omode != -1)
return respond(r, Ebotch);
if(!(r->fid->qid.type&P9QTDIR))
return respond(r, Enotdir);
if(!pc->srv->create)
return respond(r, Enofunc);
pc->srv->create(r);
break;
case TOPEN:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if((r->fid->qid.type&P9QTDIR) && (r->ifcall.mode|P9ORCLOSE) != (P9OREAD|P9ORCLOSE))
return respond(r, Eisdir);
r->ofcall.qid = r->fid->qid;
if(!pc->srv->open)
return respond(r, Enofunc);
pc->srv->open(r);
break;
case TREAD:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(r->fid->omode == -1 || r->fid->omode == P9OWRITE)
return respond(r, Ebotch);
if(!pc->srv->read)
return respond(r, Enofunc);
pc->srv->read(r);
break;
case TREMOVE:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(!pc->srv->remove)
return respond(r, Enofunc);
pc->srv->remove(r);
break;
case TSTAT:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(!pc->srv->stat)
return respond(r, Enofunc);
pc->srv->stat(r);
break;
case TWALK:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if(r->fid->omode != -1)
return respond(r, "cannot walk from an open fid");
if(r->ifcall.nwname && !(r->fid->qid.type&P9QTDIR))
return respond(r, Enotdir);
if((r->ifcall.fid != r->ifcall.newfid)) {
if(!(r->newfid = createfid(&pc->fidmap, r->ifcall.newfid, pc)))
return respond(r, Edupfid);
}else
r->newfid = r->fid;
if(!pc->srv->walk)
return respond(r, Enofunc);
pc->srv->walk(r);
break;
case TWRITE:
if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.fid)))
return respond(r, Enofid);
if((r->fid->omode&3) != P9OWRITE && (r->fid->omode&3) != P9ORDWR)
return respond(r, "write on fid not opened for writing");
if(!pc->srv->write)
return respond(r, Enofunc);
pc->srv->write(r);
break;
/* Still to be implemented: flush, wstat, auth */
}
}
void
respond(P9Req *r, char *error) {
P9Conn *pc = r->conn;
switch(r->ifcall.type) {
default:
if(!error)
cext_assert(!"Respond called on unsupported fcall type");
break;
case TVERSION:
cext_assert(!error);
free(r->ifcall.version);
pc->msize = (r->ofcall.msize < IXP_MAX_MSG) ? r->ofcall.msize : IXP_MAX_MSG;
free(pc->buf);
pc->buf = cext_emallocz(r->ofcall.msize);
break;
case TATTACH:
if(error)
destroyfid(pc, r->fid->fid);
free(r->ifcall.uname);
free(r->ifcall.aname);
break;
case TOPEN:
case TCREATE:
if(!error) {
r->fid->omode = r->ifcall.mode;
r->fid->qid = r->ofcall.qid;
}
free(r->ifcall.name);
r->ofcall.iounit = pc->msize - sizeof(unsigned long);
break;
case TWALK:
if(error || r->ofcall.nwqid < r->ifcall.nwname) {
if(r->ifcall.fid != r->ifcall.newfid && r->newfid)
destroyfid(pc, r->newfid->fid);
if(!error && r->ofcall.nwqid == 0)
error = Enofile;
}else{
if(r->ofcall.nwqid == 0)
r->newfid->qid = r->fid->qid;
else
r->newfid->qid = r->ofcall.wqid[r->ofcall.nwqid-1];
}
free(*r->ifcall.wname);
break;
case TWRITE:
free(r->ifcall.data);
break;
case TREMOVE:
if(r->fid)
destroyfid(pc, r->fid->fid);
break;
case TCLUNK:
if(r->fid)
destroyfid(pc, r->fid->fid);
if(!pc->conn && r->ifcall.tag == IXP_NOTAG)
pc->ref--;
break;
case TFLUSH:
if((r->oldreq = lookupkey(&pc->tagmap, r->ifcall.oldtag)))
respond(r->oldreq, Einterrupted);
if(!pc->conn && r->ifcall.tag == IXP_NOTAG)
pc->ref--;
break;
case TREAD:
case TSTAT:
break;
/* Still to be implemented: flush, wstat, auth */
}
r->ofcall.tag = r->ifcall.tag;
if(!error)
r->ofcall.type = r->ifcall.type + 1;
else {
r->ofcall.type = RERROR;
r->ofcall.ename = error;
}
if(pc->conn)
ixp_server_respond_fcall(pc->conn, &r->ofcall);
switch(r->ofcall.type) {
case RSTAT:
free(r->ofcall.stat);
break;
case RREAD:
free(r->ofcall.data);
break;
}
deletekey(&pc->tagmap, r->ifcall.tag);;
free(r);
if(!pc->conn && pc->ref == 0)
free_p9conn(pc);
}
/* Pending request cleanup */
static void
ixp_void_request(void *t) {
P9Req *r, *tr;
P9Conn *pc;
r = t;
pc = r->conn;
tr = cext_emallocz(sizeof(P9Req));
tr->conn = pc;
tr->ifcall.type = TFLUSH;
tr->ifcall.tag = IXP_NOTAG;
tr->ifcall.oldtag = r->ifcall.tag;
ixp_handle_req(tr);
}
/* Open FID cleanup */
static void
ixp_void_fid(void *t) {
P9Conn *pc;
P9Req *tr;
Fid *f;
f = t;
pc = f->conn;
tr = cext_emallocz(sizeof(P9Req));
tr->fid = f;
tr->conn = pc;
tr->ifcall.type = TCLUNK;
tr->ifcall.tag = IXP_NOTAG;
tr->ifcall.fid = f->fid;
ixp_handle_req(tr);
}
static void
ixp_p9conn_incref(void *r) {
P9Conn *pc = *(P9Conn **)r;
pc->ref++;
}
/* To cleanup a connction, we increase the ref count for
* each open FID and pending request and generate clunk and
* flush requests. As each request is responded to and each
* FID is clunked, we decrease the ref count. When the ref
* count is 0, we free the P9Conn and its buf. The IXPConn
* is taken care of in server.c */
static void
ixp_cleanup_conn(IXPConn *c) {
P9Conn *pc = c->aux;
pc->conn = nil;
pc->ref = 1;
execmap(&pc->tagmap, ixp_p9conn_incref);
execmap(&pc->fidmap, ixp_p9conn_incref);
if(pc->ref > 1) {
execmap(&pc->tagmap, ixp_void_request);
execmap(&pc->fidmap, ixp_void_fid);
}
if(--pc->ref == 0)
free_p9conn(pc);
}
/* Handle incoming 9P connections */
void
serve_9pcon(IXPConn *c) {
int fd = accept(c->fd, nil, nil);
if(fd < 0)
return;
P9Conn *pc = cext_emallocz(sizeof(P9Conn));
pc->srv = c->aux;
/* XXX */
pc->msize = 1024;
pc->buf = cext_emallocz(pc->msize);
initmap(&pc->tagmap, TAG_BUCKETS, &pc->taghash);
initmap(&pc->fidmap, FID_BUCKETS, &pc->fidhash);
ixp_server_open_conn(c->srv, fd, pc, ixp_server_handle_fcall, ixp_cleanup_conn);
}