From: Neil Brown <neilb@cse.unsw.edu.au>

It is (should be) an invariant that cd->buffer is always inside one of the
pages in rq_respages.  If a particular entry won't fit, it is not added and
nfserr_toosmall is returned there.

I say "should" because there is a '<=' which should be a '<', so it is
possible that if the entry fits exactly in the page, cd->buffer will be
left pointing off the end of the page, and so the invariant won't be true.

While this won't happen often, it is more likely to happen with larger
directories, which seems to be a common theme.

There is another problem in this code - num_entry_words isn't set properly
if there is a problem getting the filehandle for a file.

I'm not sure if this has been a problem or not.

Anyway, here is the patch.  I don't have a test case that breaks without
this patch, but a large directory can be listed from Solaris (which uses
readdir-plus) with this patch, so I don't think I've broken anything.

1/ make sure cd->buffer is always inside a page - previously if an
  entry fit perfectly in the remainder of a page, cd->buffer would 
  end up pointing past the end of that page.

2/ make sure num_entry_words is always correct, even on the error
  path.



---

 25-akpm/fs/nfsd/nfs3xdr.c |   16 ++++++----------
 1 files changed, 6 insertions(+), 10 deletions(-)

diff -puN fs/nfsd/nfs3xdr.c~nfsd-readdir_plus-fix fs/nfsd/nfs3xdr.c
--- 25/fs/nfsd/nfs3xdr.c~nfsd-readdir_plus-fix	2004-03-28 17:18:49.046404728 -0800
+++ 25-akpm/fs/nfsd/nfs3xdr.c	2004-03-28 17:18:49.049404272 -0800
@@ -884,10 +884,11 @@ encode_entry(struct readdir_cd *ccd, con
 		if (plus) {
 			struct svc_fh	fh;
 
-			if (compose_entry_fh(cd, &fh, name, namlen) > 0)
-				goto noexec;
-
-			p = encode_entryplus_baggage(cd, p, &fh);
+			if (compose_entry_fh(cd, &fh, name, namlen) > 0) {
+				*p++ = 0;
+				*p++ = 0;
+			} else
+				p = encode_entryplus_baggage(cd, p, &fh);
 		}
 		num_entry_words = p - cd->buffer;
 	} else if (cd->rqstp->rq_respages[pn+1] != NULL) {
@@ -916,7 +917,7 @@ encode_entry(struct readdir_cd *ccd, con
 		/* determine entry word length and lengths to go in pages */
 		num_entry_words = p1 - tmp;
 		len1 = curr_page_addr + PAGE_SIZE - (caddr_t)cd->buffer;
-		if ((num_entry_words << 2) <= len1) {
+		if ((num_entry_words << 2) < len1) {
 			/* the actual number of words in the entry is less
 			 * than elen and can still fit in the current page
 			 */
@@ -945,16 +946,11 @@ encode_entry(struct readdir_cd *ccd, con
 		return -EINVAL;
 	}
 
-out:
 	cd->buflen -= num_entry_words;
 	cd->buffer = p;
 	cd->common.err = nfs_ok;
 	return 0;
 
-noexec:
-	*p++ = 0;
-	*p++ = 0;
-	goto out;
 }
 
 int

_