1 /* Copyright ©2006-2008 Kris Maglione <fbsdaemon@gmail.com>
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 static void
_printfcall(Fcall * f)14 _printfcall(Fcall *f) {
15 	USED(f);
16 }
17 void (*ixp_printfcall)(Fcall*) = _printfcall;
18 
19 static int
min(int a,int b)20 min(int a, int b) {
21 	if(a < b)
22 		return a;
23 	return b;
24 }
25 
26 static char
27 	Eduptag[] = "tag in use",
28 	Edupfid[] = "fid in use",
29 	Enofunc[] = "function not implemented",
30 	Ebotch[] = "9P protocol botch",
31 	Enofile[] = "file does not exist",
32 	Enofid[] = "fid does not exist",
33 	Enotag[] = "tag does not exist",
34 	Enotdir[] = "not a directory",
35 	Eintr[] = "interrupted",
36 	Eisdir[] = "cannot perform operation on a directory";
37 
38 enum {
39 	TAG_BUCKETS = 61,
40 	FID_BUCKETS = 61,
41 };
42 
43 struct Ixp9Conn {
44 	Intmap		tagmap;
45 	Intmap		fidmap;
46 	void		*taghash[TAG_BUCKETS];
47 	void		*fidhash[FID_BUCKETS];
48 	Ixp9Srv		*srv;
49 	IxpConn		*conn;
50 	IxpMutex	rlock, wlock;
51 	IxpMsg		rmsg;
52 	IxpMsg		wmsg;
53 	int		ref;
54 };
55 
56 static void
decref_p9conn(Ixp9Conn * pc)57 decref_p9conn(Ixp9Conn *pc) {
58 	thread->lock(&pc->wlock);
59 	if(--pc->ref > 0) {
60 		thread->unlock(&pc->wlock);
61 		return;
62 	}
63 	thread->unlock(&pc->wlock);
64 
65 	assert(pc->conn == nil);
66 
67 	thread->mdestroy(&pc->rlock);
68 	thread->mdestroy(&pc->wlock);
69 
70 	freemap(&pc->tagmap, nil);
71 	freemap(&pc->fidmap, nil);
72 
73 	free(pc->rmsg.data);
74 	free(pc->wmsg.data);
75 	free(pc);
76 }
77 
78 static void*
createfid(Intmap * map,int fid,Ixp9Conn * pc)79 createfid(Intmap *map, int fid, Ixp9Conn *pc) {
80 	Fid *f;
81 
82 	f = emallocz(sizeof *f);
83 	pc->ref++;
84 	f->conn = pc;
85 	f->fid = fid;
86 	f->omode = -1;
87 	f->map = map;
88 	if(caninsertkey(map, fid, f))
89 		return f;
90 	free(f);
91 	return nil;
92 }
93 
94 static int
destroyfid(Ixp9Conn * pc,ulong fid)95 destroyfid(Ixp9Conn *pc, ulong fid) {
96 	Fid *f;
97 
98 	f = deletekey(&pc->fidmap, fid);
99 	if(f == nil)
100 		return 0;
101 
102 	if(pc->srv->freefid)
103 		pc->srv->freefid(f);
104 
105 	decref_p9conn(pc);
106 	free(f);
107 	return 1;
108 }
109 
110 static void
handlefcall(IxpConn * c)111 handlefcall(IxpConn *c) {
112 	Fcall fcall = {0};
113 	Ixp9Conn *pc;
114 	Ixp9Req *req;
115 
116 	pc = c->aux;
117 
118 	thread->lock(&pc->rlock);
119 	if(ixp_recvmsg(c->fd, &pc->rmsg) == 0)
120 		goto Fail;
121 	if(ixp_msg2fcall(&pc->rmsg, &fcall) == 0)
122 		goto Fail;
123 	thread->unlock(&pc->rlock);
124 
125 	req = emallocz(sizeof *req);
126 	pc->ref++;
127 	req->conn = pc;
128 	req->srv = pc->srv;
129 	req->ifcall = fcall;
130 	pc->conn = c;
131 
132 	if(caninsertkey(&pc->tagmap, fcall.hdr.tag, req) == 0) {
133 		respond(req, Eduptag);
134 		return;
135 	}
136 
137 	handlereq(req);
138 	return;
139 
140 Fail:
141 	thread->unlock(&pc->rlock);
142 	ixp_hangup(c);
143 	return;
144 }
145 
146 static void
handlereq(Ixp9Req * r)147 handlereq(Ixp9Req *r) {
148 	Ixp9Conn *pc;
149 	Ixp9Srv *srv;
150 
151 	pc = r->conn;
152 	srv = pc->srv;
153 
154 	ixp_printfcall(&r->ifcall);
155 
156 	switch(r->ifcall.hdr.type) {
157 	default:
158 		respond(r, Enofunc);
159 		break;
160 	case TVersion:
161 		if(!strcmp(r->ifcall.version.version, "9P"))
162 			r->ofcall.version.version = "9P";
163 		else if(!strcmp(r->ifcall.version.version, "9P2000"))
164 			r->ofcall.version.version = "9P2000";
165 		else
166 			r->ofcall.version.version = "unknown";
167 		r->ofcall.version.msize = r->ifcall.version.msize;
168 		respond(r, nil);
169 		break;
170 	case TAttach:
171 		if(!(r->fid = createfid(&pc->fidmap, r->ifcall.hdr.fid, pc))) {
172 			respond(r, Edupfid);
173 			return;
174 		}
175 		/* attach is a required function */
176 		srv->attach(r);
177 		break;
178 	case TClunk:
179 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
180 			respond(r, Enofid);
181 			return;
182 		}
183 		if(!srv->clunk) {
184 			respond(r, nil);
185 			return;
186 		}
187 		srv->clunk(r);
188 		break;
189 	case TFlush:
190 		if(!(r->oldreq = lookupkey(&pc->tagmap, r->ifcall.tflush.oldtag))) {
191 			respond(r, Enotag);
192 			return;
193 		}
194 		if(!srv->flush) {
195 			respond(r, Enofunc);
196 			return;
197 		}
198 		srv->flush(r);
199 		break;
200 	case TCreate:
201 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
202 			respond(r, Enofid);
203 			return;
204 		}
205 		if(r->fid->omode != -1) {
206 			respond(r, Ebotch);
207 			return;
208 		}
209 		if(!(r->fid->qid.type&QTDIR)) {
210 			respond(r, Enotdir);
211 			return;
212 		}
213 		if(!pc->srv->create) {
214 			respond(r, Enofunc);
215 			return;
216 		}
217 		pc->srv->create(r);
218 		break;
219 	case TOpen:
220 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
221 			respond(r, Enofid);
222 			return;
223 		}
224 		if((r->fid->qid.type&QTDIR) && (r->ifcall.topen.mode|P9_ORCLOSE) != (P9_OREAD|P9_ORCLOSE)) {
225 			respond(r, Eisdir);
226 			return;
227 		}
228 		r->ofcall.ropen.qid = r->fid->qid;
229 		if(!pc->srv->open) {
230 			respond(r, Enofunc);
231 			return;
232 		}
233 		pc->srv->open(r);
234 		break;
235 	case TRead:
236 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
237 			respond(r, Enofid);
238 			return;
239 		}
240 		if(r->fid->omode == -1 || r->fid->omode == P9_OWRITE) {
241 			respond(r, Ebotch);
242 			return;
243 		}
244 		if(!pc->srv->read) {
245 			respond(r, Enofunc);
246 			return;
247 		}
248 		pc->srv->read(r);
249 		break;
250 	case TRemove:
251 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
252 			respond(r, Enofid);
253 			return;
254 		}
255 		if(!pc->srv->remove) {
256 			respond(r, Enofunc);
257 			return;
258 		}
259 		pc->srv->remove(r);
260 		break;
261 	case TStat:
262 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
263 			respond(r, Enofid);
264 			return;
265 		}
266 		if(!pc->srv->stat) {
267 			respond(r, Enofunc);
268 			return;
269 		}
270 		pc->srv->stat(r);
271 		break;
272 	case TWalk:
273 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
274 			respond(r, Enofid);
275 			return;
276 		}
277 		if(r->fid->omode != -1) {
278 			respond(r, "cannot walk from an open fid");
279 			return;
280 		}
281 		if(r->ifcall.twalk.nwname && !(r->fid->qid.type&QTDIR)) {
282 			respond(r, Enotdir);
283 			return;
284 		}
285 		if((r->ifcall.hdr.fid != r->ifcall.twalk.newfid)) {
286 			if(!(r->newfid = createfid(&pc->fidmap, r->ifcall.twalk.newfid, pc))) {
287 				respond(r, Edupfid);
288 				return;
289 			}
290 		}else
291 			r->newfid = r->fid;
292 		if(!pc->srv->walk) {
293 			respond(r, Enofunc);
294 			return;
295 		}
296 		pc->srv->walk(r);
297 		break;
298 	case TWrite:
299 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
300 			respond(r, Enofid);
301 			return;
302 		}
303 		if((r->fid->omode&3) != P9_OWRITE && (r->fid->omode&3) != P9_ORDWR) {
304 			respond(r, "write on fid not opened for writing");
305 			return;
306 		}
307 		if(!pc->srv->write) {
308 			respond(r, Enofunc);
309 			return;
310 		}
311 		pc->srv->write(r);
312 		break;
313 	case TWStat:
314 		if(!(r->fid = lookupkey(&pc->fidmap, r->ifcall.hdr.fid))) {
315 			respond(r, Enofid);
316 			return;
317 		}
318 		if((ushort)~r->ifcall.twstat.stat.type) {
319 			respond(r, "wstat of type");
320 			return;
321 		}
322 		if((uint)~r->ifcall.twstat.stat.dev) {
323 			respond(r, "wstat of dev");
324 			return;
325 		}
326 		if((uchar)~r->ifcall.twstat.stat.qid.type || (ulong)~r->ifcall.twstat.stat.qid.version || (uvlong)~r->ifcall.twstat.stat.qid.path) {
327 			respond(r, "wstat of qid");
328 			return;
329 		}
330 		if(r->ifcall.twstat.stat.muid && r->ifcall.twstat.stat.muid[0]) {
331 			respond(r, "wstat of muid");
332 			return;
333 		}
334 		if((ulong)~r->ifcall.twstat.stat.mode && ((r->ifcall.twstat.stat.mode&DMDIR)>>24) != r->fid->qid.type&QTDIR) {
335 			respond(r, "wstat on DMDIR bit");
336 			return;
337 		}
338 		pc->srv->wstat(r);
339 		break;
340 	/* Still to be implemented: auth */
341 	}
342 }
343 
344 void
respond(Ixp9Req * r,const char * error)345 respond(Ixp9Req *r, const char *error) {
346 	Ixp9Conn *pc;
347 	int msize;
348 
349 	pc = r->conn;
350 
351 	switch(r->ifcall.hdr.type) {
352 	default:
353 		if(!error)
354 			assert(!"Respond called on unsupported fcall type");
355 		break;
356 	case TVersion:
357 		assert(error == nil);
358 		free(r->ifcall.version.version);
359 
360 		thread->lock(&pc->rlock);
361 		thread->lock(&pc->wlock);
362 		msize = min(r->ofcall.version.msize, IXP_MAX_MSG);
363 		pc->rmsg.data = erealloc(pc->rmsg.data, msize);
364 		pc->wmsg.data = erealloc(pc->wmsg.data, msize);
365 		pc->rmsg.size = msize;
366 		pc->wmsg.size = msize;
367 		thread->unlock(&pc->wlock);
368 		thread->unlock(&pc->rlock);
369 		r->ofcall.version.msize = msize;
370 		break;
371 	case TAttach:
372 		if(error)
373 			destroyfid(pc, r->fid->fid);
374 		free(r->ifcall.tattach.uname);
375 		free(r->ifcall.tattach.aname);
376 		break;
377 	case TOpen:
378 	case TCreate:
379 		if(!error) {
380 			r->ofcall.ropen.iounit = pc->rmsg.size - 24;
381 			r->fid->iounit = r->ofcall.ropen.iounit;
382 			r->fid->omode = r->ifcall.topen.mode;
383 			r->fid->qid = r->ofcall.ropen.qid;
384 		}
385 		free(r->ifcall.tcreate.name);
386 		break;
387 	case TWalk:
388 		if(error || r->ofcall.rwalk.nwqid < r->ifcall.twalk.nwname) {
389 			if(r->ifcall.hdr.fid != r->ifcall.twalk.newfid && r->newfid)
390 				destroyfid(pc, r->newfid->fid);
391 			if(!error && r->ofcall.rwalk.nwqid == 0)
392 				error = Enofile;
393 		}else{
394 			if(r->ofcall.rwalk.nwqid == 0)
395 				r->newfid->qid = r->fid->qid;
396 			else
397 				r->newfid->qid = r->ofcall.rwalk.wqid[r->ofcall.rwalk.nwqid-1];
398 		}
399 		free(*r->ifcall.twalk.wname);
400 		break;
401 	case TWrite:
402 		free(r->ifcall.twrite.data);
403 		break;
404 	case TRemove:
405 		if(r->fid)
406 			destroyfid(pc, r->fid->fid);
407 		break;
408 	case TClunk:
409 		if(r->fid)
410 			destroyfid(pc, r->fid->fid);
411 		break;
412 	case TFlush:
413 		if((r->oldreq = lookupkey(&pc->tagmap, r->ifcall.tflush.oldtag)))
414 			respond(r->oldreq, Eintr);
415 		break;
416 	case TWStat:
417 		ixp_freestat(&r->ifcall.twstat.stat);
418 		break;
419 	case TRead:
420 	case TStat:
421 		break;
422 	/* Still to be implemented: auth */
423 	}
424 
425 	r->ofcall.hdr.tag = r->ifcall.hdr.tag;
426 
427 	if(error == nil)
428 		r->ofcall.hdr.type = r->ifcall.hdr.type + 1;
429 	else {
430 		r->ofcall.hdr.type = RError;
431 		r->ofcall.error.ename = (char*)error;
432 	}
433 
434 	deletekey(&pc->tagmap, r->ifcall.hdr.tag);;
435 
436 	if(pc->conn) {
437 		thread->lock(&pc->wlock);
438 		msize = ixp_fcall2msg(&pc->wmsg, &r->ofcall);
439 		if(ixp_sendmsg(pc->conn->fd, &pc->wmsg) != msize)
440 			ixp_hangup(pc->conn);
441 		thread->unlock(&pc->wlock);
442 	}
443 
444 	switch(r->ofcall.hdr.type) {
445 	case RStat:
446 		free(r->ofcall.rstat.stat);
447 		break;
448 	case RRead:
449 		free(r->ofcall.rread.data);
450 		break;
451 	}
452 	free(r);
453 	decref_p9conn(pc);
454 }
455 
456 /* Flush a pending request */
457 static void
voidrequest(void * t)458 voidrequest(void *t) {
459 	Ixp9Req *r, *tr;
460 	Ixp9Conn *pc;
461 
462 	r = t;
463 	pc = r->conn;
464 	pc->ref++;
465 
466 	tr = emallocz(sizeof *tr);
467 	tr->ifcall.hdr.type = TFlush;
468 	tr->ifcall.hdr.tag = IXP_NOTAG;
469 	tr->ifcall.tflush.oldtag = r->ifcall.hdr.tag;
470 	tr->conn = pc;
471 	handlereq(tr);
472 }
473 
474 /* Clunk an open Fid */
475 static void
voidfid(void * t)476 voidfid(void *t) {
477 	Ixp9Conn *pc;
478 	Ixp9Req *tr;
479 	Fid *f;
480 
481 	f = t;
482 	pc = f->conn;
483 	pc->ref++;
484 
485 	tr = emallocz(sizeof *tr);
486 	tr->ifcall.hdr.type = TClunk;
487 	tr->ifcall.hdr.tag = IXP_NOTAG;
488 	tr->ifcall.hdr.fid = f->fid;
489 	tr->fid = f;
490 	tr->conn = pc;
491 	handlereq(tr);
492 }
493 
494 static void
cleanupconn(IxpConn * c)495 cleanupconn(IxpConn *c) {
496 	Ixp9Conn *pc;
497 
498 	pc = c->aux;
499 	pc->conn = nil;
500 	if(pc->ref > 1) {
501 		execmap(&pc->tagmap, voidrequest);
502 		execmap(&pc->fidmap, voidfid);
503 	}
504 	decref_p9conn(pc);
505 }
506 
507 /* Handle incoming 9P connections */
508 void
serve_9pcon(IxpConn * c)509 serve_9pcon(IxpConn *c) {
510 	Ixp9Conn *pc;
511 	int fd;
512 
513 	fd = accept(c->fd, nil, nil);
514 	if(fd < 0)
515 		return;
516 
517 	pc = emallocz(sizeof *pc);
518 	pc->ref++;
519 	pc->srv = c->aux;
520 	pc->rmsg.size = 1024;
521 	pc->wmsg.size = 1024;
522 	pc->rmsg.data = emalloc(pc->rmsg.size);
523 	pc->wmsg.data = emalloc(pc->wmsg.size);
524 
525 	initmap(&pc->tagmap, TAG_BUCKETS, &pc->taghash);
526 	initmap(&pc->fidmap, FID_BUCKETS, &pc->fidhash);
527 	thread->initmutex(&pc->rlock);
528 	thread->initmutex(&pc->wlock);
529 
530 	ixp_listen(c->srv, fd, pc, handlefcall, cleanupconn);
531 }
532