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