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