libixp

git clone git://oldgit.suckless.org/libixp/
Log | Files | Refs | LICENSE

request.c (13772B)


      1 /* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
      2  * See LICENSE file for license details.
      3  */
      4 #include <assert.h>
      5 #include <stdlib.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 #include <sys/socket.h>
      9 #include "ixp_local.h"
     10 
     11 static void handlereq(Ixp9Req *r);
     12 
     13 /**
     14  * Variable: ixp_printfcall
     15  *
     16  * When set to a non-null value, ixp_printfcall is called once for
     17  * every incoming and outgoing Fcall. It is intended to simplify the
     18  * writing of debugging code for clients, but may be used for any
     19  * arbitrary purpose.
     20  *
     21  * See also:
     22  *	F<ixp_respond>, F<ixp_serve9conn>
     23  */
     24 void (*ixp_printfcall)(IxpFcall*);
     25 
     26 static int
     27 min(int a, int b) {
     28 	if(a < b)
     29 		return a;
     30 	return b;
     31 }
     32 
     33 static char
     34 	Eduptag[] = "tag in use",
     35 	Edupfid[] = "fid in use",
     36 	Enofunc[] = "function not implemented",
     37 	Eopen[] = "fid is already open",
     38 	Enofile[] = "file does not exist",
     39 	Enoread[] = "file not open for reading",
     40 	Enofid[] = "fid does not exist",
     41 	Enotag[] = "tag does not exist",
     42 	Enotdir[] = "not a directory",
     43 	Eintr[] = "interrupted",
     44 	Eisdir[] = "cannot perform operation on a directory";
     45 
     46 enum {
     47 	TAG_BUCKETS = 61,
     48 	FID_BUCKETS = 61,
     49 };
     50 
     51 struct Ixp9Conn {
     52 	Map		tagmap;
     53 	Map		fidmap;
     54 	MapEnt*		taghash[TAG_BUCKETS];
     55 	MapEnt*		fidhash[FID_BUCKETS];
     56 	Ixp9Srv*	srv;
     57 	IxpConn*	conn;
     58 	IxpMutex	rlock;
     59 	IxpMutex	wlock;
     60 	IxpMsg		rmsg;
     61 	IxpMsg		wmsg;
     62 	int		ref;
     63 };
     64 
     65 static void
     66 decref_p9conn(Ixp9Conn *p9conn) {
     67 	thread->lock(&p9conn->wlock);
     68 	if(--p9conn->ref > 0) {
     69 		thread->unlock(&p9conn->wlock);
     70 		return;
     71 	}
     72 	thread->unlock(&p9conn->wlock);
     73 
     74 	assert(p9conn->conn == nil);
     75 
     76 	thread->mdestroy(&p9conn->rlock);
     77 	thread->mdestroy(&p9conn->wlock);
     78 
     79 	ixp_mapfree(&p9conn->tagmap, nil);
     80 	ixp_mapfree(&p9conn->fidmap, nil);
     81 
     82 	free(p9conn->rmsg.data);
     83 	free(p9conn->wmsg.data);
     84 	free(p9conn);
     85 }
     86 
     87 static void*
     88 createfid(Map *map, int fid, Ixp9Conn *p9conn) {
     89 	IxpFid *f;
     90 
     91 	f = emallocz(sizeof *f);
     92 	p9conn->ref++;
     93 	f->conn = p9conn;
     94 	f->fid = fid;
     95 	f->omode = -1;
     96 	f->map = map;
     97 	if(ixp_mapinsert(map, fid, f, false))
     98 		return f;
     99 	free(f);
    100 	return nil;
    101 }
    102 
    103 static int
    104 destroyfid(Ixp9Conn *p9conn, ulong fid) {
    105 	IxpFid *f;
    106 
    107 	f = ixp_maprm(&p9conn->fidmap, fid);
    108 	if(f == nil)
    109 		return 0;
    110 
    111 	if(p9conn->srv->freefid)
    112 		p9conn->srv->freefid(f);
    113 
    114 	decref_p9conn(p9conn);
    115 	free(f);
    116 	return 1;
    117 }
    118 
    119 static void
    120 handlefcall(IxpConn *c) {
    121 	IxpFcall fcall = {0};
    122 	Ixp9Conn *p9conn;
    123 	Ixp9Req *req;
    124 
    125 	p9conn = c->aux;
    126 
    127 	thread->lock(&p9conn->rlock);
    128 	if(ixp_recvmsg(c->fd, &p9conn->rmsg) == 0)
    129 		goto Fail;
    130 	if(ixp_msg2fcall(&p9conn->rmsg, &fcall) == 0)
    131 		goto Fail;
    132 	thread->unlock(&p9conn->rlock);
    133 
    134 	req = emallocz(sizeof *req);
    135 	p9conn->ref++;
    136 	req->conn = p9conn;
    137 	req->srv = p9conn->srv;
    138 	req->ifcall = fcall;
    139 	p9conn->conn = c;
    140 
    141 	if(!ixp_mapinsert(&p9conn->tagmap, fcall.hdr.tag, req, false)) {
    142 		ixp_respond(req, Eduptag);
    143 		return;
    144 	}
    145 
    146 	handlereq(req);
    147 	return;
    148 
    149 Fail:
    150 	thread->unlock(&p9conn->rlock);
    151 	ixp_hangup(c);
    152 	return;
    153 }
    154 
    155 static void
    156 handlereq(Ixp9Req *r) {
    157 	Ixp9Conn *p9conn;
    158 	Ixp9Srv *srv;
    159 
    160 	p9conn = r->conn;
    161 	srv = p9conn->srv;
    162 
    163 	if(ixp_printfcall)
    164 		ixp_printfcall(&r->ifcall);
    165 
    166 	switch(r->ifcall.hdr.type) {
    167 	default:
    168 		ixp_respond(r, Enofunc);
    169 		break;
    170 	case TVersion:
    171 		if(!strcmp(r->ifcall.version.version, "9P"))
    172 			r->ofcall.version.version = "9P";
    173 		else if(!strcmp(r->ifcall.version.version, "9P2000"))
    174 			r->ofcall.version.version = "9P2000";
    175 		else
    176 			r->ofcall.version.version = "unknown";
    177 		r->ofcall.version.msize = r->ifcall.version.msize;
    178 		ixp_respond(r, nil);
    179 		break;
    180 	case TAttach:
    181 		if(!(r->fid = createfid(&p9conn->fidmap, r->ifcall.hdr.fid, p9conn))) {
    182 			ixp_respond(r, Edupfid);
    183 			return;
    184 		}
    185 		/* attach is a required function */
    186 		srv->attach(r);
    187 		break;
    188 	case TClunk:
    189 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    190 			ixp_respond(r, Enofid);
    191 			return;
    192 		}
    193 		if(!srv->clunk) {
    194 			ixp_respond(r, nil);
    195 			return;
    196 		}
    197 		srv->clunk(r);
    198 		break;
    199 	case TFlush:
    200 		if(!(r->oldreq = ixp_mapget(&p9conn->tagmap, r->ifcall.tflush.oldtag))) {
    201 			ixp_respond(r, Enotag);
    202 			return;
    203 		}
    204 		if(!srv->flush) {
    205 			ixp_respond(r, Enofunc);
    206 			return;
    207 		}
    208 		srv->flush(r);
    209 		break;
    210 	case TCreate:
    211 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    212 			ixp_respond(r, Enofid);
    213 			return;
    214 		}
    215 		if(r->fid->omode != -1) {
    216 			ixp_respond(r, Eopen);
    217 			return;
    218 		}
    219 		if(!(r->fid->qid.type&QTDIR)) {
    220 			ixp_respond(r, Enotdir);
    221 			return;
    222 		}
    223 		if(!p9conn->srv->create) {
    224 			ixp_respond(r, Enofunc);
    225 			return;
    226 		}
    227 		p9conn->srv->create(r);
    228 		break;
    229 	case TOpen:
    230 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    231 			ixp_respond(r, Enofid);
    232 			return;
    233 		}
    234 		if((r->fid->qid.type&QTDIR) && (r->ifcall.topen.mode|P9_ORCLOSE) != (P9_OREAD|P9_ORCLOSE)) {
    235 			ixp_respond(r, Eisdir);
    236 			return;
    237 		}
    238 		r->ofcall.ropen.qid = r->fid->qid;
    239 		if(!p9conn->srv->open) {
    240 			ixp_respond(r, Enofunc);
    241 			return;
    242 		}
    243 		p9conn->srv->open(r);
    244 		break;
    245 	case TRead:
    246 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    247 			ixp_respond(r, Enofid);
    248 			return;
    249 		}
    250 		if(r->fid->omode == -1 || r->fid->omode == P9_OWRITE) {
    251 			ixp_respond(r, Enoread);
    252 			return;
    253 		}
    254 		if(!p9conn->srv->read) {
    255 			ixp_respond(r, Enofunc);
    256 			return;
    257 		}
    258 		p9conn->srv->read(r);
    259 		break;
    260 	case TRemove:
    261 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    262 			ixp_respond(r, Enofid);
    263 			return;
    264 		}
    265 		if(!p9conn->srv->remove) {
    266 			ixp_respond(r, Enofunc);
    267 			return;
    268 		}
    269 		p9conn->srv->remove(r);
    270 		break;
    271 	case TStat:
    272 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    273 			ixp_respond(r, Enofid);
    274 			return;
    275 		}
    276 		if(!p9conn->srv->stat) {
    277 			ixp_respond(r, Enofunc);
    278 			return;
    279 		}
    280 		p9conn->srv->stat(r);
    281 		break;
    282 	case TWalk:
    283 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    284 			ixp_respond(r, Enofid);
    285 			return;
    286 		}
    287 		if(r->fid->omode != -1) {
    288 			ixp_respond(r, "cannot walk from an open fid");
    289 			return;
    290 		}
    291 		if(r->ifcall.twalk.nwname && !(r->fid->qid.type&QTDIR)) {
    292 			ixp_respond(r, Enotdir);
    293 			return;
    294 		}
    295 		if((r->ifcall.hdr.fid != r->ifcall.twalk.newfid)) {
    296 			if(!(r->newfid = createfid(&p9conn->fidmap, r->ifcall.twalk.newfid, p9conn))) {
    297 				ixp_respond(r, Edupfid);
    298 				return;
    299 			}
    300 		}else
    301 			r->newfid = r->fid;
    302 		if(!p9conn->srv->walk) {
    303 			ixp_respond(r, Enofunc);
    304 			return;
    305 		}
    306 		p9conn->srv->walk(r);
    307 		break;
    308 	case TWrite:
    309 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    310 			ixp_respond(r, Enofid);
    311 			return;
    312 		}
    313 		if((r->fid->omode&3) != P9_OWRITE && (r->fid->omode&3) != P9_ORDWR) {
    314 			ixp_respond(r, "write on fid not opened for writing");
    315 			return;
    316 		}
    317 		if(!p9conn->srv->write) {
    318 			ixp_respond(r, Enofunc);
    319 			return;
    320 		}
    321 		p9conn->srv->write(r);
    322 		break;
    323 	case TWStat:
    324 		if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
    325 			ixp_respond(r, Enofid);
    326 			return;
    327 		}
    328 		if(~r->ifcall.twstat.stat.type) {
    329 			ixp_respond(r, "wstat of type");
    330 			return;
    331 		}
    332 		if(~r->ifcall.twstat.stat.dev) {
    333 			ixp_respond(r, "wstat of dev");
    334 			return;
    335 		}
    336 		if(~r->ifcall.twstat.stat.qid.type || (ulong)~r->ifcall.twstat.stat.qid.version || ~r->ifcall.twstat.stat.qid.path) {
    337 			ixp_respond(r, "wstat of qid");
    338 			return;
    339 		}
    340 		if(r->ifcall.twstat.stat.muid && r->ifcall.twstat.stat.muid[0]) {
    341 			ixp_respond(r, "wstat of muid");
    342 			return;
    343 		}
    344 		if(~r->ifcall.twstat.stat.mode && ((r->ifcall.twstat.stat.mode&DMDIR)>>24) != r->fid->qid.type&QTDIR) {
    345 			ixp_respond(r, "wstat on DMDIR bit");
    346 			return;
    347 		}
    348 		if(!p9conn->srv->wstat) {
    349 			ixp_respond(r, Enofunc);
    350 			return;
    351 		}
    352 		p9conn->srv->wstat(r);
    353 		break;
    354 	/* Still to be implemented: auth */
    355 	}
    356 }
    357 
    358 /**
    359  * Function: ixp_respond
    360  *
    361  * Sends a response to the given request. The response is
    362  * constructed from the P<ofcall> member of the P<req> parameter, or
    363  * from the P<error> parameter if it is non-null. In the latter
    364  * case, the response is of type RError, while in any other case it
    365  * is of the same type as P<req>->P<ofcall>, which must match the
    366  * request type in P<req>->P<ifcall>.
    367  *
    368  * See also:
    369  *	T<Ixp9Req>, V<ixp_printfcall>
    370  */
    371 void
    372 ixp_respond(Ixp9Req *req, const char *error) {
    373 	Ixp9Conn *p9conn;
    374 	int msize;
    375 
    376 	p9conn = req->conn;
    377 
    378 	switch(req->ifcall.hdr.type) {
    379 	default:
    380 		if(!error)
    381 			assert(!"Respond called on unsupported fcall type");
    382 		break;
    383 	case TVersion:
    384 		assert(error == nil);
    385 		free(req->ifcall.version.version);
    386 
    387 		thread->lock(&p9conn->rlock);
    388 		thread->lock(&p9conn->wlock);
    389 		msize = min(req->ofcall.version.msize, IXP_MAX_MSG);
    390 		p9conn->rmsg.data = erealloc(p9conn->rmsg.data, msize);
    391 		p9conn->wmsg.data = erealloc(p9conn->wmsg.data, msize);
    392 		p9conn->rmsg.size = msize;
    393 		p9conn->wmsg.size = msize;
    394 		thread->unlock(&p9conn->wlock);
    395 		thread->unlock(&p9conn->rlock);
    396 		req->ofcall.version.msize = msize;
    397 		break;
    398 	case TAttach:
    399 		if(error)
    400 			destroyfid(p9conn, req->fid->fid);
    401 		free(req->ifcall.tattach.uname);
    402 		free(req->ifcall.tattach.aname);
    403 		break;
    404 	case TOpen:
    405 	case TCreate:
    406 		if(!error) {
    407 			req->ofcall.ropen.iounit = p9conn->rmsg.size - 24;
    408 			req->fid->iounit = req->ofcall.ropen.iounit;
    409 			req->fid->omode = req->ifcall.topen.mode;
    410 			req->fid->qid = req->ofcall.ropen.qid;
    411 		}
    412 		free(req->ifcall.tcreate.name);
    413 		break;
    414 	case TWalk:
    415 		if(error || req->ofcall.rwalk.nwqid < req->ifcall.twalk.nwname) {
    416 			if(req->ifcall.hdr.fid != req->ifcall.twalk.newfid && req->newfid)
    417 				destroyfid(p9conn, req->newfid->fid);
    418 			if(!error && req->ofcall.rwalk.nwqid == 0)
    419 				error = Enofile;
    420 		}else{
    421 			if(req->ofcall.rwalk.nwqid == 0)
    422 				req->newfid->qid = req->fid->qid;
    423 			else
    424 				req->newfid->qid = req->ofcall.rwalk.wqid[req->ofcall.rwalk.nwqid-1];
    425 		}
    426 		free(*req->ifcall.twalk.wname);
    427 		break;
    428 	case TWrite:
    429 		free(req->ifcall.twrite.data);
    430 		break;
    431 	case TRemove:
    432 		if(req->fid)
    433 			destroyfid(p9conn, req->fid->fid);
    434 		break;
    435 	case TClunk:
    436 		if(req->fid)
    437 			destroyfid(p9conn, req->fid->fid);
    438 		break;
    439 	case TFlush:
    440 		if((req->oldreq = ixp_mapget(&p9conn->tagmap, req->ifcall.tflush.oldtag)))
    441 			ixp_respond(req->oldreq, Eintr);
    442 		break;
    443 	case TWStat:
    444 		ixp_freestat(&req->ifcall.twstat.stat);
    445 		break;
    446 	case TRead:
    447 	case TStat:
    448 		break;		
    449 	/* Still to be implemented: auth */
    450 	}
    451 
    452 	req->ofcall.hdr.tag = req->ifcall.hdr.tag;
    453 
    454 	if(error == nil)
    455 		req->ofcall.hdr.type = req->ifcall.hdr.type + 1;
    456 	else {
    457 		req->ofcall.hdr.type = RError;
    458 		req->ofcall.error.ename = (char*)error;
    459 	}
    460 
    461 	if(ixp_printfcall)
    462 		ixp_printfcall(&req->ofcall);
    463 
    464 	ixp_maprm(&p9conn->tagmap, req->ifcall.hdr.tag);;
    465 
    466 	if(p9conn->conn) {
    467 		thread->lock(&p9conn->wlock);
    468 		msize = ixp_fcall2msg(&p9conn->wmsg, &req->ofcall);
    469 		if(ixp_sendmsg(p9conn->conn->fd, &p9conn->wmsg) != msize)
    470 			ixp_hangup(p9conn->conn);
    471 		thread->unlock(&p9conn->wlock);
    472 	}
    473 
    474 	switch(req->ofcall.hdr.type) {
    475 	case RStat:
    476 		free(req->ofcall.rstat.stat);
    477 		break;
    478 	case RRead:
    479 		free(req->ofcall.rread.data);
    480 		break;
    481 	}
    482 	free(req);
    483 	decref_p9conn(p9conn);
    484 }
    485 
    486 /* Flush a pending request */
    487 static void
    488 voidrequest(void *context, void *arg) {
    489 	Ixp9Req *orig_req, *flush_req;
    490 	Ixp9Conn *conn;
    491 
    492 	orig_req = arg;
    493 	conn = orig_req->conn;
    494 	conn->ref++;
    495 
    496 	flush_req = emallocz(sizeof *orig_req);
    497 	flush_req->ifcall.hdr.type = TFlush;
    498 	flush_req->ifcall.hdr.tag = IXP_NOTAG;
    499 	flush_req->ifcall.tflush.oldtag = orig_req->ifcall.hdr.tag;
    500 	flush_req->conn = conn;
    501 
    502 	flush_req->aux = *(void**)context;
    503 	*(void**)context = flush_req;
    504 }
    505 
    506 /* Clunk an open IxpFid */
    507 static void
    508 voidfid(void *context, void *arg) {
    509 	Ixp9Conn *p9conn;
    510 	Ixp9Req *clunk_req;
    511 	IxpFid *fid;
    512 
    513 	fid = arg;
    514 	p9conn = fid->conn;
    515 	p9conn->ref++;
    516 
    517 	clunk_req = emallocz(sizeof *clunk_req);
    518 	clunk_req->ifcall.hdr.type = TClunk;
    519 	clunk_req->ifcall.hdr.tag = IXP_NOTAG;
    520 	clunk_req->ifcall.hdr.fid = fid->fid;
    521 	clunk_req->fid = fid;
    522 	clunk_req->conn = p9conn;
    523 
    524 	clunk_req->aux = *(void**)context;
    525 	*(void**)context = clunk_req;
    526 }
    527 
    528 static void
    529 cleanupconn(IxpConn *c) {
    530 	Ixp9Conn *p9conn;
    531 	Ixp9Req *req, *r;
    532 
    533 	p9conn = c->aux;
    534 	p9conn->conn = nil;
    535 	req = nil;
    536 	if(p9conn->ref > 1) {
    537 		ixp_mapexec(&p9conn->fidmap, voidfid, &req);
    538 		ixp_mapexec(&p9conn->tagmap, voidrequest, &req);
    539 	}
    540 	while((r = req)) {
    541 		req = r->aux;
    542 		r->aux = nil;
    543 		handlereq(r);
    544 	}
    545 	decref_p9conn(p9conn);
    546 }
    547 
    548 /* Handle incoming 9P connections */
    549 /**
    550  * Type: Ixp9Srv
    551  * Type: Ixp9Req
    552  * Function: ixp_serve9conn
    553  *
    554  * The ixp_serve9conn handles incoming 9P connections. It is
    555  * ordinarily passed as the P<read> member to F<ixp_listen> with an
    556  * Ixp9Srv structure passed as the P<aux> member. The handlers
    557  * defined in the Ixp9Srv structure are called whenever a matching
    558  * Fcall type is received. The handlers are expected to call
    559  * F<ixp_respond> at some point, whether before they return or at
    560  * some undefined point in the future. Whenever a client
    561  * disconnects, libixp generates whatever flush and clunk events are
    562  * required to leave the connection in a clean state and waits for
    563  * all responses before freeing the connections associated data
    564  * structures.
    565  *
    566  * Whenever a file is closed and an T<IxpFid> is about to be freed,
    567  * the P<freefid> member is called to perform any necessary cleanup
    568  * and to free any associated resources.
    569  *
    570  * See also:
    571  *	F<ixp_listen>, F<ixp_respond>, F<ixp_printfcall>,
    572  *	F<IxpFcall>, F<IxpFid>
    573  */
    574 void
    575 ixp_serve9conn(IxpConn *c) {
    576 	Ixp9Conn *p9conn;
    577 	int fd;
    578 
    579 	fd = accept(c->fd, nil, nil);
    580 	if(fd < 0)
    581 		return;
    582 
    583 	p9conn = emallocz(sizeof *p9conn);
    584 	p9conn->ref++;
    585 	p9conn->srv = c->aux;
    586 	p9conn->rmsg.size = 1024;
    587 	p9conn->wmsg.size = 1024;
    588 	p9conn->rmsg.data = emalloc(p9conn->rmsg.size);
    589 	p9conn->wmsg.data = emalloc(p9conn->wmsg.size);
    590 
    591 	ixp_mapinit(&p9conn->tagmap, p9conn->taghash, nelem(p9conn->taghash));
    592 	ixp_mapinit(&p9conn->fidmap, p9conn->fidhash, nelem(p9conn->fidhash));
    593 	thread->initmutex(&p9conn->rlock);
    594 	thread->initmutex(&p9conn->wlock);
    595 
    596 	ixp_listen(c->srv, fd, p9conn, handlefcall, cleanupconn);
    597 }