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