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