kern/62278: NFS server may not set eof flag when reading last chunk of file

Xiaolin Zang czang at panasas.FreeBSD.ORG
Mon Feb 2 10:50:37 PST 2004

>Number:         62278
>Category:       kern
>Synopsis:       NFS server may not set eof flag when reading last chunk of file
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Mon Feb 02 10:50:18 PST 2004
>Originator:     Xiaolin Zang
>Release:        4.6.2
Panasas, Inc
FreeBSD rack-bsd14 4.6.2-RELEASE FreeBSD 4.6.2-RELEASE #0: Wed Aug 14 21:23:26 GMT 2002     murray at  i386

According to RCF 1813 (NFS v3 spec), when the read has reached the end
of the file and it should set the eof flag to true in the reply.
However in freebsd 4.6.2 (may be the newer versions, too) the eof flag
is not set when it should be in the following cases.

Suppose the number of bytes requested by the client is N and (N mod 4) == 0.
And the read is getting the last chunk of the file and the size of
this chunk is M.  (N - M) is 1, 2 or 3.

The cause is the following lines near the end of the nfsrv_read function.

 	if (v3) {
 		*tl++ = txdr_unsigned(cnt);
		if (len < reqlen)   /*
 			*tl++ = nfs_true;  /*
 			*tl++ = nfs_false;

Note variable len is the round-up (to a 4-byte word) of the actually
number of bytes read (in variable cnt).

This was found when using RH-8.0 2.4.20-19.8 as the NFS client against
a FreeBsd NFS server since that RH Linux uses the eof flag to check
the correctness of a read when fewer bytes are read than requested.
The RH NFS client always asks for chunks of 4096 bytes.  Thus reading
(cat, cp, etc) any file of size (N * 4096 - n) where N > 0 and n is 1,
2 or 3 yields input/output error.  But the same operation will succeed
if repeated immediately, probably because the chunks are in the cache
The following change should fix the problem.

==== freebsd/src/sys/nfs/nfs_serv.c#3 - freebsd/src/sys/nfs/nfs_serv.c ====
@@ -785,7 +785,7 @@
 	struct uio io, *uiop = &io;
 	struct vattr va, *vap = &va;
 	struct nfsheur *nh;
-	off_t off;
+	off_t off, initial_off;
 	int ioflag = 0;
 	u_quad_t frev;
@@ -794,10 +794,10 @@
 	if (v3) {
 		nfsm_dissect(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
-		off = fxdr_hyper(tl);
+		initial_off = off = fxdr_hyper(tl);
 	} else {
 		nfsm_dissect(tl, u_int32_t *, NFSX_UNSIGNED);
-		off = (off_t)fxdr_unsigned(u_int32_t, *tl);
+		initial_off = off = (off_t)fxdr_unsigned(u_int32_t, *tl);
 	nfsm_srvstrsiz(reqlen, NFS_SRVMAXDATA(nfsd));
@@ -991,7 +991,7 @@
 		nfsm_adj(mb, len - tlen, tlen - cnt);
 	if (v3) {
 		*tl++ = txdr_unsigned(cnt);
-		if (len < reqlen)
+		if (initial_off + cnt >= vap->va_size)
 			*tl++ = nfs_true;
 			*tl++ = nfs_false;

By the way I have made a robot that can read the image and enter it in 
the little yellow space -- just kidding.

More information about the freebsd-bugs mailing list