xref: /plan9/sys/src/cmd/cfs/cfs.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 
6 #include "cformat.h"
7 #include "lru.h"
8 #include "bcache.h"
9 #include "disk.h"
10 #include "inode.h"
11 #include "file.h"
12 #include "stats.h"
13 
14 enum
15 {
16 	Nfid=		10240,
17 };
18 
19 /* maximum length of a file */
20 #define MAXLEN 0x7fffffffffffffffLL
21 
22 typedef struct Mfile Mfile;
23 typedef struct Ram Ram;
24 typedef struct P9fs P9fs;
25 typedef struct Clunkitem Clunkitem;
26 
27 struct Mfile
28 {
29 	Qid	qid;
30 	char	busy;
31 };
32 
33 Mfile	mfile[Nfid];
34 Icache	ic;
35 int	debug, statson;
36 
37 struct P9fs
38 {
39 	int	fd[2];
40 	Fcall	rhdr;
41 	Fcall	thdr;
42 	long	len;
43 	char	*name;
44 };
45 
46 /*
47  * We do clunks asynchronously (we don't wait for the rclunk from
48  * the server).  The fid, however, may not be reused until the Rcklunk
49  * is in.  We therefore remember outstanding Rclunks in a linked
50  * list of Clunkitems.  It is hard to imagine having more than one or two
51  * outstanding.  We pretend clunks always succeed.
52  */
53 struct Clunkitem
54 {
55 	u32int	fid;
56 	ushort	tag;
57 	Clunkitem	*next;
58 };
59 Clunkitem	*clunklist;
60 
61 
62 P9fs	c;	/* client conversation */
63 P9fs	s;	/* server conversation */
64 
65 struct Cfsstat  cfsstat, cfsprev;
66 char	statbuf[2048];
67 int	statlen;
68 
69 #define	MAXFDATA	8192	/* i/o size for read/write */
70 
71 int		messagesize = MAXFDATA+IOHDRSZ;
72 
73 uchar	datasnd[MAXFDATA + IOHDRSZ];
74 uchar	datarcv[MAXFDATA + IOHDRSZ];
75 
76 Qid	rootqid;
77 Qid	ctlqid = {0x5555555555555555LL, 0, 0};
78 
79 Clunkitem *getclunk(u32int, ushort);
80 void	rversion(void);
81 void	rauth(Mfile*);
82 void	rflush(void);
83 void	rattach(Mfile*);
84 void	rwalk(Mfile*);
85 void	ropen(Mfile*);
86 void	rcreate(Mfile*);
87 void	rread(Mfile*);
88 void	rwrite(Mfile*);
89 void	rclunk(Mfile*);
90 void	rremove(Mfile*);
91 void	rstat(Mfile*);
92 void	rwstat(Mfile*);
93 void	error(char*, ...);
94 void	warning(char*);
95 void	mountinit(char*, char*);
96 Clunkitem *notclunked(u32int, ushort);
97 void	io(void);
98 void putclunk(u32int, ushort);
99 void	sendreply(char*);
100 void	sendmsg(P9fs*, Fcall*);
101 u32int rcvclunk(P9fs*, Fcall*);
102 void	rcvmsg(P9fs*, Fcall*);
103 int	delegate(void);
104 int	askserver(void);
105 void	cachesetup(int, char*);
106 int	ctltest(Mfile*);
107 void	genstats(void);
108 
109 char *mname[]={
110 	[Tversion]		"Tversion",
111 	[Tauth]	"Tauth",
112 	[Tflush]	"Tflush",
113 	[Tattach]	"Tattach",
114 	[Twalk]		"Twalk",
115 	[Topen]		"Topen",
116 	[Tcreate]	"Tcreate",
117 	[Tclunk]	"Tclunk",
118 	[Tread]		"Tread",
119 	[Twrite]	"Twrite",
120 	[Tremove]	"Tremove",
121 	[Tstat]		"Tstat",
122 	[Twstat]	"Twstat",
123 	[Rversion]	"Rversion",
124 	[Rauth]	"Rauth",
125 	[Rerror]	"Rerror",
126 	[Rflush]	"Rflush",
127 	[Rattach]	"Rattach",
128 	[Rwalk]		"Rwalk",
129 	[Ropen]		"Ropen",
130 	[Rcreate]	"Rcreate",
131 	[Rclunk]	"Rclunk",
132 	[Rread]		"Rread",
133 	[Rwrite]	"Rwrite",
134 	[Rremove]	"Rremove",
135 	[Rstat]		"Rstat",
136 	[Rwstat]	"Rwstat",
137 			0,
138 };
139 
140 void
141 usage(void)
142 {
143 	fprint(2, "usage:\tcfs -s [-rd] [-f partition]");
144 	fprint(2, "\tcfs [-rd] [-f partition] [-a netaddr] [mt-pt]\n");
145 	exits("usage");
146 }
147 
148 void
149 main(int argc, char *argv[])
150 {
151 	int std;
152 	int format;
153 	char *part;
154 	char *server;
155 	char *mtpt;
156 
157 	std = 0;
158 	format = 0;
159 	part = "/dev/sdC0/cache";
160 	server = "il!emelie";
161 	mtpt = "/tmp";
162 
163 	ARGBEGIN{
164 	case 'a':
165 		server = ARGF();
166 		if(server == 0)
167 			usage();
168 		break;
169 	case 'S':
170 		statson = 1;
171 		break;
172 	case 's':
173 		std = 1;
174 		break;
175 	case 'r':
176 		format = 1;
177 		break;
178 	case 'f':
179 		part = ARGF();
180 		if(part == 0)
181 			usage();
182 		break;
183 	case 'd':
184 		debug = 1;
185 		break;
186 	default:
187 		usage();
188 	}ARGEND
189 	if(argc && *argv)
190 		mtpt = *argv;
191 
192 	if(debug)
193 		fmtinstall('F', fcallfmt);
194 
195 	cachesetup(format, part);
196 
197 	c.name = "client";
198 	s.name = "server";
199 	if(std){
200 		c.fd[0] = c.fd[1] = 1;
201 		s.fd[0] = s.fd[1] = 0;
202 	}else
203 		mountinit(server, mtpt);
204 
205 	switch(fork()){
206 	case 0:
207 		io();
208 		exits("");
209 	case -1:
210 		error("fork");
211 	default:
212 		exits("");
213 	}
214 }
215 
216 void
217 cachesetup(int format, char *partition)
218 {
219 	int f;
220 	int secsize;
221 	int inodes;
222 	int blocksize;
223 
224 	secsize = 512;
225 	inodes = 1024;
226 	blocksize = 4*1024;
227 
228 	f = open(partition, ORDWR);
229 	if(f < 0)
230 		error("opening partition");
231 
232 	if(format || iinit(&ic, f, secsize)<0){
233 		if(iformat(&ic, f, inodes, "bootes", blocksize, secsize) < 0)
234 			error("formatting failed");
235 	}
236 }
237 
238 void
239 mountinit(char *server, char *mountpoint)
240 {
241 	int p[2];
242 
243 	/*
244 	 *  grab a channel and call up the file server
245 	 */
246 	s.fd[0] = s.fd[1] = dial(netmkaddr(server, 0, "9fs"), 0, 0, 0);
247 	if(s.fd[0] < 0)
248 		error("opening data");
249 
250 	/*
251  	 *  mount onto name space
252 	 */
253 	if(pipe(p) < 0)
254 		error("pipe failed");
255 	switch(fork()){
256 	case 0:
257 		break;
258 	default:
259 		if(amount(p[1], mountpoint, MREPL|MCREATE, "") < 0)
260 			error("mount failed");
261 		exits(0);
262 	case -1:
263 		error("fork failed\n");
264 /*BUG: no wait!*/
265 	}
266 	c.fd[0] = c.fd[1] = p[0];
267 }
268 
269 void
270 io(void)
271 {
272 	int type;
273 	Mfile *mf;
274     loop:
275 	rcvmsg(&c, &c.thdr);
276 
277 	type = c.thdr.type;
278 
279 	if(statson){
280 		cfsstat.cm[type].n++;
281 		cfsstat.cm[type].s = nsec();
282 	}
283 
284 	mf = &mfile[c.thdr.fid];
285 	switch(type){
286 	default:
287 		error("type");
288 		break;
289 	case Tversion:
290 		rversion();
291 		break;
292 	case Tauth:
293 		mf = &mfile[c.thdr.afid];
294 		rauth(mf);
295 		break;
296 	case Tflush:
297 		rflush();
298 		break;
299 	case Tattach:
300 		rattach(mf);
301 		break;
302 	case Twalk:
303 		rwalk(mf);
304 		break;
305 	case Topen:
306 		ropen(mf);
307 		break;
308 	case Tcreate:
309 		rcreate(mf);
310 		break;
311 	case Tread:
312 		rread(mf);
313 		break;
314 	case Twrite:
315 		rwrite(mf);
316 		break;
317 	case Tclunk:
318 		rclunk(mf);
319 		break;
320 	case Tremove:
321 		rremove(mf);
322 		break;
323 	case Tstat:
324 		rstat(mf);
325 		break;
326 	case Twstat:
327 		rwstat(mf);
328 		break;
329 	}
330 	if(statson){
331 		cfsstat.cm[type].t += nsec() -cfsstat.cm[type].s;
332 	}
333 	goto loop;
334 }
335 
336 void
337 rversion(void)
338 {
339 	if(messagesize > c.thdr.msize)
340 		messagesize = c.thdr.msize;
341 	c.thdr.msize = messagesize;	/* set downstream size */
342 	delegate();
343 }
344 
345 void
346 rauth(Mfile *mf)
347 {
348 	Mfile *amf;
349 
350 	if(delegate() == 0){
351 		if (c.thdr.afid != NOFID){
352 			amf = &mfile[c.thdr.afid];
353 			if(amf->busy)
354 				error("rauth afid on used channel");
355 			amf->qid = s.rhdr.aqid;
356 			amf->busy = 1;
357 		}
358 		mf->qid = s.rhdr.qid;
359 		mf->busy = 1;
360 	}
361 }
362 
363 void
364 rflush(void)		/* synchronous so easy */
365 {
366 	sendreply(0);
367 }
368 
369 void
370 rattach(Mfile *mf)
371 {
372 	if(delegate() == 0){
373 		mf->qid = s.rhdr.qid;
374 		mf->busy = 1;
375 		if (statson == 1){
376 			statson++;
377 			rootqid = mf->qid;
378 		}
379 	}
380 }
381 
382 void
383 rwalk(Mfile *mf)
384 {
385 	Mfile *nmf;
386 	u32int fid;
387 
388 	nmf = nil;
389 	if(statson
390 	  && mf->qid.type == rootqid.type && mf->qid.path == rootqid.path
391 	  && c.thdr.nwname == 1 && strcmp(c.thdr.wname[0], "cfsctl") == 0){
392 		/* This is the ctl file */
393 		nmf = &mfile[c.thdr.newfid];
394 		if(c.thdr.newfid != c.thdr.fid && nmf->busy)
395 			error("clone to used channel");
396 		nmf = &mfile[c.thdr.newfid];
397 		nmf->qid = ctlqid;
398 		nmf->busy = 1;
399 		c.rhdr.nwqid = 1;
400 		c.rhdr.wqid[0] = ctlqid;
401 		sendreply(0);
402 		return;
403 	}
404 	if(c.thdr.newfid != c.thdr.fid){
405 		if(c.thdr.newfid<0 || Nfid<=c.thdr.newfid)
406 			error("clone nfid out of range");
407 		if(notclunked(c.thdr.newfid, NOTAG)){
408 			/* wait for any outstanding clunks on this new fid */
409 			do{
410 				fid = rcvclunk(&s, &s.rhdr);
411 			}while(fid != c.thdr.newfid);
412 		}
413 		nmf = &mfile[c.thdr.newfid];
414 		if(nmf->busy)
415 			error("clone to used channel");
416 		nmf = &mfile[c.thdr.newfid];
417 		nmf->qid = mf->qid;
418 		nmf->busy = 1;
419 		mf = nmf; /* Walk mf */
420 	}
421 
422 	if(delegate() < 0){	/* complete failure */
423 		if(nmf)
424 			nmf->busy = 0;
425 		return;
426 	}
427 
428 	if(s.rhdr.nwqid == c.thdr.nwname){	/* complete success */
429 		if(s.rhdr.nwqid > 0)
430 			mf->qid = s.rhdr.wqid[s.rhdr.nwqid-1];
431 		return;
432 	}
433 
434 	/* partial success; release fid */
435 	if(nmf)
436 		nmf->busy = 0;
437 }
438 
439 void
440 ropen(Mfile *mf)
441 {
442 	if(statson && ctltest(mf)){
443 		/* Opening ctl file */
444 		if(c.thdr.mode != OREAD){
445 			sendreply("does not exist");
446 			return;
447 		}
448 		c.rhdr.qid = ctlqid;
449 		c.rhdr.iounit = 0;
450 		sendreply(0);
451 		genstats();
452 		return;
453 	}
454 	if(delegate() == 0){
455 		mf->qid = s.rhdr.qid;
456 		if(c.thdr.mode & OTRUNC)
457 			iget(&ic, mf->qid);
458 	}
459 }
460 
461 void
462 rcreate(Mfile *mf)
463 {
464 	if(statson && ctltest(mf)){
465 		sendreply("exists");
466 		return;
467 	}
468 	if(delegate() == 0){
469 		mf->qid = s.rhdr.qid;
470 		mf->qid.vers++;
471 	}
472 }
473 
474 void
475 rclunk(Mfile *mf)
476 {
477 	if(!mf->busy){
478 		sendreply(0);
479 		return;
480 	}
481 	mf->busy = 0;
482 	if(statson && ctltest(mf)){
483 		sendreply(0);
484 		return;
485 	}
486 	if(statson){
487 		cfsstat.sm[Tclunk].n++;
488 		cfsstat.sm[Tclunk].s = nsec();
489 	}
490 	/* forward clunk to server */
491 	sendmsg(&s, &c.thdr);
492 	/* record outstanding rclunk from server */
493 	putclunk(c.thdr.fid, c.thdr.tag);
494 	/* reply immediately */
495 	sendreply(0);
496 }
497 
498 void
499 rremove(Mfile *mf)
500 {
501 	if(statson && ctltest(mf)){
502 		sendreply("not removed");
503 		return;
504 	}
505 	mf->busy = 0;
506 	delegate();
507 }
508 
509 void
510 rread(Mfile *mf)
511 {
512 	int cnt;
513 	long off, first;
514 	char *cp;
515 	Ibuf *b;
516 	long n;
517 	char data[MAXFDATA];
518 	int done;
519 
520 	first = off = c.thdr.offset;
521 	cnt = c.thdr.count;
522 
523 	if(statson && ctltest(mf)){
524 		if(cnt > statlen-off)
525 			c.rhdr.count = statlen-off;
526 		else
527 			c.rhdr.count = cnt;
528 		if(c.rhdr.count < 0){
529 			sendreply("eof");
530 			return;
531 		}
532 		c.rhdr.data = statbuf + off;
533 		sendreply(0);
534 		return;
535 	}
536 	if(mf->qid.type & (QTDIR|QTAUTH)){
537 		delegate();
538 		if (statson) {
539 			cfsstat.ndirread++;
540 			if(c.rhdr.count > 0){
541 				cfsstat.bytesread += c.rhdr.count;
542 				cfsstat.bytesfromdirs += c.rhdr.count;
543 			}
544 		}
545 		return;
546 	}
547 
548 	b = iget(&ic, mf->qid);
549 	if(b == 0){
550 		DPRINT(2, "delegating read\n");
551 		delegate();
552 		if (statson){
553 			cfsstat.ndelegateread++;
554 			if(c.rhdr.count > 0){
555 				cfsstat.bytesread += c.rhdr.count;
556 				cfsstat.bytesfromserver += c.rhdr.count;
557 			}
558 		}
559 		return;
560 	}
561 
562 	cp = data;
563 	done = 0;
564 	while(cnt>0 && !done){
565 		if(off >= b->inode.length){
566 			DPRINT(2, "offset %ld greater than length %lld\n", off, b->inode.length);
567 			break;
568 		}
569 		n = fread(&ic, b, cp, off, cnt);
570 		if(n <= 0){
571 			n = -n;
572 			if(n==0 || n>cnt)
573 				n = cnt;
574 			DPRINT(2, "fetch %ld bytes of data from server at offset %ld\n", n, off);
575 			s.thdr.type = c.thdr.type;
576 			s.thdr.fid = c.thdr.fid;
577 			s.thdr.tag = c.thdr.tag;
578 			s.thdr.offset = off;
579 			s.thdr.count = n;
580 			if(statson){
581 				cfsstat.ndelegateread++;
582 			}
583 			if(askserver() < 0){
584 				sendreply(s.rhdr.ename);
585 				return;
586 			}
587 			if(s.rhdr.count != n)
588 				done = 1;
589 			n = s.rhdr.count;
590 			if(n == 0){
591 				/* end of file */
592 				if(b->inode.length > off){
593 					DPRINT(2, "file %llud.%ld, length %ld\n",
594 						b->inode.qid.path, b->inode.qid.vers, off);
595 					b->inode.length = off;
596 				}
597 				break;
598 			}
599 			memmove(cp, s.rhdr.data, n);
600 			fwrite(&ic, b, cp, off, n);
601 			if (statson){
602 				cfsstat.bytestocache += n;
603 				cfsstat.bytesfromserver += n;
604 			}
605 		}else{
606 			DPRINT(2, "fetched %ld bytes from cache\n", n);
607 			if(statson){
608 				cfsstat.bytesfromcache += n;
609 			}
610 		}
611 		cnt -= n;
612 		off += n;
613 		cp += n;
614 	}
615 	c.rhdr.data = data;
616 	c.rhdr.count = off - first;
617 	if(statson){
618 		cfsstat.bytesread += c.rhdr.count;
619 	}
620 	sendreply(0);
621 }
622 
623 void
624 rwrite(Mfile *mf)
625 {
626 	Ibuf *b;
627 	char buf[MAXFDATA];
628 
629 	if(statson && ctltest(mf)){
630 		sendreply("read only");
631 		return;
632 	}
633 	if(mf->qid.type & (QTDIR|QTAUTH)){
634 		delegate();
635 		if(statson && c.rhdr.count > 0)
636 			cfsstat.byteswritten += c.rhdr.count;
637 		return;
638 	}
639 
640 	memmove(buf, c.thdr.data, c.thdr.count);
641 	if(delegate() < 0)
642 		return;
643 
644 	if(s.rhdr.count > 0)
645 		cfsstat.byteswritten += s.rhdr.count;
646 	if(mf->qid.type & QTAPPEND)	/* don't modify our cache for append-only data; always read from server*/
647 		return;
648 	b = iget(&ic, mf->qid);
649 	if(b == 0)
650 		return;
651 	if (b->inode.length < c.thdr.offset + s.rhdr.count)
652 		b->inode.length = c.thdr.offset + s.rhdr.count;
653 	mf->qid.vers++;
654 	if (s.rhdr.count != c.thdr.count)
655 		syslog(0, "cfslog", "rhdr.count %ud, thdr.count %ud\n",
656 			s.rhdr.count, c.thdr.count);
657 	if(fwrite(&ic, b, buf, c.thdr.offset, s.rhdr.count) == s.rhdr.count){
658 		iinc(&ic, b);
659 		if(statson)
660 			cfsstat.bytestocache += s.rhdr.count;
661 	}
662 }
663 
664 void
665 rstat(Mfile *mf)
666 {
667 	Dir d;
668 
669 	if(statson && ctltest(mf)){
670 		genstats();
671 		d.qid = ctlqid;
672 		d.mode = 0444;
673 		d.length = statlen;	/* would be nice to do better */
674 		d.name = "cfsctl";
675 		d.uid = "none";
676 		d.gid = "none";
677 		d.muid = "none";
678 		d.atime = time(nil);
679 		d.mtime = d.atime;
680 		c.rhdr.nstat = convD2M(&d, c.rhdr.stat, sizeof(c.rhdr) - (c.rhdr.stat - (uchar*)&c.rhdr));
681 		sendreply(0);
682 		return;
683 	}
684 	if(delegate() == 0){
685 		Ibuf *b;
686 
687 		convM2D(s.rhdr.stat, s.rhdr.nstat , &d, nil);
688 		mf->qid = d.qid;
689 		b = iget(&ic, mf->qid);
690 		if(b)
691 			b->inode.length = d.length;
692 	}
693 }
694 
695 void
696 rwstat(Mfile *mf)
697 {
698 	Ibuf *b;
699 
700 	if(statson && ctltest(mf)){
701 		sendreply("read only");
702 		return;
703 	}
704 	delegate();
705 	if(b = iget(&ic, mf->qid))
706 		b->inode.length = MAXLEN;
707 }
708 
709 void
710 error(char *fmt, ...) {
711 	va_list arg;
712 	static char buf[2048];
713 
714 	va_start(arg, fmt);
715 	vseprint(buf, buf+sizeof(buf), fmt, arg);
716 	va_end(arg);
717 	fprint(2, "%s: %s\n", argv0, buf);
718 	exits("error");
719 }
720 
721 void
722 warning(char *s)
723 {
724 	fprint(2, "cfs: %s: %r\n", s);
725 }
726 
727 /*
728  *  send a reply to the client
729  */
730 void
731 sendreply(char *err)
732 {
733 
734 	if(err){
735 		c.rhdr.type = Rerror;
736 		c.rhdr.ename = err;
737 	}else{
738 		c.rhdr.type = c.thdr.type+1;
739 		c.rhdr.fid = c.thdr.fid;
740 	}
741 	c.rhdr.tag = c.thdr.tag;
742 	sendmsg(&c, &c.rhdr);
743 }
744 
745 /*
746  *  send a request to the server, get the reply, and send that to
747  *  the client
748  */
749 int
750 delegate(void)
751 {
752 	int type;
753 
754 	type = c.thdr.type;
755 	if(statson){
756 		cfsstat.sm[type].n++;
757 		cfsstat.sm[type].s = nsec();
758 	}
759 
760 	sendmsg(&s, &c.thdr);
761 	rcvmsg(&s, &s.rhdr);
762 
763 	if(statson){
764 		cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
765 	}
766 
767 	sendmsg(&c, &s.rhdr);
768 	return c.thdr.type+1 == s.rhdr.type ? 0 : -1;
769 }
770 
771 /*
772  *  send a request to the server and get a reply
773  */
774 int
775 askserver(void)
776 {
777 	int type;
778 
779 	s.thdr.tag = c.thdr.tag;
780 
781 	type = s.thdr.type;
782 	if(statson){
783 		cfsstat.sm[type].n++;
784 		cfsstat.sm[type].s = nsec();
785 	}
786 
787 	sendmsg(&s, &s.thdr);
788 	rcvmsg(&s, &s.rhdr);
789 
790 	if(statson){
791 		cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
792 	}
793 
794 	return s.thdr.type+1 == s.rhdr.type ? 0 : -1;
795 }
796 
797 /*
798  *  send/receive messages with logging
799  */
800 void
801 sendmsg(P9fs *p, Fcall *f)
802 {
803 	DPRINT(2, "->%s: %F\n", p->name, f);
804 
805 	p->len = convS2M(f, datasnd, messagesize);
806 	if(p->len <= 0)
807 		error("convS2M");
808 	if(write(p->fd[1], datasnd, p->len)!=p->len)
809 		error("sendmsg");
810 }
811 
812 void
813 dump(uchar *p, int len)
814 {
815 	fprint(2, "%d bytes", len);
816 	while(len > 0){
817 		fprint(2, " %.2ux", *p++);
818 		len--;
819 	}
820 	fprint(2, "\n");
821 }
822 
823 u32int
824 rcvclunk(P9fs *p, Fcall *f)
825 {
826 	Clunkitem *ci;
827 	int rlen;
828 	char buf[128];
829 	u32int fid;
830 
831 	p->len = read9pmsg(p->fd[0], datarcv, sizeof(datarcv));
832 	if(p->len <= 0){
833 		sprint(buf, "read9pmsg(%d)->%ld: %r", p->fd[0], p->len);
834 		error(buf);
835 	}
836 
837 	if((rlen = convM2S(datarcv, p->len, f)) != p->len)
838 		error("rcvmsg format error, expected length %d, got %d", rlen, p->len);
839 	if(f->type != Rclunk && f->type != Rerror)
840 		sysfatal("rcvclunk: unexpected message, type %d", f->type);
841 	if((ci = getclunk(NOFID, f->tag)) == nil)
842 		sysfatal("rcvclunk: unexpected clunk, %d", f->tag);
843 	fid = ci->fid;
844 	free(ci);
845 	return fid;
846 }
847 
848 void
849 rcvmsg(P9fs *p, Fcall *f)
850 {
851 	Clunkitem *ci;
852 	int olen, rlen;
853 	char buf[128];
854 
855 	olen = p->len;
856 again:
857 	p->len = read9pmsg(p->fd[0], datarcv, sizeof(datarcv));
858 	if(p->len <= 0){
859 		sprint(buf, "read9pmsg(%d)->%ld: %r", p->fd[0], p->len);
860 		error(buf);
861 	}
862 
863 	if((rlen = convM2S(datarcv, p->len, f)) != p->len)
864 		error("rcvmsg format error, expected length %d, got %d", rlen, p->len);
865 	if(f->type == Rclunk){
866 		if(ci = getclunk(NOFID, f->tag))
867 			free(ci);
868 		goto again;
869 	}
870 	if(f->type == Rerror && (ci = getclunk(NOFID, f->tag))){
871 		free(ci);
872 		goto again;
873 	}
874 	if(f->fid<0 || Nfid<=f->fid){
875 		fprint(2, "<-%s: %d %s on %d\n", p->name, f->type,
876 			mname[f->type]? mname[f->type] : "mystery",
877 			f->fid);
878 		dump((uchar*)datasnd, olen);
879 		dump((uchar*)datarcv, p->len);
880 		error("rcvmsg fid out of range");
881 	}
882 	DPRINT(2, "<-%s: %F\n", p->name, f);
883 }
884 
885 int
886 ctltest(Mfile *mf)
887 {
888 	return mf->busy && mf->qid.type == ctlqid.type && mf->qid.path == ctlqid.path;
889 }
890 
891 void
892 genstats(void)
893 {
894 	int i;
895 	char *p;
896 
897 	p = statbuf;
898 
899 	p += snprint(p, sizeof(statbuf)+statbuf-p, "        Client                          Server\n");
900 	p += snprint(p, sizeof(statbuf)+statbuf-p, "   #calls     Δ  ms/call    Δ      #calls     Δ  ms/call    Δ\n");
901 	for (i = 0; i < nelem(cfsstat.cm); i++)
902 		if(cfsstat.cm[i].n || cfsstat.sm[i].n) {
903 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ",
904 				cfsstat.cm[i].n, cfsstat.cm[i].n - cfsprev.cm[i].n);
905 			if (cfsstat.cm[i].n)
906 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
907 					0.000001*cfsstat.cm[i].t/cfsstat.cm[i].n);
908 			else
909 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
910 			if(cfsstat.cm[i].n - cfsprev.cm[i].n)
911 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
912 					0.000001*(cfsstat.cm[i].t - cfsprev.cm[i].t)/(cfsstat.cm[i].n - cfsprev.cm[i].n));
913 			else
914 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
915 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ",
916 				cfsstat.sm[i].n, cfsstat.sm[i].n - cfsprev.sm[i].n);
917 			if (cfsstat.sm[i].n)
918 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
919 					0.000001*cfsstat.sm[i].t/cfsstat.sm[i].n);
920 			else
921 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
922 			if(cfsstat.sm[i].n - cfsprev.sm[i].n)
923 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
924 					0.000001*(cfsstat.sm[i].t - cfsprev.sm[i].t)/(cfsstat.sm[i].n - cfsprev.sm[i].n));
925 			else
926 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
927 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%s\n", mname[i]);
928 		}
929 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndirread\n",
930 		cfsstat.ndirread, cfsstat.ndirread - cfsprev.ndirread);
931 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndelegateread\n",
932 		cfsstat.ndelegateread, cfsstat.ndelegateread - cfsprev.ndelegateread);
933 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ninsert\n",
934 		cfsstat.ninsert, cfsstat.ninsert - cfsprev.ninsert);
935 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndelete\n",
936 		cfsstat.ndelete, cfsstat.ndelete - cfsprev.ndelete);
937 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud nupdate\n",
938 		cfsstat.nupdate, cfsstat.nupdate - cfsprev.nupdate);
939 
940 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesread\n",
941 		cfsstat.bytesread, cfsstat.bytesread - cfsprev.bytesread);
942 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud byteswritten\n",
943 		cfsstat.byteswritten, cfsstat.byteswritten - cfsprev.byteswritten);
944 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromserver\n",
945 		cfsstat.bytesfromserver, cfsstat.bytesfromserver - cfsprev.bytesfromserver);
946 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromdirs\n",
947 		cfsstat.bytesfromdirs, cfsstat.bytesfromdirs - cfsprev.bytesfromdirs);
948 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromcache\n",
949 		cfsstat.bytesfromcache, cfsstat.bytesfromcache - cfsprev.bytesfromcache);
950 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytestocache\n",
951 		cfsstat.bytestocache, cfsstat.bytestocache - cfsprev.bytestocache);
952 	statlen = p - statbuf;
953 	cfsprev = cfsstat;
954 }
955 
956 void
957 putclunk(u32int fid, ushort tag)
958 {
959 	Clunkitem *c;
960 
961 	c = malloc(sizeof(Clunkitem));
962 	if(c == nil)
963 		sysfatal("putclunk: malloc: %r");
964 	c->fid = fid;
965 	c->tag = tag;
966 	c->next = clunklist;
967 	clunklist = c;
968 }
969 
970 Clunkitem *
971 getclunk(u32int fid, ushort tag)
972 {
973 	Clunkitem **p, *c;
974 
975 	for(p = &clunklist; c = *p; p = &c->next){
976 		if(fid != c->fid && tag != c->tag)
977 			continue;
978 		*p = c->next;
979 		return c;
980 	}
981 	return nil;
982 }
983 
984 Clunkitem *
985 notclunked(u32int fid, ushort tag)
986 {
987 	Clunkitem *c;
988 
989 	for(c = clunklist; c != nil; c = c->next)
990 		if(fid == c->fid || tag == c->tag)
991 			return c;
992 	return nil;
993 }
994