Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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