patch-2.2.18 linux/fs/nfs/nfs2xdr.c

Next file: linux/fs/nfs/nfs3proc.c
Previous file: linux/fs/nfs/mount_clnt.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/fs/nfs/nfs2xdr.c linux/fs/nfs/nfs2xdr.c
@@ -1,13 +1,15 @@
 /*
- * linux/fs/nfs/xdr.c
+ * linux/fs/nfs/nfs2xdr.c
  *
  * XDR functions to encode/decode NFS RPC arguments and results.
  *
  * Copyright (C) 1992, 1993, 1994  Rick Sladkey
  * Copyright (C) 1996 Olaf Kirch
+ * 04 Aug 1998  Ion Badulescu <ionut@cs.columbia.edu>
+ * 		FIFO's need special handling in NFSv2
  */
 
-#define NFS_NEED_XDR_TYPES
+#define NFS_NEED_NFS2_XDR_TYPES
 
 #include <linux/param.h>
 #include <linux/sched.h>
@@ -20,6 +22,8 @@
 #include <linux/pagemap.h>
 #include <linux/proc_fs.h>
 #include <linux/sunrpc/clnt.h>
+#include <linux/nfs.h>
+#include <linux/nfs2.h>
 #include <linux/nfs_fs.h>
 
 /* Uncomment this to support servers requiring longword lengths */
@@ -28,8 +32,7 @@
 #define NFSDBG_FACILITY		NFSDBG_XDR
 /* #define NFS_PARANOIA 1 */
 
-#define QUADLEN(len)		(((len) + 3) >> 2)
-static int			nfs_stat_to_errno(int stat);
+extern int			nfs_stat_to_errno(int stat);
 
 /* Mapping from NFS error code to "errno" error code. */
 #define errno_NFSERR_IO		EIO
@@ -40,8 +43,8 @@
  */
 #define NFS_fhandle_sz		8
 #define NFS_sattr_sz		8
-#define NFS_filename_sz		1+(NFS_MAXNAMLEN>>2)
-#define NFS_path_sz		1+(NFS_MAXPATHLEN>>2)
+#define NFS_filename_sz		1+(NFS2_MAXNAMLEN>>2)
+#define NFS_path_sz		1+(NFS2_MAXPATHLEN>>2)
 #define NFS_fattr_sz		17
 #define NFS_info_sz		5
 #define NFS_entry_sz		NFS_filename_sz+3
@@ -49,6 +52,7 @@
 #define NFS_enc_void_sz		0
 #define NFS_diropargs_sz	NFS_fhandle_sz+NFS_filename_sz
 #define NFS_sattrargs_sz	NFS_fhandle_sz+NFS_sattr_sz
+#define NFS_readlinkargs_sz	NFS_fhandle_sz
 #define NFS_readargs_sz		NFS_fhandle_sz+3
 #define NFS_writeargs_sz	NFS_fhandle_sz+4
 #define NFS_createargs_sz	NFS_diropargs_sz+NFS_sattr_sz
@@ -60,8 +64,9 @@
 #define NFS_dec_void_sz		0
 #define NFS_attrstat_sz		1+NFS_fattr_sz
 #define NFS_diropres_sz		1+NFS_fhandle_sz+NFS_fattr_sz
-#define NFS_readlinkres_sz	1+NFS_path_sz
+#define NFS_readlinkres_sz	1
 #define NFS_readres_sz		1+NFS_fattr_sz+1
+#define NFS_writeres_sz         NFS_attrstat_sz
 #define NFS_stat_sz		1
 #define NFS_readdirres_sz	1
 #define NFS_statfsres_sz	1+NFS_info_sz
@@ -72,15 +77,19 @@
 static inline u32 *
 xdr_encode_fhandle(u32 *p, struct nfs_fh *fhandle)
 {
-	*((struct nfs_fh *) p) = *fhandle;
-	return p + QUADLEN(sizeof(*fhandle));
+	memcpy(p, fhandle->data, NFS2_FHSIZE);
+	return p + XDR_QUADLEN(NFS2_FHSIZE);
 }
 
 static inline u32 *
 xdr_decode_fhandle(u32 *p, struct nfs_fh *fhandle)
 {
-	*fhandle = *((struct nfs_fh *) p);
-	return p + QUADLEN(sizeof(*fhandle));
+	/* Zero handle first to allow comparisons */
+	memset(fhandle, 0, sizeof(*fhandle));
+	/* NFSv2 handles have a fixed length */
+	fhandle->size = NFS2_FHSIZE;
+	memcpy(fhandle->data, p, NFS2_FHSIZE);
+	return p + XDR_QUADLEN(NFS2_FHSIZE);
 }
 
 static inline u32 *
@@ -91,7 +100,14 @@
 	if (*len > maxlen)
 		return NULL;
 	*string = (char *) p;
-	return p + QUADLEN(*len);
+	return p + XDR_QUADLEN(*len);
+}
+
+static inline u32*
+xdr_decode_time(u32 *p, u64 *timep)
+{
+	*timep = ((u64)ntohl(*p++) << 32) + (u64)ntohl(*p++);
+	return p;
 }
 
 static inline u32 *
@@ -103,33 +119,51 @@
 	fattr->uid = ntohl(*p++);
 	fattr->gid = ntohl(*p++);
 	fattr->size = ntohl(*p++);
-	fattr->blocksize = ntohl(*p++);
+	fattr->du.nfs2.blocksize = ntohl(*p++);
 	fattr->rdev = ntohl(*p++);
-	fattr->blocks = ntohl(*p++);
+	fattr->du.nfs2.blocks = ntohl(*p++);
 	fattr->fsid = ntohl(*p++);
 	fattr->fileid = ntohl(*p++);
-	fattr->atime.seconds = ntohl(*p++);
-	fattr->atime.useconds = ntohl(*p++);
-	fattr->mtime.seconds = ntohl(*p++);
-	fattr->mtime.useconds = ntohl(*p++);
-	fattr->ctime.seconds = ntohl(*p++);
-	fattr->ctime.useconds = ntohl(*p++);
+	p = xdr_decode_time(p, &fattr->atime);
+	p = xdr_decode_time(p, &fattr->mtime);
+	p = xdr_decode_time(p, &fattr->ctime);
+	fattr->valid |= NFS_ATTR_FATTR;
+	if (fattr->type == NFCHR && fattr->rdev == NFS2_FIFO_DEV) {
+		fattr->type = NFFIFO;
+		fattr->mode = (fattr->mode & ~S_IFMT) | S_IFIFO;
+		fattr->rdev = 0;
+	}
 	return p;
 }
 
+#define SATTR(p, attr, flag, field) \
+        *p++ = (attr->ia_valid & flag) ? htonl(attr->field) : ~(u32) 0
 static inline u32 *
-xdr_encode_sattr(u32 *p, struct nfs_sattr *sattr)
+xdr_encode_sattr(u32 *p, struct iattr *attr)
 {
-	*p++ = htonl(sattr->mode);
-	*p++ = htonl(sattr->uid);
-	*p++ = htonl(sattr->gid);
-	*p++ = htonl(sattr->size);
-	*p++ = htonl(sattr->atime.seconds);
-	*p++ = htonl(sattr->atime.useconds);
-	*p++ = htonl(sattr->mtime.seconds);
-	*p++ = htonl(sattr->mtime.useconds);
-	return p;
+	SATTR(p, attr, ATTR_MODE, ia_mode);
+	SATTR(p, attr, ATTR_UID, ia_uid);
+	SATTR(p, attr, ATTR_GID, ia_gid);
+	SATTR(p, attr, ATTR_SIZE, ia_size);
+
+	if (attr->ia_valid & (ATTR_ATIME|ATTR_ATIME_SET)) {
+		*p++ = htonl(attr->ia_atime);
+		*p++ = 0;
+	} else {
+		*p++ = ~(u32) 0;
+		*p++ = ~(u32) 0;
+	}
+
+	if (attr->ia_valid & (ATTR_MTIME|ATTR_MTIME_SET)) {
+		*p++ = htonl(attr->ia_mtime);
+		*p++ = 0;
+	} else {
+		*p++ = ~(u32) 0;	
+		*p++ = ~(u32) 0;
+	}
+  	return p;
 }
+#undef SATTR;
 
 /*
  * NFS encode functions
@@ -176,7 +210,7 @@
 nfs_xdr_diropargs(struct rpc_rqst *req, u32 *p, struct nfs_diropargs *args)
 {
 	p = xdr_encode_fhandle(p, args->fh);
-	p = xdr_encode_string(p, args->name);
+	p = xdr_encode_string(p, args->name, args->len);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 	return 0;
 }
@@ -190,7 +224,8 @@
 nfs_xdr_readargs(struct rpc_rqst *req, u32 *p, struct nfs_readargs *args)
 {
 	struct rpc_auth	*auth = req->rq_task->tk_auth;
-	int		replen, buflen;
+	size_t		replen;
+	unsigned int	nr;
 
 	p = xdr_encode_fhandle(p, args->fh);
 	*p++ = htonl(args->offset);
@@ -198,21 +233,22 @@
 	*p++ = htonl(args->count);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 
-#if 1
+	/* Get the number of buffers in the receive iovec */
+        nr = args->nriov;
+
+        if (nr+1 > MAX_IOVEC) {
+                printk(KERN_ERR "NFS: Bad number of iov's in xdr_readargs\n");
+                return -EINVAL;
+        }
+
 	/* set up reply iovec */
 	replen = (RPC_REPHDRSIZE + auth->au_rslack + NFS_readres_sz) << 2;
-	buflen = req->rq_rvec[0].iov_len;
 	req->rq_rvec[0].iov_len  = replen;
-	req->rq_rvec[1].iov_base = args->buffer;
-	req->rq_rvec[1].iov_len  = args->count;
-	req->rq_rvec[2].iov_base = (u8 *) req->rq_rvec[0].iov_base + replen;
-	req->rq_rvec[2].iov_len  = buflen - replen;
-	req->rq_rlen = args->count + buflen;
-	req->rq_rnr = 3;
-#else
-	replen = (RPC_REPHDRSIZE + auth->au_rslack + NFS_readres_sz) << 2;
-	req->rq_rvec[0].iov_len  = replen;
-#endif
+        /* Copy the iovec */
+        memcpy(req->rq_rvec + 1, args->iov, nr * sizeof(struct iovec));
+
+	req->rq_rlen = args->count + replen;
+	req->rq_rnr += nr;
 
 	return 0;
 }
@@ -224,9 +260,9 @@
 nfs_xdr_readres(struct rpc_rqst *req, u32 *p, struct nfs_readres *res)
 {
 	struct iovec *iov = req->rq_rvec;
-	int	status, count, recvd, hdrlen;
+	u32	count, recvd, hdrlen;
+	int	status;
 
-	dprintk("RPC:      readres OK status %lx\n", (long)ntohl(*p));
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
 	p = xdr_decode_fattr(p, res->fattr);
@@ -234,22 +270,22 @@
 	count = ntohl(*p++);
 	hdrlen = (u8 *) p - (u8 *) iov->iov_base;
 	recvd = req->rq_rlen - hdrlen;
-	if (p != iov[2].iov_base) {
-		/* Unexpected reply header size. Punt.
-		 * XXX: Move iovec contents to align data on page
-		 * boundary and adjust RPC header size guess */
-		printk("NFS: Odd RPC header size in read reply: %d\n", hdrlen);
+	if (hdrlen > iov[0].iov_len) {
+		printk(KERN_WARNING "NFS: Odd RPC header size in read reply: %d\n", hdrlen);
 		return -errno_NFSERR_IO;
 	}
-	if (count > recvd) {
-		printk("NFS: server cheating in read reply: "
-			"count %d > recvd %d\n", count, recvd);
-		count = recvd;
+	if (hdrlen != iov[0].iov_len) {
+		dprintk("NFS: Short READ header. iovec will be shifted.\n");
+		xdr_shift_iovec(iov, req->rq_rnr, iov->iov_len - hdrlen);
 	}
 
 	dprintk("RPC:      readres OK count %d\n", count);
-	if (count < res->count)
-		memset((u8 *)(iov[1].iov_base+count), 0, res->count-count);
+	if (count < res->count) {
+		xdr_zero_iovec(iov+1, req->rq_rnr-1, res->count - count);
+		res->count = count;
+		res->eof = 1;  /* Silly NFSv3ism which can't be helped */
+	} else
+		res->eof = 0;
 
 	return count;
 }
@@ -261,6 +297,7 @@
 static int
 nfs_xdr_writeargs(struct rpc_rqst *req, u32 *p, struct nfs_writeargs *args)
 {
+	unsigned int nr;
 	u32 count = args->count;
 
 	p = xdr_encode_fhandle(p, args->fh);
@@ -270,28 +307,35 @@
 	*p++ = htonl(count);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 
-	req->rq_svec[1].iov_base = (void *) args->buffer;
-	req->rq_svec[1].iov_len = count;
-	req->rq_slen += count;
-	req->rq_snr = 2;
+	/* Get the number of buffers in the send iovec */
+	nr = args->nriov;
+
+	if (nr+2 > MAX_IOVEC) {
+                printk(KERN_ERR "NFS: Bad number of iov's in xdr_writeargs "
+                        "(nr %d max %d)\n", nr, MAX_IOVEC);
+                return -EINVAL;
+        }
+
+	/* Copy the iovec */
+        memcpy(req->rq_svec + 1, args->iov, nr * sizeof(struct iovec));
 
 #ifdef NFS_PAD_WRITES
 	/*
 	 * Some old servers require that the message length
 	 * be a multiple of 4, so we pad it here if needed.
 	 */
-	count = ((count + 3) & ~3) - count;
-	if (count) {
-#if 0
-printk("nfs_writeargs: padding write, len=%d, slen=%d, pad=%d\n",
-req->rq_svec[1].iov_len, req->rq_slen, count);
-#endif
-		req->rq_svec[2].iov_base = (void *) "\0\0\0";
-		req->rq_svec[2].iov_len  = count;
-		req->rq_slen += count;
-		req->rq_snr = 3;
+	if (count & 3) {
+		struct iovec	*iov = req->rq_svec + nr + 1;
+		int		pad = 4 - (count & 3);
+
+		iov->iov_base = (void *) "\0\0\0";
+		iov->iov_len  = pad;
+		count += pad;
+		nr++;
 	}
 #endif
+	req->rq_slen += count;
+	req->rq_snr += nr;
 
 	return 0;
 }
@@ -304,7 +348,7 @@
 nfs_xdr_createargs(struct rpc_rqst *req, u32 *p, struct nfs_createargs *args)
 {
 	p = xdr_encode_fhandle(p, args->fh);
-	p = xdr_encode_string(p, args->name);
+	p = xdr_encode_string(p, args->name, args->len);
 	p = xdr_encode_sattr(p, args->sattr);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 	return 0;
@@ -317,9 +361,9 @@
 nfs_xdr_renameargs(struct rpc_rqst *req, u32 *p, struct nfs_renameargs *args)
 {
 	p = xdr_encode_fhandle(p, args->fromfh);
-	p = xdr_encode_string(p, args->fromname);
+	p = xdr_encode_string(p, args->fromname, args->fromlen);
 	p = xdr_encode_fhandle(p, args->tofh);
-	p = xdr_encode_string(p, args->toname);
+	p = xdr_encode_string(p, args->toname, args->tolen);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 	return 0;
 }
@@ -332,7 +376,7 @@
 {
 	p = xdr_encode_fhandle(p, args->fromfh);
 	p = xdr_encode_fhandle(p, args->tofh);
-	p = xdr_encode_string(p, args->toname);
+	p = xdr_encode_string(p, args->toname, args->tolen);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 	return 0;
 }
@@ -344,8 +388,8 @@
 nfs_xdr_symlinkargs(struct rpc_rqst *req, u32 *p, struct nfs_symlinkargs *args)
 {
 	p = xdr_encode_fhandle(p, args->fromfh);
-	p = xdr_encode_string(p, args->fromname);
-	p = xdr_encode_string(p, args->topath);
+	p = xdr_encode_string(p, args->fromname, args->fromlen);
+	p = xdr_encode_string(p, args->topath, args->tolen);
 	p = xdr_encode_sattr(p, args->sattr);
 	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
 	return 0;
@@ -360,7 +404,7 @@
 	struct rpc_task	*task = req->rq_task;
 	struct rpc_auth	*auth = task->tk_auth;
 	u32		bufsiz = args->bufsiz;
-	int		replen;
+	size_t		replen;
 
 	/*
 	 * Some servers (e.g. HP OS 9.5) seem to expect the buffer size
@@ -376,119 +420,95 @@
 
 	/* set up reply iovec */
 	replen = (RPC_REPHDRSIZE + auth->au_rslack + NFS_readdirres_sz) << 2;
-	/*
-	dprintk("RPC: readdirargs: slack is 4 * (%d + %d + %d) = %d\n",
-		RPC_REPHDRSIZE, auth->au_rslack, NFS_readdirres_sz, replen);
-	 */
 	req->rq_rvec[0].iov_len  = replen;
 	req->rq_rvec[1].iov_base = args->buffer;
 	req->rq_rvec[1].iov_len  = args->bufsiz;
 	req->rq_rlen = replen + args->bufsiz;
-	req->rq_rnr = 2;
-
-	/*
-	dprintk("RPC:      readdirargs set up reply vec:\n");
-	dprintk("          rvec[0] = %p/%d\n",
-			req->rq_rvec[0].iov_base,
-			req->rq_rvec[0].iov_len);
-	dprintk("          rvec[1] = %p/%d\n",
-			req->rq_rvec[1].iov_base,
-			req->rq_rvec[1].iov_len);
-	 */
+	req->rq_rnr ++;
 
 	return 0;
 }
 
 /*
- * Decode the result of a readdir call. We decode the result in place
- * to avoid a malloc of NFS_MAXNAMLEN+1 for each file name.
- * After decoding, the layout in memory looks like this:
- *	entry1 entry2 ... entryN <space> stringN ... string2 string1
- * Each entry consists of three __u32 values, the same space as NFS uses.
- * Note that the strings are not null-terminated so that the entire number
- * of entries returned by the server should fit into the buffer.
+ * Decode the result of a readdir call.
+ * We're not really decoding anymore, we just leave the buffer untouched
+ * and only check that it is syntactically correct.
+ * The real decoding happens in nfs_decode_entry below, called directly
+ * from nfs_readdir for each entry.
  */
 static int
 nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs_readdirres *res)
 {
 	struct iovec		*iov = req->rq_rvec;
+	u32			hdrlen;
 	int			 status, nr;
-	char			*string, *start;
-	u32			*end, *entry, len, fileid, cookie;
+	u32			*end, *entry, len;
 
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
-	if ((void *) p != ((u8 *) iov->iov_base+iov->iov_len)) {
-		/* Unexpected reply header size. Punt. */
-		printk("NFS: Odd RPC header size in readdirres reply\n");
+	hdrlen = (u8*)p - (u8*) iov->iov_base;
+	if (hdrlen > iov[0].iov_len) {
+		printk(KERN_WARNING "NFS: Odd RPC header size in READDIR reply: %d\n", hdrlen);
 		return -errno_NFSERR_IO;
 	}
+	if (hdrlen != iov[0].iov_len) {
+		dprintk("NFS: Short READDIR header. iovec will be shifted.\n");
+		xdr_shift_iovec(iov, req->rq_rnr, iov->iov_len - hdrlen);
+	}
 
 	/* Get start and end address of XDR data */
 	p   = (u32 *) iov[1].iov_base;
 	end = (u32 *) ((u8 *) p + iov[1].iov_len);
 
 	/* Get start and end of dirent buffer */
-	entry  = (u32 *) res->buffer;
-	start  = (char *) res->buffer;
-	string = (char *) res->buffer + res->bufsiz;
-	for (nr = 0; *p++; nr++) {
-		fileid = ntohl(*p++);
+	if (res->buffer != p) {
+		printk(KERN_ERR "NFS: Bad result buffer in readdir\n");
+		return -errno_NFSERR_IO;
+	}
 
+	for (nr = 0; *p++; nr++) {
+		entry = p - 1;
+		if (p + 2 > end)
+			goto short_pkt;
+		p++; /* fileid */
 		len = ntohl(*p++);
-		/*
-		 * Check whether the server has exceeded our reply buffer,
-		 * and set a flag to convert the size to longwords.
-		 */
-		if ((p + QUADLEN(len) + 3) > end) {
-			struct rpc_clnt *clnt = req->rq_task->tk_client;
-			printk(KERN_WARNING
-				"NFS: server %s, readdir reply truncated\n",
-				clnt->cl_server);
-			printk(KERN_WARNING "NFS: nr=%d, slots=%d, len=%d\n",
-				nr, (end - p), len);
-			clnt->cl_flags |= NFS_CLNTF_BUFSIZE;
-			break;
-		}
-		if (len > NFS_MAXNAMLEN) {
-			printk("NFS: giant filename in readdir (len %x)!\n",
+		p += XDR_QUADLEN(len) + 1;	/* name plus cookie */
+		if (len > NFS2_MAXNAMLEN) {
+			printk(KERN_WARNING "NFS: giant filename in readdir (len 0x%x)!\n",
 						len);
 			return -errno_NFSERR_IO;
 		}
-		string -= len;
-		if ((void *) (entry+3) > (void *) string) {
-			/* 
-			 * This error is impossible as long as the temp
-			 * buffer is no larger than the user buffer. The 
-			 * current packing algorithm uses the same amount
-			 * of space in the user buffer as in the XDR data,
-			 * so it's guaranteed to fit.
-			 */
-			printk("NFS: incorrect buffer size in %s!\n",
-				__FUNCTION__);
-			break;
-		}
-
-		memmove(string, p, len);
-		p += QUADLEN(len);
-		cookie = ntohl(*p++);
-		/*
-		 * To make everything fit, we encode the length, offset,
-		 * and eof flag into 32 bits. This works for filenames
-		 * up to 32K and PAGE_SIZE up to 64K.
-		 */
-		status = !p[0] && p[1] ? (1 << 15) : 0; /* eof flag */
-		*entry++ = fileid;
-		*entry++ = cookie;
-		*entry++ = ((string - start) << 16) | status | (len & 0x7FFF);
-	}
-#ifdef NFS_PARANOIA
-printk("nfs_xdr_readdirres: %d entries, ent sp=%d, str sp=%d\n",
-nr, ((char *) entry - start), (start + res->bufsiz - string));
-#endif
+		if (p + 2 > end)
+			goto short_pkt;
+	}
+	return nr;
+ short_pkt:
+	printk(KERN_NOTICE "NFS: short packet in readdir reply!\n");
+	entry[0] = entry[1] = 0;
 	return nr;
 }
 
+u32 *
+nfs_decode_dirent(u32 *p, struct nfs_entry *entry, int plus)
+{
+	if (!*p++) {
+		if (!*p)
+			return ERR_PTR(-EAGAIN);
+		entry->eof = 1;
+		return ERR_PTR(-EBADCOOKIE);
+	}
+
+	entry->ino	  = ntohl(*p++);
+	entry->len	  = ntohl(*p++);
+	entry->name	  = (const char *) p;
+	p		 += XDR_QUADLEN(entry->len);
+	entry->prev_cookie	  = entry->cookie;
+	entry->cookie	  = ntohl(*p++);
+	entry->eof	  = !p[0] && p[1];
+
+	return p;
+}
+
 /*
  * NFS XDR decode functions
  */
@@ -523,12 +543,9 @@
 {
 	int	status;
 
-	dprintk("RPC:      attrstat status %lx\n", (long)ntohl(*p));
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
 	xdr_decode_fattr(p, fattr);
-	dprintk("RPC:      attrstat OK type %d mode %o dev %x ino %x\n",
-		fattr->type, fattr->mode, fattr->fsid, fattr->fileid);
 	return 0;
 }
 
@@ -541,14 +558,31 @@
 {
 	int	status;
 
-	dprintk("RPC:      diropres status %lx\n", (long)ntohl(*p));
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
 	p = xdr_decode_fhandle(p, res->fh);
 	xdr_decode_fattr(p, res->fattr);
-	dprintk("RPC:      diropres OK type %x mode %o dev %x ino %x\n",
-		res->fattr->type, res->fattr->mode,
-		res->fattr->fsid, res->fattr->fileid);
+	return 0;
+}
+
+/*
+ * Encode READLINK args
+ */
+static int
+nfs_xdr_readlinkargs(struct rpc_rqst *req, u32 *p, struct nfs_readlinkargs *args)
+{
+	struct rpc_task *task = req->rq_task;
+	struct rpc_auth *auth = task->tk_auth;
+	size_t		replen;
+
+	p = xdr_encode_fhandle(p, args->fh);
+	req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
+	replen = (RPC_REPHDRSIZE + auth->au_rslack + NFS_readlinkres_sz) << 2;
+	req->rq_rvec[0].iov_len  = replen;
+	req->rq_rvec[1].iov_base = args->buffer;
+	req->rq_rvec[1].iov_len  = args->bufsiz;
+	req->rq_rlen = replen + args->bufsiz;
+	req->rq_rnr ++;
 	return 0;
 }
 
@@ -558,33 +592,81 @@
 static int
 nfs_xdr_readlinkres(struct rpc_rqst *req, u32 *p, struct nfs_readlinkres *res)
 {
+	struct iovec *iov = req->rq_rvec;
+	u32	hdrlen;
+	u32	*strlen;
+	char	*string;
 	int	status;
+	unsigned int len;
 
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
-	xdr_decode_string2(p, res->string, res->lenp, res->maxlen);
-
-	/* Caller takes over the buffer here to avoid extra copy */
-	res->buffer = req->rq_task->tk_buffer;
-	req->rq_task->tk_buffer = NULL;
+	hdrlen = (u8*)p - (u8*) iov->iov_base;
+	if (hdrlen > iov[0].iov_len) {
+		printk(KERN_WARNING "NFS: Odd RPC header size in READLINK reply: %d\n", hdrlen);
+		return -errno_NFSERR_IO;
+	}
+	if (hdrlen != iov[0].iov_len) {
+		dprintk("NFS: Short READLINK header. iovec will be shifted.\n");
+		xdr_shift_iovec(iov, req->rq_rnr, iov->iov_len - hdrlen);
+	}
+	strlen = (u32*)res->buffer;
+	/* Convert length of symlink */
+	len = ntohl(*strlen);
+	if (len > res->bufsiz - 5)
+		len = res->bufsiz - 5;
+	*strlen = len;
+	/* NULL terminate the string we got */
+	string = (char *)(strlen + 1);
+	string[len] = 0;
 	return 0;
 }
 
 /*
+ * Decode WRITE reply
+ */
+static int
+nfs_xdr_writeres(struct rpc_rqst *req, u32 *p, struct nfs_writeres *res)
+{
+	res->verf->committed = NFS_FILE_SYNC;
+	return nfs_xdr_attrstat(req, p, res->fattr);
+}
+
+/*
  * Decode STATFS reply
  */
 static int
 nfs_xdr_statfsres(struct rpc_rqst *req, u32 *p, struct nfs_fsinfo *res)
 {
 	int	status;
+	u32	xfer_size;
 
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
-	res->tsize = ntohl(*p++);
-	res->bsize = ntohl(*p++);
-	res->blocks = ntohl(*p++);
-	res->bfree = ntohl(*p++);
-	res->bavail = ntohl(*p++);
+
+	/* For NFSv2, we more or less have to guess the preferred
+	 * read/write/readdir sizes from the single 'transfer size'
+	 * value.
+	 */
+	xfer_size = ntohl(*p++);	/* tsize */
+	res->rtmax  = 8 * 1024;
+	res->rtpref = xfer_size;
+	res->rtmult = xfer_size;
+	res->wtmax  = 8 * 1024;
+	res->wtpref = xfer_size;
+	res->wtmult = xfer_size;
+	res->dtpref = PAGE_CACHE_SIZE;
+	res->maxfilesize = 0x7FFFFFFF;	/* just a guess */
+	res->bsize  = ntohl(*p++);
+
+	res->tbytes = ntohl(*p++) * res->bsize;
+	res->fbytes = ntohl(*p++) * res->bsize;
+	res->abytes = ntohl(*p++) * res->bsize;
+	res->tfiles = 0;
+	res->ffiles = 0;
+	res->afiles = 0;
+	res->namelen = 0;
+
 	return 0;
 }
 
@@ -601,7 +683,7 @@
 	{ NFSERR_NOENT,		ENOENT		},
 	{ NFSERR_IO,		errno_NFSERR_IO	},
 	{ NFSERR_NXIO,		ENXIO		},
-	{ NFSERR_EAGAIN,	EAGAIN		},
+/*	{ NFSERR_EAGAIN,	EAGAIN		}, */
 	{ NFSERR_ACCES,		EACCES		},
 	{ NFSERR_EXIST,		EEXIST		},
 	{ NFSERR_XDEV,		EXDEV		},
@@ -612,18 +694,31 @@
 	{ NFSERR_FBIG,		EFBIG		},
 	{ NFSERR_NOSPC,		ENOSPC		},
 	{ NFSERR_ROFS,		EROFS		},
-	{ NFSERR_OPNOTSUPP,	EOPNOTSUPP	},
+	{ NFSERR_MLINK,		EMLINK		},
 	{ NFSERR_NAMETOOLONG,	ENAMETOOLONG	},
 	{ NFSERR_NOTEMPTY,	ENOTEMPTY	},
 	{ NFSERR_DQUOT,		EDQUOT		},
 	{ NFSERR_STALE,		ESTALE		},
+	{ NFSERR_REMOTE,	EREMOTE		},
 #ifdef EWFLUSH
 	{ NFSERR_WFLUSH,	EWFLUSH		},
 #endif
+	{ NFSERR_BADHANDLE,	EBADHANDLE	},
+	{ NFSERR_NOT_SYNC,	ENOTSYNC	},
+	{ NFSERR_BAD_COOKIE,	EBADCOOKIE	},
+	{ NFSERR_NOTSUPP,	ENOTSUPP	},
+	{ NFSERR_TOOSMALL,	ETOOSMALL	},
+	{ NFSERR_SERVERFAULT,	ESERVERFAULT	},
+	{ NFSERR_BADTYPE,	EBADTYPE	},
+	{ NFSERR_JUKEBOX,	EJUKEBOX	},
 	{ -1,			EIO		}
 };
 
-static int
+/*
+ * Convert an NFS error code to a local one.
+ * This one is used jointly by NFSv2 and NFSv3.
+ */
+int
 nfs_stat_to_errno(int stat)
 {
 	int i;
@@ -632,7 +727,7 @@
 		if (nfs_errtbl[i].stat == stat)
 			return nfs_errtbl[i].errno;
 	}
-	printk("nfs_stat_to_errno: bad nfs status return value: %d\n", stat);
+	printk(KERN_ERR "nfs_stat_to_errno: bad nfs status return value: %d\n", stat);
 	return nfs_errtbl[i].errno;
 }
 
@@ -644,7 +739,8 @@
     { "nfs_" #proc,					\
       (kxdrproc_t) nfs_xdr_##argtype,			\
       (kxdrproc_t) nfs_xdr_##restype,			\
-      MAX(NFS_##argtype##_sz,NFS_##restype##_sz) << 2	\
+      MAX(NFS_##argtype##_sz,NFS_##restype##_sz) << 2,	\
+      0							\
     }
 
 static struct rpc_procinfo	nfs_procedures[18] = {
@@ -653,10 +749,10 @@
     PROC(setattr,	sattrargs,	attrstat),
     PROC(root,		enc_void,	dec_void),
     PROC(lookup,	diropargs,	diropres),
-    PROC(readlink,	fhandle,	readlinkres),
+    PROC(readlink,	readlinkargs,	readlinkres),
     PROC(read,		readargs,	readres),
     PROC(writecache,	enc_void,	dec_void),
-    PROC(write,		writeargs,	attrstat),
+    PROC(write,		writeargs,	writeres),
     PROC(create,	createargs,	diropres),
     PROC(remove,	diropargs,	stat),
     PROC(rename,	renameargs,	stat),
@@ -668,22 +764,8 @@
     PROC(statfs,	fhandle,	statfsres),
 };
 
-static struct rpc_version	nfs_version2 = {
+struct rpc_version		nfs_version2 = {
 	2,
 	sizeof(nfs_procedures)/sizeof(nfs_procedures[0]),
 	nfs_procedures
-};
-
-static struct rpc_version *	nfs_version[] = {
-	NULL,
-	NULL,
-	&nfs_version2
-};
-
-struct rpc_program	nfs_program = {
-	"nfs",
-	NFS_PROGRAM,
-	sizeof(nfs_version) / sizeof(nfs_version[0]),
-	nfs_version,
-	&nfs_rpcstat,
 };

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)