]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/cfs/cfs.c
webfs(4): document -d and -D flags
[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                 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
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
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
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
364 rflush(void)            /* synchronous so easy */
365 {
366         sendreply(0);
367 }
368
369 void
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
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
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                 genstats();
442                 ctlqid.vers++;
443                 c.rhdr.qid = ctlqid;
444                 c.rhdr.iounit = 0;
445                 sendreply(0);
446                 return;
447         }
448         if(delegate() == 0){
449                 mf->qid = s.rhdr.qid;
450                 if(c.thdr.mode & OTRUNC)
451                         iget(&ic, mf->qid);
452         }
453 }
454
455 void
456 rcreate(Mfile *mf)
457 {
458         if(statson && ctltest(mf)){
459                 sendreply("exists");
460                 return;
461         }
462         if(delegate() == 0){
463                 mf->qid = s.rhdr.qid;
464                 mf->qid.vers++;
465         }
466 }
467
468 void
469 rclunk(Mfile *mf)
470 {
471         if(!mf->busy){
472                 sendreply(0);
473                 return;
474         }
475         mf->busy = 0;
476         delegate();
477 }
478
479 void
480 rremove(Mfile *mf)
481 {
482         if(statson && ctltest(mf)){
483                 sendreply("not removed");
484                 return;
485         }
486         mf->busy = 0;
487         delegate();
488 }
489
490 void
491 rread(Mfile *mf)
492 {
493         int cnt, done;
494         long n;
495         vlong off, first;
496         char *cp;
497         char data[MAXFDATA];
498         Ibuf *b;
499
500         off = c.thdr.offset;
501         first = off;
502         cnt = c.thdr.count;
503
504         if(statson && ctltest(mf)){
505                 if(cnt > statlen-off)
506                         c.rhdr.count = statlen-off;
507                 else
508                         c.rhdr.count = cnt;
509                 if((int)c.rhdr.count < 0){
510                         sendreply("eof");
511                         return;
512                 }
513                 c.rhdr.data = statbuf + off;
514                 sendreply(0);
515                 return;
516         }
517         if(mf->qid.type & (QTDIR|QTAUTH)){
518                 delegate();
519                 if (statson) {
520                         cfsstat.ndirread++;
521                         if(c.rhdr.count > 0){
522                                 cfsstat.bytesread += c.rhdr.count;
523                                 cfsstat.bytesfromdirs += c.rhdr.count;
524                         }
525                 }
526                 return;
527         }
528
529         b = iget(&ic, mf->qid);
530         if(b == 0){
531                 DPRINT(2, "delegating read\n");
532                 delegate();
533                 if (statson){
534                         cfsstat.ndelegateread++;
535                         if(c.rhdr.count > 0){
536                                 cfsstat.bytesread += c.rhdr.count;
537                                 cfsstat.bytesfromserver += c.rhdr.count;
538                         }
539                 }
540                 return;
541         }
542
543         cp = data;
544         done = 0;
545         while(cnt>0 && !done){
546                 if(off >= b->inode.length){
547                         DPRINT(2, "offset %lld greater than length %lld\n",
548                                 off, b->inode.length);
549                         break;
550                 }
551                 n = fread(&ic, b, cp, off, cnt);
552                 if(n <= 0){
553                         n = -n;
554                         if(n==0 || n>cnt)
555                                 n = cnt;
556                         DPRINT(2,
557                          "fetch %ld bytes of data from server at offset %lld\n",
558                                 n, off);
559                         s.thdr.type = c.thdr.type;
560                         s.thdr.fid = c.thdr.fid;
561                         s.thdr.tag = c.thdr.tag;
562                         s.thdr.offset = off;
563                         s.thdr.count = n;
564                         if(statson)
565                                 cfsstat.ndelegateread++;
566                         if(askserver() < 0){
567                                 sendreply(s.rhdr.ename);
568                                 return;
569                         }
570                         if(s.rhdr.count != n)
571                                 done = 1;
572                         n = s.rhdr.count;
573                         if(n == 0){
574                                 /* end of file */
575                                 if(b->inode.length > off){
576                                         DPRINT(2, "file %llud.%ld, length %lld\n",
577                                                 b->inode.qid.path,
578                                                 b->inode.qid.vers, off);
579                                         b->inode.length = off;
580                                 }
581                                 break;
582                         }
583                         memmove(cp, s.rhdr.data, n);
584                         fwrite(&ic, b, cp, off, n);
585                         if (statson){
586                                 cfsstat.bytestocache += n;
587                                 cfsstat.bytesfromserver += n;
588                         }
589                 }else{
590                         DPRINT(2, "fetched %ld bytes from cache\n", n);
591                         if(statson)
592                                 cfsstat.bytesfromcache += n;
593                 }
594                 cnt -= n;
595                 off += n;
596                 cp += n;
597         }
598         c.rhdr.data = data;
599         c.rhdr.count = off - first;
600         if(statson)
601                 cfsstat.bytesread += c.rhdr.count;
602         sendreply(0);
603 }
604
605 void
606 rwrite(Mfile *mf)
607 {
608         Ibuf *b;
609         char buf[MAXFDATA];
610
611         if(statson && ctltest(mf)){
612                 sendreply("read only");
613                 return;
614         }
615         if(mf->qid.type & (QTDIR|QTAUTH)){
616                 delegate();
617                 if(statson && c.rhdr.count > 0)
618                         cfsstat.byteswritten += c.rhdr.count;
619                 return;
620         }
621
622         memmove(buf, c.thdr.data, c.thdr.count);
623         if(delegate() < 0)
624                 return;
625
626         if(s.rhdr.count > 0)
627                 cfsstat.byteswritten += s.rhdr.count;
628         /* don't modify our cache for append-only data; always read from server*/
629         if(mf->qid.type & QTAPPEND)
630                 return;
631         b = iget(&ic, mf->qid);
632         if(b == 0)
633                 return;
634         if (b->inode.length < c.thdr.offset + s.rhdr.count)
635                 b->inode.length = c.thdr.offset + s.rhdr.count;
636         mf->qid.vers++;
637         if (s.rhdr.count != c.thdr.count)
638                 syslog(0, "cfslog", "rhdr.count %ud, thdr.count %ud",
639                         s.rhdr.count, c.thdr.count);
640         if(fwrite(&ic, b, buf, c.thdr.offset, s.rhdr.count) == s.rhdr.count){
641                 iinc(&ic, b);
642                 if(statson)
643                         cfsstat.bytestocache += s.rhdr.count;
644         }
645 }
646
647 void
648 rstat(Mfile *mf)
649 {
650         uchar buf[STATMAX];
651         Dir d;
652
653         if(statson && ctltest(mf)){
654                 d.qid = ctlqid;
655                 d.mode = 0444;
656                 d.length = statlen;     /* would be nice to do better */
657                 d.name = "cfsctl";
658                 d.uid = "none";
659                 d.gid = "none";
660                 d.muid = "none";
661                 d.atime = time(nil);
662                 d.mtime = d.atime;
663                 c.rhdr.nstat = convD2M(&d, buf, sizeof buf);
664                 c.rhdr.stat = buf;
665                 sendreply(0);
666                 return;
667         }
668         if(delegate() == 0){
669                 Ibuf *b;
670
671                 convM2D(s.rhdr.stat, s.rhdr.nstat , &d, nil);
672                 mf->qid = d.qid;
673                 b = iget(&ic, mf->qid);
674                 if(b)
675                         b->inode.length = d.length;
676         }
677 }
678
679 void
680 rwstat(Mfile *mf)
681 {
682         Ibuf *b;
683
684         if(statson && ctltest(mf)){
685                 sendreply("read only");
686                 return;
687         }
688         delegate();
689         if(b = iget(&ic, mf->qid))
690                 b->inode.length = MAXLEN;
691 }
692
693 void
694 error(char *fmt, ...)
695 {
696         va_list arg;
697         static char buf[2048];
698
699         va_start(arg, fmt);
700         vseprint(buf, buf+sizeof(buf), fmt, arg);
701         va_end(arg);
702         fprint(2, "%s: %s\n", argv0, buf);
703         exits("error");
704 }
705
706 void
707 warning(char *s)
708 {
709         fprint(2, "cfs: %s: %r\n", s);
710 }
711
712 /*
713  *  send a reply to the client
714  */
715 void
716 sendreply(char *err)
717 {
718
719         if(err){
720                 c.rhdr.type = Rerror;
721                 c.rhdr.ename = err;
722         }else{
723                 c.rhdr.type = c.thdr.type+1;
724                 c.rhdr.fid = c.thdr.fid;
725         }
726         c.rhdr.tag = c.thdr.tag;
727         sendmsg(&c, &c.rhdr);
728 }
729
730 /*
731  *  send a request to the server, get the reply, and send that to
732  *  the client
733  */
734 int
735 delegate(void)
736 {
737         int type;
738
739         type = c.thdr.type;
740         if(statson){
741                 cfsstat.sm[type].n++;
742                 cfsstat.sm[type].s = nsec();
743         }
744
745         sendmsg(&s, &c.thdr);
746         rcvmsg(&s, &s.rhdr);
747
748         if(statson)
749                 cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
750
751         sendmsg(&c, &s.rhdr);
752         return c.thdr.type+1 == s.rhdr.type ? 0 : -1;
753 }
754
755 /*
756  *  send a request to the server and get a reply
757  */
758 int
759 askserver(void)
760 {
761         int type;
762
763         s.thdr.tag = c.thdr.tag;
764
765         type = s.thdr.type;
766         if(statson){
767                 cfsstat.sm[type].n++;
768                 cfsstat.sm[type].s = nsec();
769         }
770
771         sendmsg(&s, &s.thdr);
772         rcvmsg(&s, &s.rhdr);
773
774         if(statson)
775                 cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s;
776
777         return s.thdr.type+1 == s.rhdr.type ? 0 : -1;
778 }
779
780 /*
781  *  send/receive messages with logging
782  */
783 void
784 sendmsg(P9fs *p, Fcall *f)
785 {
786         DPRINT(2, "->%s: %F\n", p->name, f);
787
788         p->len = convS2M(f, datasnd, messagesize);
789         if(p->len <= 0)
790                 error("convS2M");
791         if(write(p->fd[1], datasnd, p->len)!=p->len)
792                 error("sendmsg");
793 }
794
795 void
796 dump(uchar *p, int len)
797 {
798         fprint(2, "%d bytes", len);
799         while(len-- > 0)
800                 fprint(2, " %.2ux", *p++);
801         fprint(2, "\n");
802 }
803
804 void
805 rcvmsg(P9fs *p, Fcall *f)
806 {
807         int olen, rlen;
808         char buf[128];
809
810         olen = p->len;
811         p->len = read9pmsg(p->fd[0], datarcv, sizeof(datarcv));
812         if(p->len == 0)
813                 exits("");
814         if(p->len < 0){
815                 snprint(buf, sizeof buf, "read9pmsg(%d)->%ld: %r",
816                         p->fd[0], p->len);
817                 error(buf);
818         }
819         if((rlen = convM2S(datarcv, p->len, f)) != p->len)
820                 error("rcvmsg format error, expected length %d, got %d",
821                         rlen, p->len);
822         if(f->fid >= Nfid){
823                 fprint(2, "<-%s: %d %s on %d\n", p->name, f->type,
824                         mname[f->type]? mname[f->type]: "mystery", f->fid);
825                 dump((uchar*)datasnd, olen);
826                 dump((uchar*)datarcv, p->len);
827                 error("rcvmsg fid out of range");
828         }
829         DPRINT(2, "<-%s: %F\n", p->name, f);
830 }
831
832 int
833 ctltest(Mfile *mf)
834 {
835         return mf->busy && mf->qid.type == ctlqid.type &&
836                 mf->qid.path == ctlqid.path;
837 }
838
839 void
840 genstats(void)
841 {
842         int i;
843         char *p;
844
845         p = statbuf;
846
847         p += snprint(p, sizeof statbuf+statbuf-p,
848                 "        Client                          Server\n");
849         p += snprint(p, sizeof statbuf+statbuf-p,
850             "   #calls     Δ  ms/call    Δ      #calls     Δ  ms/call    Δ\n");
851         for (i = 0; i < nelem(cfsstat.cm); i++)
852                 if(cfsstat.cm[i].n || cfsstat.sm[i].n) {
853                         p += snprint(p, sizeof statbuf+statbuf-p,
854                                 "%7lud %7lud ", cfsstat.cm[i].n,
855                                 cfsstat.cm[i].n - cfsprev.cm[i].n);
856                         if (cfsstat.cm[i].n)
857                                 p += snprint(p, sizeof statbuf+statbuf-p,
858                                         "%7.3f ", 0.000001*cfsstat.cm[i].t/
859                                         cfsstat.cm[i].n);
860                         else
861                                 p += snprint(p, sizeof statbuf+statbuf-p,
862                                         "        ");
863                         if(cfsstat.cm[i].n - cfsprev.cm[i].n)
864                                 p += snprint(p, sizeof statbuf+statbuf-p,
865                                         "%7.3f ", 0.000001*
866                                         (cfsstat.cm[i].t - cfsprev.cm[i].t)/
867                                         (cfsstat.cm[i].n - cfsprev.cm[i].n));
868                         else
869                                 p += snprint(p, sizeof statbuf+statbuf-p,
870                                         "        ");
871                         p += snprint(p, sizeof statbuf+statbuf-p,
872                                 "%7lud %7lud ", cfsstat.sm[i].n,
873                                 cfsstat.sm[i].n - cfsprev.sm[i].n);
874                         if (cfsstat.sm[i].n)
875                                 p += snprint(p, sizeof statbuf+statbuf-p,
876                                         "%7.3f ", 0.000001*cfsstat.sm[i].t/
877                                         cfsstat.sm[i].n);
878                         else
879                                 p += snprint(p, sizeof statbuf+statbuf-p,
880                                         "        ");
881                         if(cfsstat.sm[i].n - cfsprev.sm[i].n)
882                                 p += snprint(p, sizeof statbuf+statbuf-p,
883                                         "%7.3f ", 0.000001*
884                                         (cfsstat.sm[i].t - cfsprev.sm[i].t)/
885                                         (cfsstat.sm[i].n - cfsprev.sm[i].n));
886                         else
887                                 p += snprint(p, sizeof statbuf+statbuf-p,
888                                         "        ");
889                         p += snprint(p, sizeof statbuf+statbuf-p, "%s\n",
890                                 mname[i]);
891                 }
892         p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndirread\n",
893                 cfsstat.ndirread, cfsstat.ndirread - cfsprev.ndirread);
894         p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndelegateread\n",
895                 cfsstat.ndelegateread, cfsstat.ndelegateread -
896                 cfsprev.ndelegateread);
897         p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ninsert\n",
898                 cfsstat.ninsert, cfsstat.ninsert - cfsprev.ninsert);
899         p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndelete\n",
900                 cfsstat.ndelete, cfsstat.ndelete - cfsprev.ndelete);
901         p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud nupdate\n",
902                 cfsstat.nupdate, cfsstat.nupdate - cfsprev.nupdate);
903
904         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesread\n",
905                 cfsstat.bytesread, cfsstat.bytesread - cfsprev.bytesread);
906         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud byteswritten\n",
907                 cfsstat.byteswritten, cfsstat.byteswritten -
908                 cfsprev.byteswritten);
909         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromserver\n",
910                 cfsstat.bytesfromserver, cfsstat.bytesfromserver -
911                 cfsprev.bytesfromserver);
912         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromdirs\n",
913                 cfsstat.bytesfromdirs, cfsstat.bytesfromdirs -
914                 cfsprev.bytesfromdirs);
915         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromcache\n",
916                 cfsstat.bytesfromcache, cfsstat.bytesfromcache -
917                 cfsprev.bytesfromcache);
918         p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytestocache\n",
919                 cfsstat.bytestocache, cfsstat.bytestocache -
920                 cfsprev.bytestocache);
921         statlen = p - statbuf;
922         cfsprev = cfsstat;
923 }