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