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