xref: /plan9-contrib/sys/src/cmd/cfs/cfs.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
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 	Mfile *amf;
327 
328 	if(delegate() == 0){
329 		if (c.thdr.afid != NOFID){
330 			amf = &mfile[c.thdr.afid];
331 			if(amf->busy)
332 				error("rauth afid on used channel");
333 			amf->qid = s.rhdr.aqid;
334 			amf->busy = 1;
335 		}
336 		mf->qid = s.rhdr.qid;
337 		mf->busy = 1;
338 	}
339 }
340 
341 void
342 rflush(void)		/* synchronous so easy */
343 {
344 	sendreply(0);
345 }
346 
347 void
348 rattach(Mfile *mf)
349 {
350 	if(delegate() == 0){
351 		mf->qid = s.rhdr.qid;
352 		mf->busy = 1;
353 		if (statson == 1){
354 			statson++;
355 			rootqid = mf->qid;
356 		}
357 	}
358 }
359 
360 void
361 rwalk(Mfile *mf)
362 {
363 	Mfile *nmf;
364 
365 	nmf = nil;
366 	if(statson
367 	  && mf->qid.type == rootqid.type && mf->qid.path == rootqid.path
368 	  && c.thdr.nwname == 1 && strcmp(c.thdr.wname[0], "cfsctl") == 0){
369 		/* This is the ctl file */
370 		nmf = &mfile[c.thdr.newfid];
371 		if(c.thdr.newfid != c.thdr.fid && nmf->busy)
372 			error("clone to used channel");
373 		nmf = &mfile[c.thdr.newfid];
374 		nmf->qid = ctlqid;
375 		nmf->busy = 1;
376 		c.rhdr.nwqid = 1;
377 		c.rhdr.wqid[0] = ctlqid;
378 		sendreply(0);
379 		return;
380 	}
381 	if(c.thdr.newfid != c.thdr.fid){
382 		if(c.thdr.newfid<0 || Nfid<=c.thdr.newfid)
383 			error("clone nfid out of range");
384 		nmf = &mfile[c.thdr.newfid];
385 		if(nmf->busy)
386 			error("clone to used channel");
387 		nmf = &mfile[c.thdr.newfid];
388 		nmf->qid = mf->qid;
389 		nmf->busy = 1;
390 		mf = nmf; /* Walk mf */
391 	}
392 
393 	if(delegate() < 0){	/* complete failure */
394 		if(nmf)
395 			nmf->busy = 0;
396 		return;
397 	}
398 
399 	if(s.rhdr.nwqid == c.thdr.nwname){	/* complete success */
400 		if(s.rhdr.nwqid > 0)
401 			mf->qid = s.rhdr.wqid[s.rhdr.nwqid-1];
402 		return;
403 	}
404 
405 	/* partial success; release fid */
406 	if(nmf)
407 		nmf->busy = 0;
408 }
409 
410 void
411 ropen(Mfile *mf)
412 {
413 	if(statson && ctltest(mf)){
414 		/* Opening ctl file */
415 		if(c.thdr.mode != OREAD){
416 			sendreply("does not exist");
417 			return;
418 		}
419 		c.rhdr.qid = ctlqid;
420 		c.rhdr.iounit = 0;
421 		sendreply(0);
422 		genstats();
423 		return;
424 	}
425 	if(delegate() == 0){
426 		mf->qid = s.rhdr.qid;
427 		if(c.thdr.mode & OTRUNC)
428 			iget(&ic, mf->qid);
429 	}
430 }
431 
432 void
433 rcreate(Mfile *mf)
434 {
435 	if(statson && ctltest(mf)){
436 		sendreply("exists");
437 		return;
438 	}
439 	if(delegate() == 0){
440 		mf->qid = s.rhdr.qid;
441 		mf->qid.vers++;
442 	}
443 }
444 
445 void
446 rclunk(Mfile *mf)
447 {
448 	if(!mf->busy){
449 		sendreply(0);
450 		return;
451 	}
452 	mf->busy = 0;
453 	delegate();
454 }
455 
456 void
457 rremove(Mfile *mf)
458 {
459 	if(statson && ctltest(mf)){
460 		sendreply("not removed");
461 		return;
462 	}
463 	mf->busy = 0;
464 	delegate();
465 }
466 
467 void
468 rread(Mfile *mf)
469 {
470 	int cnt;
471 	long off, first;
472 	char *cp;
473 	Ibuf *b;
474 	long n;
475 	char data[MAXFDATA];
476 	int done;
477 
478 	first = off = c.thdr.offset;
479 	cnt = c.thdr.count;
480 
481 	if(statson && ctltest(mf)){
482 		if(cnt > statlen-off)
483 			c.rhdr.count = statlen-off;
484 		else
485 			c.rhdr.count = cnt;
486 		if(c.rhdr.count < 0){
487 			sendreply("eof");
488 			return;
489 		}
490 		c.rhdr.data = statbuf + off;
491 		sendreply(0);
492 		return;
493 	}
494 	if(mf->qid.type & (QTDIR|QTAUTH)){
495 		delegate();
496 		if (statson) {
497 			cfsstat.ndirread++;
498 			if(c.rhdr.count > 0){
499 				cfsstat.bytesread += c.rhdr.count;
500 				cfsstat.bytesfromdirs += c.rhdr.count;
501 			}
502 		}
503 		return;
504 	}
505 
506 	b = iget(&ic, mf->qid);
507 	if(b == 0){
508 		DPRINT(2, "delegating read\n");
509 		delegate();
510 		if (statson){
511 			cfsstat.ndelegateread++;
512 			if(c.rhdr.count > 0){
513 				cfsstat.bytesread += c.rhdr.count;
514 				cfsstat.bytesfromserver += c.rhdr.count;
515 			}
516 		}
517 		return;
518 	}
519 
520 	cp = data;
521 	done = 0;
522 	while(cnt>0 && !done){
523 		if(off >= b->inode.length){
524 			DPRINT(2, "offset %ld greater than length %lld\n", off, b->inode.length);
525 			break;
526 		}
527 		n = fread(&ic, b, cp, off, cnt);
528 		if(n <= 0){
529 			n = -n;
530 			if(n==0 || n>cnt)
531 				n = cnt;
532 			DPRINT(2, "fetch %ld bytes of data from server at offset %ld\n", n, off);
533 			s.thdr.type = c.thdr.type;
534 			s.thdr.fid = c.thdr.fid;
535 			s.thdr.tag = c.thdr.tag;
536 			s.thdr.offset = off;
537 			s.thdr.count = n;
538 			if(statson){
539 				cfsstat.ndelegateread++;
540 			}
541 			if(askserver() < 0){
542 				sendreply(s.rhdr.ename);
543 				return;
544 			}
545 			if(s.rhdr.count != n)
546 				done = 1;
547 			n = s.rhdr.count;
548 			if(n == 0){
549 				/* end of file */
550 				if(b->inode.length > off){
551 					DPRINT(2, "file %llud.%ld, length %ld\n",
552 						b->inode.qid.path, b->inode.qid.vers, off);
553 					b->inode.length = off;
554 				}
555 				break;
556 			}
557 			memmove(cp, s.rhdr.data, n);
558 			fwrite(&ic, b, cp, off, n);
559 			if (statson){
560 				cfsstat.bytestocache += n;
561 				cfsstat.bytesfromserver += n;
562 			}
563 		}else{
564 			DPRINT(2, "fetched %ld bytes from cache\n", n);
565 			if(statson){
566 				cfsstat.bytesfromcache += n;
567 			}
568 		}
569 		cnt -= n;
570 		off += n;
571 		cp += n;
572 	}
573 	c.rhdr.data = data;
574 	c.rhdr.count = off - first;
575 	if(statson){
576 		cfsstat.bytesread += c.rhdr.count;
577 	}
578 	sendreply(0);
579 }
580 
581 void
582 rwrite(Mfile *mf)
583 {
584 	Ibuf *b;
585 	char buf[MAXFDATA];
586 
587 	if(statson && ctltest(mf)){
588 		sendreply("read only");
589 		return;
590 	}
591 	if(mf->qid.type & (QTDIR|QTAUTH)){
592 		delegate();
593 		if(statson && c.rhdr.count > 0)
594 			cfsstat.byteswritten += c.rhdr.count;
595 		return;
596 	}
597 
598 	memmove(buf, c.thdr.data, c.thdr.count);
599 	if(delegate() < 0)
600 		return;
601 
602 	if(s.rhdr.count > 0)
603 		cfsstat.byteswritten += s.rhdr.count;
604 	if(mf->qid.type & QTAPPEND)	/* don't modify our cache for append-only data; always read from server*/
605 		return;
606 	b = iget(&ic, mf->qid);
607 	if(b == 0)
608 		return;
609 	if (b->inode.length < c.thdr.offset + s.rhdr.count)
610 		b->inode.length = c.thdr.offset + s.rhdr.count;
611 	mf->qid.vers++;
612 	if (s.rhdr.count != c.thdr.count)
613 		syslog(0, "cfslog", "rhdr.count %ud, thdr.count %ud\n",
614 			s.rhdr.count, c.thdr.count);
615 	if(fwrite(&ic, b, buf, c.thdr.offset, s.rhdr.count) == s.rhdr.count){
616 		iinc(&ic, b);
617 		if(statson)
618 			cfsstat.bytestocache += s.rhdr.count;
619 	}
620 }
621 
622 void
623 rstat(Mfile *mf)
624 {
625 	Dir d;
626 
627 	if(statson && ctltest(mf)){
628 		genstats();
629 		d.qid = ctlqid;
630 		d.mode = 0444;
631 		d.length = statlen;	/* would be nice to do better */
632 		d.name = "cfsctl";
633 		d.uid = "none";
634 		d.gid = "none";
635 		d.muid = "none";
636 		d.atime = time(nil);
637 		d.mtime = d.atime;
638 		c.rhdr.nstat = convD2M(&d, c.rhdr.stat, sizeof(c.rhdr) - (c.rhdr.stat - (uchar*)&c.rhdr));
639 		sendreply(0);
640 		return;
641 	}
642 	if(delegate() == 0){
643 		Ibuf *b;
644 
645 		convM2D(s.rhdr.stat, s.rhdr.nstat , &d, nil);
646 		mf->qid = d.qid;
647 		b = iget(&ic, mf->qid);
648 		if(b)
649 			b->inode.length = d.length;
650 	}
651 }
652 
653 void
654 rwstat(Mfile *mf)
655 {
656 	Ibuf *b;
657 
658 	if(statson && ctltest(mf)){
659 		sendreply("read only");
660 		return;
661 	}
662 	delegate();
663 	if(b = iget(&ic, mf->qid))
664 		b->inode.length = MAXLEN;
665 }
666 
667 void
668 error(char *fmt, ...) {
669 	va_list arg;
670 	static char buf[2048];
671 
672 	va_start(arg, fmt);
673 	vseprint(buf, buf+sizeof(buf), fmt, arg);
674 	va_end(arg);
675 	fprint(2, "%s: %s\n", argv0, buf);
676 	exits("error");
677 }
678 
679 void
680 warning(char *s)
681 {
682 	fprint(2, "cfs: %s: %r\n", s);
683 }
684 
685 /*
686  *  send a reply to the client
687  */
688 void
689 sendreply(char *err)
690 {
691 
692 	if(err){
693 		c.rhdr.type = Rerror;
694 		c.rhdr.ename = err;
695 	}else{
696 		c.rhdr.type = c.thdr.type+1;
697 		c.rhdr.fid = c.thdr.fid;
698 	}
699 	c.rhdr.tag = c.thdr.tag;
700 	sendmsg(&c, &c.rhdr);
701 }
702 
703 /*
704  *  send a request to the server, get the reply, and send that to
705  *  the client
706  */
707 int
708 delegate(void)
709 {
710 	int type;
711 
712 	type = c.thdr.type;
713 	if(statson){
714 		cfsstat.sm[type].n++;
715 		cfsstat.sm[type].s = nsec();
716 	}
717 
718 	sendmsg(&s, &c.thdr);
719 	rcvmsg(&s, &s.rhdr);
720 
721 	if(statson){
722 		cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
723 	}
724 
725 	sendmsg(&c, &s.rhdr);
726 	return c.thdr.type+1 == s.rhdr.type ? 0 : -1;
727 }
728 
729 /*
730  *  send a request to the server and get a reply
731  */
732 int
733 askserver(void)
734 {
735 	int type;
736 
737 	s.thdr.tag = c.thdr.tag;
738 
739 	type = s.thdr.type;
740 	if(statson){
741 		cfsstat.sm[type].n++;
742 		cfsstat.sm[type].s = nsec();
743 	}
744 
745 	sendmsg(&s, &s.thdr);
746 	rcvmsg(&s, &s.rhdr);
747 
748 	if(statson){
749 		cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
750 	}
751 
752 	return s.thdr.type+1 == s.rhdr.type ? 0 : -1;
753 }
754 
755 /*
756  *  send/receive messages with logging
757  */
758 void
759 sendmsg(P9fs *p, Fcall *f)
760 {
761 	DPRINT(2, "->%s: %F\n", p->name, f);
762 
763 	p->len = convS2M(f, datasnd, messagesize);
764 	if(p->len <= 0)
765 		error("convS2M");
766 	if(write(p->fd[1], datasnd, p->len)!=p->len)
767 		error("sendmsg");
768 }
769 
770 void
771 dump(uchar *p, int len)
772 {
773 	fprint(2, "%d bytes", len);
774 	while(len > 0){
775 		fprint(2, " %.2ux", *p++);
776 		len--;
777 	}
778 	fprint(2, "\n");
779 }
780 
781 void
782 rcvmsg(P9fs *p, Fcall *f)
783 {
784 	int olen, rlen;
785 	char buf[128];
786 
787 	olen = p->len;
788 	p->len = read9pmsg(p->fd[0], datarcv, sizeof(datarcv));
789 	if(p->len <= 0){
790 		sprint(buf, "read9pmsg(%d)->%ld: %r", p->fd[0], p->len);
791 		error(buf);
792 	}
793 
794 	if((rlen = convM2S(datarcv, p->len, f)) != p->len)
795 		error("rcvmsg format error, expected length %d, got %d", rlen, p->len);
796 	if(f->fid<0 || Nfid<=f->fid){
797 		fprint(2, "<-%s: %d %s on %d\n", p->name, f->type,
798 			mname[f->type]? mname[f->type] : "mystery",
799 			f->fid);
800 		dump((uchar*)datasnd, olen);
801 		dump((uchar*)datarcv, p->len);
802 		error("rcvmsg fid out of range");
803 	}
804 	DPRINT(2, "<-%s: %F\n", p->name, f);
805 }
806 
807 int
808 ctltest(Mfile *mf)
809 {
810 	return mf->busy && mf->qid.type == ctlqid.type && mf->qid.path == ctlqid.path;
811 }
812 
813 void
814 genstats(void)
815 {
816 	int i;
817 	char *p;
818 
819 	p = statbuf;
820 
821 	p += snprint(p, sizeof(statbuf)+statbuf-p, "        Client                          Server\n");
822 	p += snprint(p, sizeof(statbuf)+statbuf-p, "   #calls     Δ  ms/call    Δ      #calls     Δ  ms/call    Δ\n");
823 	for (i = 0; i < nelem(cfsstat.cm); i++)
824 		if(cfsstat.cm[i].n || cfsstat.sm[i].n) {
825 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ",
826 				cfsstat.cm[i].n, cfsstat.cm[i].n - cfsprev.cm[i].n);
827 			if (cfsstat.cm[i].n)
828 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
829 					0.000001*cfsstat.cm[i].t/cfsstat.cm[i].n);
830 			else
831 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
832 			if(cfsstat.cm[i].n - cfsprev.cm[i].n)
833 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
834 					0.000001*(cfsstat.cm[i].t - cfsprev.cm[i].t)/(cfsstat.cm[i].n - cfsprev.cm[i].n));
835 			else
836 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
837 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ",
838 				cfsstat.sm[i].n, cfsstat.sm[i].n - cfsprev.sm[i].n);
839 			if (cfsstat.sm[i].n)
840 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
841 					0.000001*cfsstat.sm[i].t/cfsstat.sm[i].n);
842 			else
843 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
844 			if(cfsstat.sm[i].n - cfsprev.sm[i].n)
845 				p += snprint(p, sizeof(statbuf)+statbuf-p, "%7.3f ",
846 					0.000001*(cfsstat.sm[i].t - cfsprev.sm[i].t)/(cfsstat.sm[i].n - cfsprev.sm[i].n));
847 			else
848 				p += snprint(p, sizeof(statbuf)+statbuf-p, "        ");
849 			p += snprint(p, sizeof(statbuf)+statbuf-p, "%s\n", mname[i]);
850 		}
851 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndirread\n",
852 		cfsstat.ndirread, cfsstat.ndirread - cfsprev.ndirread);
853 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndelegateread\n",
854 		cfsstat.ndelegateread, cfsstat.ndelegateread - cfsprev.ndelegateread);
855 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ninsert\n",
856 		cfsstat.ninsert, cfsstat.ninsert - cfsprev.ninsert);
857 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud ndelete\n",
858 		cfsstat.ndelete, cfsstat.ndelete - cfsprev.ndelete);
859 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud nupdate\n",
860 		cfsstat.nupdate, cfsstat.nupdate - cfsprev.nupdate);
861 
862 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesread\n",
863 		cfsstat.bytesread, cfsstat.bytesread - cfsprev.bytesread);
864 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud byteswritten\n",
865 		cfsstat.byteswritten, cfsstat.byteswritten - cfsprev.byteswritten);
866 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromserver\n",
867 		cfsstat.bytesfromserver, cfsstat.bytesfromserver - cfsprev.bytesfromserver);
868 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromdirs\n",
869 		cfsstat.bytesfromdirs, cfsstat.bytesfromdirs - cfsprev.bytesfromdirs);
870 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytesfromcache\n",
871 		cfsstat.bytesfromcache, cfsstat.bytesfromcache - cfsprev.bytesfromcache);
872 	p += snprint(p, sizeof(statbuf)+statbuf-p, "%7lud %7lud bytestocache\n",
873 		cfsstat.bytestocache, cfsstat.bytestocache - cfsprev.bytestocache);
874 	statlen = p - statbuf;
875 	cfsprev = cfsstat;
876 }
877