From: "Kendrick M. Smith" Subject: REPOST patch 20/38: CLIENT: giant patch importing NFSv4 client functionality Date: Wed, 14 Aug 2002 16:46:13 -0400 (EDT) Sender: nfs-admin@lists.sourceforge.net Message-ID: Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Return-path: Received: from berzerk.gpcc.itd.umich.edu ([141.211.2.162]) by usw-sf-list1.sourceforge.net with esmtp (Exim 3.31-VA-mm2 #1 (Debian)) id 17f520-0005jD-00 for ; Wed, 14 Aug 2002 13:46:16 -0700 To: linux-kernel@vger.kernel.org, Errors-To: nfs-admin@lists.sourceforge.net List-Help: List-Post: List-Subscribe: , List-Id: Discussion of NFS under Linux development, interoperability, and testing. List-Unsubscribe: , List-Archive: Now that all the hooks are in place, this large patch imports all of the new code for the NFSv4 client. nfs4proc.c - procedure vectors nfs4xdr.c - XDR nfs4state.c - state bookkeeping (very minimal for now) nfs4renewd.c - a daemon (implemented as an rpc_task) to keep state from expiring on the server This patch makes almost no changes to the existing NFS codebase (these have been taken care of by the preceding patches). --- old/fs/nfs/Makefile Mon Aug 12 11:44:55 2002 +++ new/fs/nfs/Makefile Mon Aug 12 11:45:35 2002 @@ -8,6 +8,7 @@ nfs-y := dir.o file.o flushd.o inode. proc.o read.o symlink.o unlink.o write.o nfs-$(CONFIG_ROOT_NFS) += nfsroot.o mount_clnt.o nfs-$(CONFIG_NFS_V3) += nfs3proc.o nfs3xdr.o +nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o nfs-objs := $(nfs-y) include $(TOPDIR)/Rules.make --- old/fs/nfs/file.c Mon Aug 12 11:44:55 2002 +++ new/fs/nfs/file.c Mon Aug 12 11:45:35 2002 @@ -254,6 +254,12 @@ nfs_lock(struct file *filp, int cmd, str if (!inode) return -EINVAL; + /* This will be in a forthcoming patch. */ + if (NFS_PROTO(inode)->version == 4) { + printk(KERN_INFO "NFS: file locking over NFSv4 is not yet supported\n"); + return -EIO; + } + /* No mandatory locks over NFS */ if ((inode->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID) return -ENOLCK; --- old/fs/nfs/inode.c Mon Aug 12 11:44:55 2002 +++ new/fs/nfs/inode.c Mon Aug 12 11:45:35 2002 @@ -76,8 +76,13 @@ static struct rpc_version * nfs_version[ NULL, NULL, &nfs_version2, -#ifdef CONFIG_NFS_V3 +#if defined(CONFIG_NFS_V3) &nfs_version3, +#elif defined(CONFIG_NFS_V4) + NULL, +#endif +#if defined(CONFIG_NFS_V4) + &nfs_version4, #endif }; @@ -289,7 +294,20 @@ int nfs_fill_super(struct super_block *s server->caps = 0; /* Check NFS protocol revision and initialize RPC op vector * and file handle pool. */ - if (data->flags & NFS_MOUNT_VER3) { + if (data->flags & NFS_MOUNT_VER4) { +#ifdef CONFIG_NFS_V4 + server->rpc_ops = &nfs_v4_clientops; + version = 4; + if (data->version < 5) { + printk(KERN_NOTICE "NFS: NFSv4 not supported by mount program.\n"); + goto out_unlock; + } +#else + printk(KERN_NOTICE "NFS: NFSv4 not supported.\n"); + goto out_unlock; +#endif + } + else if (data->flags & NFS_MOUNT_VER3) { #ifdef CONFIG_NFS_V3 server->rpc_ops = &nfs_v3_clientops; version = 3; --- old/include/linux/nfs_fs.h Mon Aug 12 11:44:55 2002 +++ new/include/linux/nfs_fs.h Mon Aug 12 11:45:35 2002 @@ -465,6 +465,24 @@ extern void * nfs_root_data(void); #define NFS_JUKEBOX_RETRY_TIME (5 * HZ) #ifdef CONFIG_NFS_V4 +struct nfs4_client { + atomic_t cl_count; /* refcount */ + u64 cl_clientid; /* constant */ + + /* + * Starts a list of lockowners, linked through lo_list. + */ + struct list_head cl_lockowners; /* protected by state_spinlock */ +}; + +/* nfs4proc.c */ +extern int nfs4_proc_renew(struct nfs_server *server); + +/* nfs4renewd.c */ +extern int nfs4_init_renewd(struct nfs_server *server); +#endif /* CONFIG_NFS_V4 */ + +#ifdef CONFIG_NFS_V4 extern struct nfs4_client *nfs4_get_client(void); extern void nfs4_put_client(struct nfs4_client *clp); --- old/include/linux/nfs_xdr.h Mon Aug 12 11:44:55 2002 +++ new/include/linux/nfs_xdr.h Mon Aug 12 11:45:35 2002 @@ -300,6 +300,216 @@ struct nfs3_readdirres { int plus; }; +#ifdef CONFIG_NFS_V4 + +typedef u64 clientid4; + +struct nfs4_change_info { + u32 atomic; + u64 before; + u64 after; +}; + +struct nfs4_access { + u32 ac_req_access; /* request */ + u32 * ac_resp_supported; /* response */ + u32 * ac_resp_access; /* response */ +}; + +struct nfs4_close { + char * cl_stateid; /* request */ + u32 cl_seqid; /* request */ +}; + +struct nfs4_commit { + u64 co_start; /* request */ + u32 co_len; /* request */ + struct nfs_writeverf * co_verifier; /* response */ +}; + +struct nfs4_create { + u32 cr_ftype; /* request */ + union { /* request */ + struct { + u32 textlen; + const char * text; + } symlink; /* NF4LNK */ + struct { + u32 specdata1; + u32 specdata2; + } device; /* NF4BLK, NF4CHR */ + } u; + u32 cr_namelen; /* request */ + const char * cr_name; /* request */ + struct iattr * cr_attrs; /* request */ + struct nfs4_change_info * cr_cinfo; /* response */ +}; +#define cr_textlen u.symlink.textlen +#define cr_text u.symlink.text +#define cr_specdata1 u.device.specdata1 +#define cr_specdata2 u.device.specdata2 + +struct nfs4_getattr { + u32 * gt_bmval; /* request */ + struct nfs_fattr * gt_attrs; /* response */ + struct nfs_fsinfo * gt_fsinfo; /* response */ +}; + +struct nfs4_getfh { + struct nfs_fh * gf_fhandle; /* response */ +}; + +struct nfs4_link { + u32 ln_namelen; /* request */ + const char * ln_name; /* request */ + struct nfs4_change_info * ln_cinfo; /* response */ +}; + +struct nfs4_lookup { + struct qstr * lo_name; /* request */ +}; + +struct nfs4_open { + u32 op_share_access; /* request */ + u32 op_opentype; /* request */ + u32 op_createmode; /* request */ + union { /* request */ + struct iattr * attrs; /* UNCHECKED, GUARDED */ + nfs4_verifier verifier; /* EXCLUSIVE */ + } u; + struct qstr * op_name; /* request */ + char * op_stateid; /* response */ + struct nfs4_change_info * op_cinfo; /* response */ + u32 * op_rflags; /* response */ +}; +#define op_attrs u.attrs +#define op_verifier u.verifier + +struct nfs4_open_confirm { + char * oc_stateid; /* request */ +}; + +struct nfs4_putfh { + struct nfs_fh * pf_fhandle; /* request */ +}; + +struct nfs4_read { + u64 rd_offset; /* request */ + u32 rd_length; /* request */ + u32 *rd_eof; /* response */ + u32 *rd_bytes_read; /* response */ + struct page ** rd_pages; /* zero-copy data */ + unsigned int rd_pgbase; /* zero-copy data */ +}; + +struct nfs4_readdir { + u64 rd_cookie; /* request */ + nfs4_verifier rd_req_verifier; /* request */ + u32 rd_count; /* request */ + u32 rd_bmval[2]; /* request */ + nfs4_verifier rd_resp_verifier; /* response */ + struct page ** rd_pages; /* zero-copy data */ + unsigned int rd_pgbase; /* zero-copy data */ +}; + +struct nfs4_readlink { + u32 rl_count; /* zero-copy data */ + struct page ** rl_pages; /* zero-copy data */ +}; + +struct nfs4_remove { + u32 rm_namelen; /* request */ + const char * rm_name; /* request */ + struct nfs4_change_info * rm_cinfo; /* response */ +}; + +struct nfs4_rename { + u32 rn_oldnamelen; /* request */ + const char * rn_oldname; /* request */ + u32 rn_newnamelen; /* request */ + const char * rn_newname; /* request */ + struct nfs4_change_info * rn_src_cinfo; /* response */ + struct nfs4_change_info * rn_dst_cinfo; /* response */ +}; + +struct nfs4_setattr { + char * st_stateid; /* request */ + struct iattr * st_iap; /* request */ +}; + +struct nfs4_setclientid { + nfs4_verifier sc_verifier; /* request */ + char * sc_name; /* request */ + u32 sc_prog; /* request */ + char sc_netid[4]; /* request */ + char sc_uaddr[24]; /* request */ + u32 sc_cb_ident; /* request */ +}; + +struct nfs4_write { + u64 wr_offset; /* request */ + u32 wr_stable_how; /* request */ + u32 wr_len; /* request */ + u32 * wr_bytes_written; /* response */ + struct nfs_writeverf * wr_verf; /* response */ + struct page ** wr_pages; /* zero-copy data */ + unsigned int wr_pgbase; /* zero-copy data */ +}; + +struct nfs4_op { + u32 opnum; + u32 nfserr; + union { + struct nfs4_access access; + struct nfs4_close close; + struct nfs4_commit commit; + struct nfs4_create create; + struct nfs4_getattr getattr; + struct nfs4_getfh getfh; + struct nfs4_link link; + struct nfs4_lookup lookup; + struct nfs4_open open; + struct nfs4_open_confirm open_confirm; + struct nfs4_putfh putfh; + struct nfs4_read read; + struct nfs4_readdir readdir; + struct nfs4_readlink readlink; + struct nfs4_remove remove; + struct nfs4_rename rename; + struct nfs4_setattr setattr; + struct nfs4_setclientid setclientid; + struct nfs4_write write; + } u; +}; + +struct nfs4_compound { + unsigned int flags; /* defined below */ + struct nfs_server * server; + + /* RENEW information */ + int renew_index; + unsigned long timestamp; + + /* scratch variables for XDR encode/decode */ + int nops; + u32 * p; + u32 * end; + + /* the individual COMPOUND operations */ + struct nfs4_op *ops; + + /* request */ + int req_nops; + u32 taglen; + char * tag; + + /* response */ + int resp_nops; + int toplevel_status; +}; + +#endif /* CONFIG_NFS_V4 */ + struct nfs_read_data { struct rpc_task task; struct inode *inode; @@ -313,7 +523,11 @@ struct nfs_read_data { struct nfs_readres res; } v3; /* also v2 */ #ifdef CONFIG_NFS_V4 - /* NFSv4 data will come here... */ + struct { + struct nfs4_compound compound; + struct nfs4_op ops[3]; + u32 res_count; + } v4; #endif } u; }; @@ -328,11 +542,17 @@ struct nfs_write_data { struct page *pagevec[NFS_WRITE_MAXIOV]; union { struct { - struct nfs_writeargs args; - struct nfs_writeres res; + struct nfs_writeargs args; /* argument struct */ + struct nfs_writeres res; /* result struct */ } v3; #ifdef CONFIG_NFS_V4 - /* NFSv4 data to come here... */ + struct { + struct nfs4_compound compound; + struct nfs4_op ops[3]; + u32 arg_count; + u32 arg_stable; + u32 res_count; + } v4; #endif } u; }; @@ -401,8 +621,10 @@ struct nfs_rpc_ops { */ extern struct nfs_rpc_ops nfs_v2_clientops; extern struct nfs_rpc_ops nfs_v3_clientops; +extern struct nfs_rpc_ops nfs_v4_clientops; extern struct rpc_version nfs_version2; extern struct rpc_version nfs_version3; +extern struct rpc_version nfs_version4; extern struct rpc_program nfs_program; extern struct rpc_stat nfs_rpcstat; --- old/fs/nfs/nfs4proc.c Wed Dec 31 18:00:00 1969 +++ new/fs/nfs/nfs4proc.c Mon Aug 12 11:45:35 2002 @@ -0,0 +1,1476 @@ +/* + * fs/nfs/nfs4proc.c + * + * Client-side procedure declarations for NFSv4. + * + * Copyright (c) 2002 The Regents of the University of Michigan. + * All rights reserved. + * + * Kendrick Smith + * Andy Adamson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NFSDBG_FACILITY NFSDBG_PROC + +#define GET_OP(cp,name) &cp->ops[cp->req_nops].u.name +#define OPNUM(cp) cp->ops[cp->req_nops].opnum + +extern u32 *nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus); + +static nfs4_stateid zero_stateid = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static spinlock_t renew_lock = SPIN_LOCK_UNLOCKED; + +static void +nfs4_setup_compound(struct nfs4_compound *cp, struct nfs4_op *ops, + struct nfs_server *server, char *tag) +{ + memset(cp, 0, sizeof(*cp)); + cp->ops = ops; + cp->server = server; + +#if NFS4_DEBUG + cp->taglen = strlen(tag); + cp->tag = tag; +#endif +} + +static void +nfs4_setup_access(struct nfs4_compound *cp, u32 req_access, u32 *resp_supported, u32 *resp_access) +{ + struct nfs4_access *access = GET_OP(cp, access); + + access->ac_req_access = req_access; + access->ac_resp_supported = resp_supported; + access->ac_resp_access = resp_access; + + OPNUM(cp) = OP_ACCESS; + cp->req_nops++; +} + +static void +nfs4_setup_close(struct nfs4_compound *cp, nfs4_stateid stateid, u32 seqid) +{ + struct nfs4_close *close = GET_OP(cp, close); + + close->cl_stateid = stateid; + close->cl_seqid = seqid; + + OPNUM(cp) = OP_CLOSE; + cp->req_nops++; + cp->renew_index = cp->req_nops; +} + +static void +nfs4_setup_commit(struct nfs4_compound *cp, u64 start, u32 len, struct nfs_writeverf *verf) +{ + struct nfs4_commit *commit = GET_OP(cp, commit); + + commit->co_start = start; + commit->co_len = len; + commit->co_verifier = verf; + + OPNUM(cp) = OP_COMMIT; + cp->req_nops++; +} + +static void +nfs4_setup_create_dir(struct nfs4_compound *cp, struct qstr *name, + struct iattr *sattr, struct nfs4_change_info *info) +{ + struct nfs4_create *create = GET_OP(cp, create); + + create->cr_ftype = NF4DIR; + create->cr_namelen = name->len; + create->cr_name = name->name; + create->cr_attrs = sattr; + create->cr_cinfo = info; + + OPNUM(cp) = OP_CREATE; + cp->req_nops++; +} + +static void +nfs4_setup_create_symlink(struct nfs4_compound *cp, struct qstr *name, + struct qstr *linktext, struct iattr *sattr, + struct nfs4_change_info *info) +{ + struct nfs4_create *create = GET_OP(cp, create); + + create->cr_ftype = NF4LNK; + create->cr_textlen = linktext->len; + create->cr_text = linktext->name; + create->cr_namelen = name->len; + create->cr_name = name->name; + create->cr_attrs = sattr; + create->cr_cinfo = info; + + OPNUM(cp) = OP_CREATE; + cp->req_nops++; +} + +static void +nfs4_setup_create_special(struct nfs4_compound *cp, struct qstr *name, + dev_t dev, struct iattr *sattr, + struct nfs4_change_info *info) +{ + int mode = sattr->ia_mode; + struct nfs4_create *create = GET_OP(cp, create); + + BUG_ON(!(sattr->ia_valid & ATTR_MODE)); + BUG_ON(!S_ISFIFO(mode) && !S_ISBLK(mode) && !S_ISCHR(mode) && !S_ISSOCK(mode)); + + if (S_ISFIFO(mode)) + create->cr_ftype = NF4FIFO; + else if (S_ISBLK(mode)) { + create->cr_ftype = NF4BLK; + create->cr_specdata1 = MAJOR(dev); + create->cr_specdata2 = MINOR(dev); + } + else if (S_ISCHR(mode)) { + create->cr_ftype = NF4CHR; + create->cr_specdata1 = MAJOR(dev); + create->cr_specdata2 = MINOR(dev); + } + else + create->cr_ftype = NF4SOCK; + + create->cr_namelen = name->len; + create->cr_name = name->name; + create->cr_attrs = sattr; + create->cr_cinfo = info; + + OPNUM(cp) = OP_CREATE; + cp->req_nops++; +} + +/* + * This is our standard bitmap for GETATTR requests. + */ +u32 nfs4_standard_bitmap[2] = { + FATTR4_WORD0_TYPE + | FATTR4_WORD0_CHANGE + | FATTR4_WORD0_SIZE + | FATTR4_WORD0_FSID + | FATTR4_WORD0_FILEID, + FATTR4_WORD1_MODE + | FATTR4_WORD1_NUMLINKS + | FATTR4_WORD1_OWNER + | FATTR4_WORD1_OWNER_GROUP + | FATTR4_WORD1_RAWDEV + | FATTR4_WORD1_SPACE_USED + | FATTR4_WORD1_TIME_ACCESS + | FATTR4_WORD1_TIME_METADATA + | FATTR4_WORD1_TIME_MODIFY +}; + +u32 nfs4_statfs_bitmap[2] = { + FATTR4_WORD0_FILES_AVAIL + | FATTR4_WORD0_FILES_FREE + | FATTR4_WORD0_FILES_TOTAL + | FATTR4_WORD0_MAXFILESIZE + | FATTR4_WORD0_MAXLINK + | FATTR4_WORD0_MAXNAME + | FATTR4_WORD0_MAXREAD + | FATTR4_WORD0_MAXWRITE, + FATTR4_WORD1_SPACE_AVAIL + | FATTR4_WORD1_SPACE_FREE + | FATTR4_WORD1_SPACE_TOTAL +}; + +/* mount bitmap: standard bitmap + statfs bitmap + lease time */ +u32 nfs4_mount_bitmap[2] = { + FATTR4_WORD0_TYPE + | FATTR4_WORD0_CHANGE + | FATTR4_WORD0_SIZE + | FATTR4_WORD0_FSID + | FATTR4_WORD0_LEASE_TIME + | FATTR4_WORD0_FILEID + | FATTR4_WORD0_FILES_AVAIL + | FATTR4_WORD0_FILES_TOTAL + | FATTR4_WORD0_MAXFILESIZE + | FATTR4_WORD0_MAXLINK + | FATTR4_WORD0_MAXNAME + | FATTR4_WORD0_MAXREAD + | FATTR4_WORD0_MAXWRITE, + FATTR4_WORD1_MODE + | FATTR4_WORD1_NUMLINKS + | FATTR4_WORD1_OWNER + | FATTR4_WORD1_OWNER_GROUP + | FATTR4_WORD1_RAWDEV + | FATTR4_WORD1_SPACE_AVAIL + | FATTR4_WORD1_SPACE_FREE + | FATTR4_WORD1_SPACE_TOTAL + | FATTR4_WORD1_SPACE_USED + | FATTR4_WORD1_TIME_ACCESS + | FATTR4_WORD1_TIME_METADATA + | FATTR4_WORD1_TIME_MODIFY +}; + +static void +nfs4_setup_getattr(struct nfs4_compound *cp, u32 *bitmap, + struct nfs_fattr *fattr, struct nfs_fsinfo *fsinfo) +{ + struct nfs4_getattr *getattr = GET_OP(cp, getattr); + + getattr->gt_bmval = bitmap; + getattr->gt_attrs = fattr; + getattr->gt_fsinfo = fsinfo; + + OPNUM(cp) = OP_GETATTR; + cp->req_nops++; +} + +static void +nfs4_setup_getfh(struct nfs4_compound *cp, struct nfs_fh *fhandle) +{ + struct nfs4_getfh *getfh = GET_OP(cp, getfh); + + getfh->gf_fhandle = fhandle; + + OPNUM(cp) = OP_GETFH; + cp->req_nops++; +} + +static void +nfs4_setup_link(struct nfs4_compound *cp, struct qstr *name, + struct nfs4_change_info *info) +{ + struct nfs4_link *link = GET_OP(cp, link); + + link->ln_namelen = name->len; + link->ln_name = name->name; + link->ln_cinfo = info; + + OPNUM(cp) = OP_LINK; + cp->req_nops++; +} + +static void +nfs4_setup_lookup(struct nfs4_compound *cp, struct qstr *q) +{ + struct nfs4_lookup *lookup = GET_OP(cp, lookup); + + lookup->lo_name = q; + + OPNUM(cp) = OP_LOOKUP; + cp->req_nops++; +} + +static void +nfs4_setup_putfh(struct nfs4_compound *cp, struct nfs_fh *fhandle) +{ + struct nfs4_putfh *putfh = GET_OP(cp, putfh); + + putfh->pf_fhandle = fhandle; + + OPNUM(cp) = OP_PUTFH; + cp->req_nops++; +} + +static void +nfs4_setup_putrootfh(struct nfs4_compound *cp) +{ + OPNUM(cp) = OP_PUTROOTFH; + cp->req_nops++; +} + +static void +nfs4_setup_open(struct nfs4_compound *cp, int flags, struct qstr *name, + struct iattr *sattr, char *stateid, struct nfs4_change_info *cinfo, + u32 *rflags) +{ + struct nfs4_open *open = GET_OP(cp, open); + + BUG_ON(cp->flags); + + open->op_share_access = flags & 3; + open->op_opentype = (flags & O_CREAT) ? NFS4_OPEN_CREATE : NFS4_OPEN_NOCREATE; + open->op_createmode = NFS4_CREATE_UNCHECKED; + open->op_attrs = sattr; + if (flags & O_EXCL) { + u32 *p = (u32 *) open->op_verifier; + p[0] = jiffies; + p[1] = current->pid; + open->op_createmode = NFS4_CREATE_EXCLUSIVE; + } + open->op_name = name; + open->op_stateid = stateid; + open->op_cinfo = cinfo; + open->op_rflags = rflags; + + OPNUM(cp) = OP_OPEN; + cp->req_nops++; + cp->renew_index = cp->req_nops; +} + +static void +nfs4_setup_open_confirm(struct nfs4_compound *cp, char *stateid) +{ + struct nfs4_open_confirm *open_confirm = GET_OP(cp, open_confirm); + + open_confirm->oc_stateid = stateid; + + OPNUM(cp) = OP_OPEN_CONFIRM; + cp->req_nops++; + cp->renew_index = cp->req_nops; +} + +static void +nfs4_setup_read(struct nfs4_compound *cp, u64 offset, u32 length, + struct page **pages, unsigned int pgbase, u32 *eofp, u32 *bytes_read) +{ + struct nfs4_read *read = GET_OP(cp, read); + + read->rd_offset = offset; + read->rd_length = length; + read->rd_pages = pages; + read->rd_pgbase = pgbase; + read->rd_eof = eofp; + read->rd_bytes_read = bytes_read; + + OPNUM(cp) = OP_READ; + cp->req_nops++; +} + +static void +nfs4_setup_readdir(struct nfs4_compound *cp, u64 cookie, u32 *verifier, + struct page **pages, unsigned int bufsize, struct dentry *dentry) +{ + u32 *start, *p; + struct nfs4_readdir *readdir = GET_OP(cp, readdir); + + BUG_ON(bufsize < 80); + readdir->rd_cookie = (cookie > 2) ? cookie : 0; + memcpy(readdir->rd_req_verifier, verifier, sizeof(nfs4_verifier)); + readdir->rd_count = bufsize; + readdir->rd_bmval[0] = FATTR4_WORD0_FILEID; + readdir->rd_bmval[1] = 0; + readdir->rd_pages = pages; + readdir->rd_pgbase = 0; + + OPNUM(cp) = OP_READDIR; + cp->req_nops++; + + if (cookie >= 2) + return; + + /* + * NFSv4 servers do not return entries for '.' and '..' + * Therefore, we fake these entries here. We let '.' + * have cookie 0 and '..' have cookie 1. Note that + * when talking to the server, we always send cookie 0 + * instead of 1 or 2. + */ + start = p = (u32 *)kmap(*pages); + + if (cookie == 0) { + *p++ = xdr_one; /* next */ + *p++ = xdr_zero; /* cookie, first word */ + *p++ = xdr_one; /* cookie, second word */ + *p++ = xdr_one; /* entry len */ + memcpy(p, ".\0\0\0", 4); /* entry */ + p++; + *p++ = xdr_one; /* bitmap length */ + *p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */ + *p++ = htonl(8); /* attribute buffer length */ + p = xdr_encode_hyper(p, NFS_FILEID(dentry->d_inode)); + } + + *p++ = xdr_one; /* next */ + *p++ = xdr_zero; /* cookie, first word */ + *p++ = xdr_two; /* cookie, second word */ + *p++ = xdr_two; /* entry len */ + memcpy(p, "..\0\0", 4); /* entry */ + p++; + *p++ = xdr_one; /* bitmap length */ + *p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */ + *p++ = htonl(8); /* attribute buffer length */ + p = xdr_encode_hyper(p, NFS_FILEID(dentry->d_parent->d_inode)); + + readdir->rd_pgbase = (char *)p - (char *)start; + readdir->rd_count -= readdir->rd_pgbase; + kunmap(*pages); +} + +static void +nfs4_setup_readlink(struct nfs4_compound *cp, int count, struct page **pages) +{ + struct nfs4_readlink *readlink = GET_OP(cp, readlink); + + readlink->rl_count = count; + readlink->rl_pages = pages; + + OPNUM(cp) = OP_READLINK; + cp->req_nops++; +} + +static void +nfs4_setup_remove(struct nfs4_compound *cp, struct qstr *name, struct nfs4_change_info *cinfo) +{ + struct nfs4_remove *remove = GET_OP(cp, remove); + + remove->rm_namelen = name->len; + remove->rm_name = name->name; + remove->rm_cinfo = cinfo; + + OPNUM(cp) = OP_REMOVE; + cp->req_nops++; +} + +static void +nfs4_setup_rename(struct nfs4_compound *cp, struct qstr *old, struct qstr *new, + struct nfs4_change_info *old_cinfo, struct nfs4_change_info *new_cinfo) +{ + struct nfs4_rename *rename = GET_OP(cp, rename); + + rename->rn_oldnamelen = old->len; + rename->rn_oldname = old->name; + rename->rn_newnamelen = new->len; + rename->rn_newname = new->name; + rename->rn_src_cinfo = old_cinfo; + rename->rn_dst_cinfo = new_cinfo; + + OPNUM(cp) = OP_RENAME; + cp->req_nops++; +} + +static void +nfs4_setup_renew(struct nfs4_compound *cp) +{ + OPNUM(cp) = OP_RENEW; + cp->req_nops++; + cp->renew_index = cp->req_nops; +} + +static void +nfs4_setup_restorefh(struct nfs4_compound *cp) +{ + OPNUM(cp) = OP_RESTOREFH; + cp->req_nops++; +} + +static void +nfs4_setup_savefh(struct nfs4_compound *cp) +{ + OPNUM(cp) = OP_SAVEFH; + cp->req_nops++; +} + +static void +nfs4_setup_setattr(struct nfs4_compound *cp, char *stateid, struct iattr *iap) +{ + struct nfs4_setattr *setattr = GET_OP(cp, setattr); + + setattr->st_stateid = stateid; + setattr->st_iap = iap; + + OPNUM(cp) = OP_SETATTR; + cp->req_nops++; +} + +static void +nfs4_setup_setclientid(struct nfs4_compound *cp, u32 program, unsigned short port) +{ + struct nfs4_setclientid *setclientid = GET_OP(cp, setclientid); + struct nfs_server *server = cp->server; + struct timeval tv; + u32 *p; + + do_gettimeofday(&tv); + p = (u32 *)setclientid->sc_verifier; + *p++ = tv.tv_sec; + *p++ = tv.tv_usec; + setclientid->sc_name = server->ip_addr; + sprintf(setclientid->sc_netid, "udp"); + sprintf(setclientid->sc_uaddr, "%s.%d.%d", server->ip_addr, port >> 8, port & 255); + setclientid->sc_prog = program; + setclientid->sc_cb_ident = 0; + + OPNUM(cp) = OP_SETCLIENTID; + cp->req_nops++; +} + +static void +nfs4_setup_setclientid_confirm(struct nfs4_compound *cp) +{ + OPNUM(cp) = OP_SETCLIENTID_CONFIRM; + cp->req_nops++; + cp->renew_index = cp->req_nops; +} + +static void +nfs4_setup_write(struct nfs4_compound *cp, u64 offset, u32 length, int stable, + struct page **pages, unsigned int pgbase, u32 *bytes_written, + struct nfs_writeverf *verf) +{ + struct nfs4_write *write = GET_OP(cp, write); + + write->wr_offset = offset; + write->wr_stable_how = stable; + write->wr_len = length; + write->wr_bytes_written = bytes_written; + write->wr_verf = verf; + + write->wr_pages = pages; + write->wr_pgbase = pgbase; + + OPNUM(cp) = OP_WRITE; + cp->req_nops++; +} + +static inline void +process_lease(struct nfs4_compound *cp) +{ + struct nfs_server *server; + + /* + * Generic lease processing: If this operation contains a + * lease-renewing operation, and it succeeded, update the RENEW time + * in the superblock. Instead of the current time, we use the time + * when the request was sent out. (All we know is that the lease was + * renewed sometime between then and now, and we have to assume the + * worst case.) + * + * Notes: + * (1) renewd doesn't acquire the spinlock when messing with + * server->last_renewal; this is OK since rpciod always runs + * under the BKL. + * (2) cp->timestamp was set at the end of XDR encode. + */ + if (!cp->renew_index) + return; + if (!cp->toplevel_status || cp->resp_nops > cp->renew_index) { + server = cp->server; + spin_lock(&renew_lock); + if (server->last_renewal < cp->timestamp) + server->last_renewal = cp->timestamp; + spin_unlock(&renew_lock); + } +} + +static int +nfs4_call_compound(struct nfs4_compound *cp, struct rpc_cred *cred, int flags) +{ + int status; + struct rpc_message msg = { + rpc_proc: NFSPROC4_COMPOUND, + rpc_argp: cp, + rpc_resp: cp, + rpc_cred: cred, + }; + + status = rpc_call_sync(cp->server->client, &msg, flags); + if (!status) + process_lease(cp); + + return status; +} + +static inline void +process_cinfo(struct nfs4_change_info *info, struct nfs_fattr *fattr) +{ + BUG_ON((fattr->valid & NFS_ATTR_FATTR) == 0); + BUG_ON((fattr->valid & NFS_ATTR_FATTR_V4) == 0); + + if (fattr->change_attr == info->after) { + fattr->pre_change_attr = info->before; + fattr->valid |= NFS_ATTR_PRE_CHANGE; + fattr->timestamp = jiffies; + } +} + +static int +do_open(struct inode *dir, struct qstr *name, int flags, struct iattr *sattr, + struct nfs_fattr *fattr, struct nfs_fh *fhandle, u32 *seqid, char *stateid) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs4_change_info dir_cinfo; + struct nfs_fattr dir_attr; + u32 rflags; + int status; + + dir_attr.valid = 0; + fattr->valid = 0; + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "open"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_savefh(&compound); + nfs4_setup_open(&compound, flags, name, sattr, stateid, &dir_cinfo, &rflags); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fhandle); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + if ((status = nfs4_call_compound(&compound, NULL, 0))) + return status; + + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + if (!(rflags & NFS4_OPEN_RESULT_CONFIRM)) { + *seqid = 1; + return 0; + } + *seqid = 2; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "open_confirm"); + nfs4_setup_putfh(&compound, fhandle); + nfs4_setup_open_confirm(&compound, stateid); + return nfs4_call_compound(&compound, NULL, 0); +} + +static int +do_setattr(struct nfs_server *server, struct nfs_fattr *fattr, + struct nfs_fh *fhandle, struct iattr *sattr, char *stateid) +{ + struct nfs4_compound compound; + struct nfs4_op ops[3]; + + fattr->valid = 0; + nfs4_setup_compound(&compound, ops, server, "setattr"); + nfs4_setup_putfh(&compound, fhandle); + nfs4_setup_setattr(&compound, stateid, sattr); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + return nfs4_call_compound(&compound, NULL, 0); +} + +static int +do_close(struct nfs_server *server, struct nfs_fh *fhandle, u32 seqid, char *stateid) +{ + struct nfs4_compound compound; + struct nfs4_op ops[2]; + + nfs4_setup_compound(&compound, ops, server, "close"); + nfs4_setup_putfh(&compound, fhandle); + nfs4_setup_close(&compound, stateid, seqid); + return nfs4_call_compound(&compound, NULL, 0); +} + +static int +nfs4_proc_get_root(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[4]; + struct nfs_fsinfo fsinfo; + unsigned char * p; + struct qstr q; + int status; + + fattr->valid = 0; + + if (!(server->nfs4_state = nfs4_get_client())) + return -ENOMEM; + + /* + * SETCLIENTID. + * Until delegations are imported, we don't bother setting the program + * number and port to anything meaningful. + */ + nfs4_setup_compound(&compound, ops, server, "setclientid"); + nfs4_setup_setclientid(&compound, 0, 0); + if ((status = nfs4_call_compound(&compound, NULL, 0))) + goto out; + + /* + * SETCLIENTID_CONFIRM, plus root filehandle. + * We also get the lease time here. + */ + nfs4_setup_compound(&compound, ops, server, "setclientid_confirm"); + nfs4_setup_setclientid_confirm(&compound); + nfs4_setup_putrootfh(&compound); + nfs4_setup_getattr(&compound, nfs4_mount_bitmap, fattr, &fsinfo); + nfs4_setup_getfh(&compound, fhandle); + if ((status = nfs4_call_compound(&compound, NULL, 0))) + goto out; + + /* + * Now that we have instantiated the clientid and determined + * the lease time, we can initialize the renew daemon for this + * server. + */ + server->lease_time = fsinfo.lease_time * HZ; + if ((status = nfs4_init_renewd(server))) + goto out; + + /* + * Now we do a seperate LOOKUP for each component of the mount path. + * The LOOKUPs are done seperately so that we can conveniently + * catch an ERR_WRONGSEC if it occurs along the way... + */ + p = server->mnt_path; + for (;;) { + while (*p == '/') + p++; + if (!*p) + break; + q.name = p; + while (*p && (*p != '/')) + p++; + q.len = p - q.name; + + nfs4_setup_compound(&compound, ops, server, "mount"); + nfs4_setup_putfh(&compound, fhandle); + nfs4_setup_lookup(&compound, &q); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fhandle); + status = nfs4_call_compound(&compound, NULL, 0); + if (!status) + continue; + if (status == -ENOENT) { + printk(KERN_NOTICE "NFS: mount path %s does not exist!\n", server->mnt_path); + printk(KERN_NOTICE "NFS: suggestion: try mounting '/' instead.\n"); + } + break; + } + +out: + return status; +} + +static int +nfs4_proc_getattr(struct inode *inode, struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[2]; + + fattr->valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "getattr"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + return nfs4_call_compound(&compound, NULL, 0); +} + +static int +nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr, + struct iattr *sattr) +{ + struct inode * inode = dentry->d_inode; + int size_change = sattr->ia_valid & ATTR_SIZE; + struct nfs_fh throwaway_fh; + u32 seqid; + nfs4_stateid stateid; + int status; + + fattr->valid = 0; + + if (size_change) { + status = do_open(dentry->d_parent->d_inode, &dentry->d_name, + NFS4_SHARE_ACCESS_WRITE, NULL, fattr, + &throwaway_fh, &seqid, stateid); + if (status) + return status; + + /* + * Because OPEN is always done by name in nfsv4, it is + * possible that we opened a different file by the same + * name. We can recognize this race condition, but we + * can't do anything about it besides returning an error. + * + * XXX: Should we compare filehandles too, as in + * nfs_find_actor()? + */ + if (fattr->fileid != NFS_FILEID(inode)) { + printk(KERN_WARNING "nfs: raced in setattr, returning -EIO\n"); + do_close(NFS_SERVER(inode), NFS_FH(inode), seqid, stateid); + return -EIO; + } + } + else + memcpy(stateid, zero_stateid, sizeof(nfs4_stateid)); + + status = do_setattr(NFS_SERVER(inode), fattr, NFS_FH(inode), sattr, stateid); + if (size_change) + do_close(NFS_SERVER(inode), NFS_FH(inode), seqid, stateid); + return status; +} + +static int +nfs4_proc_lookup(struct inode *dir, struct qstr *name, + struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[5]; + struct nfs_fattr dir_attr; + int status; + + dir_attr.valid = 0; + fattr->valid = 0; + + dprintk("NFS call lookup %s\n", name->name); + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "lookup"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + nfs4_setup_lookup(&compound, name); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fhandle); + status = nfs4_call_compound(&compound, NULL, 0); + dprintk("NFS reply lookup: %d\n", status); + + if (status >= 0) + status = nfs_refresh_inode(dir, &dir_attr); + return status; +} + +static int +nfs4_proc_access(struct inode *inode, struct rpc_cred *cred, int mode) +{ + struct nfs4_compound compound; + struct nfs4_op ops[3]; + struct nfs_fattr fattr; + u32 req_access = 0, resp_supported, resp_access; + int status; + + fattr.valid = 0; + + /* + * Determine which access bits we want to ask for... + */ + if (mode & MAY_READ) + req_access |= NFS4_ACCESS_READ; + if (S_ISDIR(inode->i_mode)) { + if (mode & MAY_WRITE) + req_access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND | NFS4_ACCESS_DELETE; + if (mode & MAY_EXEC) + req_access |= NFS4_ACCESS_LOOKUP; + } + else { + if (mode & MAY_WRITE) + req_access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND; + if (mode & MAY_EXEC) + req_access |= NFS4_ACCESS_EXECUTE; + } + + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "access"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &fattr, NULL); + nfs4_setup_access(&compound, req_access, &resp_supported, &resp_access); + status = nfs4_call_compound(&compound, cred, 0); + nfs_refresh_inode(inode, &fattr); + + if (!status) { + if (req_access != resp_supported) { + printk(KERN_NOTICE "NFS: server didn't support all access bits!\n"); + status = -ENOTSUPP; + } + else if (req_access != resp_access) + status = -EACCES; + } + return status; +} + +/* + * TODO: For the time being, we don't try to get any attributes + * along with any of the zero-copy operations READ, READDIR, + * READLINK, WRITE. + * + * In the case of the first three, we want to put the GETATTR + * after the read-type operation -- this is because it is hard + * to predict the length of a GETATTR response in v4, and thus + * align the READ data correctly. This means that the GETATTR + * may end up partially falling into the page cache, and we should + * shift it into the 'tail' of the xdr_buf before processing. + * To do this efficiently, we need to know the total length + * of data received, which doesn't seem to be available outside + * of the RPC layer. + * + * In the case of WRITE, we also want to put the GETATTR after + * the operation -- in this case because we want to make sure + * we get the post-operation mtime and size. This means that + * we can't use xdr_encode_pages() as written: we need a variant + * of it which would leave room in the 'tail' iovec. + * + * Both of these changes to the XDR layer would in fact be quite + * minor, but I decided to leave them for a subsequent patch. + */ +static int +nfs4_proc_readlink(struct inode *inode, struct page *page) +{ + struct nfs4_compound compound; + struct nfs4_op ops[2]; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "readlink"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_readlink(&compound, PAGE_CACHE_SIZE, &page); + return nfs4_call_compound(&compound, NULL, 0); +} + +static int +nfs4_proc_read(struct inode *inode, struct rpc_cred *cred, + struct nfs_fattr *fattr, int flags, + unsigned int base, unsigned int count, + struct page *page, int *eofp) +{ + u64 offset = page_offset(page) + base; + struct nfs4_compound compound; + struct nfs4_op ops[2]; + u32 bytes_read; + int status; + + fattr->valid = 0; + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "read [sync]"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_read(&compound, offset, count, &page, base, eofp, &bytes_read); + status = nfs4_call_compound(&compound, cred, 0); + + if (status >= 0) + status = bytes_read; + return status; +} + +static int +nfs4_proc_write(struct inode *inode, struct rpc_cred *cred, + struct nfs_fattr *fattr, int flags, + unsigned int base, unsigned int count, + struct page *page, struct nfs_writeverf *verf) +{ + u64 offset = page_offset(page) + base; + struct nfs4_compound compound; + struct nfs4_op ops[2]; + u32 bytes_written; + int stable = (flags & NFS_RW_SYNC) ? NFS_FILE_SYNC : NFS_UNSTABLE; + int rpcflags = (flags & NFS_RW_SWAP) ? NFS_RPC_SWAPFLAGS : 0; + int status; + + fattr->valid = 0; + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "write [sync]"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_write(&compound, offset, count, stable, &page, base, &bytes_written, verf); + status = nfs4_call_compound(&compound, cred, rpcflags); + + if (status >= 0) + status = bytes_written; + return status; +} + +static int +nfs4_proc_create(struct inode *dir, struct qstr *name, struct iattr *sattr, + int flags, struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + int oflags; + u32 seqid; + nfs4_stateid stateid; + int status; + + oflags = NFS4_SHARE_ACCESS_READ | O_CREAT | (flags & O_EXCL); + status = do_open(dir, name, oflags, sattr, fattr, fhandle, &seqid, stateid); + if (!status) { + if (flags & O_EXCL) + status = do_setattr(NFS_SERVER(dir), fattr, fhandle, sattr, stateid); + do_close(NFS_SERVER(dir), fhandle, seqid, stateid); + } + return status; +} + +static int +nfs4_proc_remove(struct inode *dir, struct qstr *name) +{ + struct nfs4_compound compound; + struct nfs4_op ops[3]; + struct nfs4_change_info dir_cinfo; + struct nfs_fattr dir_attr; + int status; + + dir_attr.valid = 0; + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "remove"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_remove(&compound, name, &dir_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + } + return status; +} + +struct unlink_desc { + struct nfs4_compound compound; + struct nfs4_op ops[3]; + struct nfs4_change_info cinfo; + struct nfs_fattr attrs; +}; + +static int +nfs4_proc_unlink_setup(struct rpc_message *msg, struct dentry *dir, struct qstr *name) +{ + struct unlink_desc * up; + struct nfs4_compound * cp; + + up = (struct unlink_desc *) kmalloc(sizeof(*up), GFP_KERNEL); + if (!up) + return -ENOMEM; + cp = &up->compound; + + nfs4_setup_compound(cp, up->ops, NFS_SERVER(dir->d_inode), "unlink_setup"); + nfs4_setup_putfh(cp, NFS_FH(dir->d_inode)); + nfs4_setup_remove(cp, name, &up->cinfo); + nfs4_setup_getattr(cp, nfs4_standard_bitmap, &up->attrs, NULL); + + msg->rpc_proc = NFSPROC4_COMPOUND; + msg->rpc_argp = cp; + msg->rpc_resp = cp; + return 0; +} + +static int +nfs4_proc_unlink_done(struct dentry *dir, struct rpc_task *task) +{ + struct rpc_message *msg = &task->tk_msg; + struct unlink_desc *up; + + if (msg->rpc_argp) { + up = (struct unlink_desc *) msg->rpc_argp; + process_lease(&up->compound); + process_cinfo(&up->cinfo, &up->attrs); + nfs_refresh_inode(dir->d_inode, &up->attrs); + kfree(up); + msg->rpc_argp = NULL; + } + return 0; +} + +static int +nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name, + struct inode *new_dir, struct qstr *new_name) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs4_change_info old_cinfo, new_cinfo; + struct nfs_fattr old_dir_attr, new_dir_attr; + int status; + + old_dir_attr.valid = 0; + new_dir_attr.valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(old_dir), "rename"); + nfs4_setup_putfh(&compound, NFS_FH(old_dir)); + nfs4_setup_savefh(&compound); + nfs4_setup_putfh(&compound, NFS_FH(new_dir)); + nfs4_setup_rename(&compound, old_name, new_name, &old_cinfo, &new_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &new_dir_attr, NULL); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &old_dir_attr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&old_cinfo, &old_dir_attr); + process_cinfo(&new_cinfo, &new_dir_attr); + nfs_refresh_inode(old_dir, &old_dir_attr); + nfs_refresh_inode(new_dir, &new_dir_attr); + } + return status; +} + +static int +nfs4_proc_link(struct inode *inode, struct inode *dir, struct qstr *name) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs4_change_info dir_cinfo; + struct nfs_fattr dir_attr, fattr; + int status; + + dir_attr.valid = 0; + fattr.valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "link"); + nfs4_setup_putfh(&compound, NFS_FH(inode)); + nfs4_setup_savefh(&compound); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_link(&compound, name, &dir_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &fattr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + nfs_refresh_inode(inode, &fattr); + } + return status; +} + +static int +nfs4_proc_symlink(struct inode *dir, struct qstr *name, struct qstr *path, + struct iattr *sattr, struct nfs_fh *fhandle, + struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs_fattr dir_attr; + struct nfs4_change_info dir_cinfo; + int status; + + dir_attr.valid = 0; + fattr->valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "symlink"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_savefh(&compound); + nfs4_setup_create_symlink(&compound, name, path, sattr, &dir_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fhandle); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + } + return status; +} + +static int +nfs4_proc_mkdir(struct inode *dir, struct qstr *name, struct iattr *sattr, + struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs_fattr dir_attr; + struct nfs4_change_info dir_cinfo; + int status; + + dir_attr.valid = 0; + fattr->valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "mkdir"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_savefh(&compound); + nfs4_setup_create_dir(&compound, name, sattr, &dir_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fhandle); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + } + return status; +} + +static int +nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred, + u64 cookie, struct page *page, unsigned int count, int plus) +{ + struct inode *dir = dentry->d_inode; + struct nfs4_compound compound; + struct nfs4_op ops[2]; + int status; + + lock_kernel(); + + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "readdir"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_readdir(&compound, cookie, NFS_COOKIEVERF(dir), &page, count, dentry); + status = nfs4_call_compound(&compound, cred, 0); + + unlock_kernel(); + return status; +} + +static int +nfs4_proc_mknod(struct inode *dir, struct qstr *name, struct iattr *sattr, + dev_t rdev, struct nfs_fh *fh, struct nfs_fattr *fattr) +{ + struct nfs4_compound compound; + struct nfs4_op ops[7]; + struct nfs_fattr dir_attr; + struct nfs4_change_info dir_cinfo; + int status; + + dir_attr.valid = 0; + fattr->valid = 0; + + nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "mknod"); + nfs4_setup_putfh(&compound, NFS_FH(dir)); + nfs4_setup_savefh(&compound); + nfs4_setup_create_special(&compound, name, rdev,sattr, &dir_cinfo); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, fattr, NULL); + nfs4_setup_getfh(&compound, fh); + nfs4_setup_restorefh(&compound); + nfs4_setup_getattr(&compound, nfs4_standard_bitmap, &dir_attr, NULL); + status = nfs4_call_compound(&compound, NULL, 0); + + if (!status) { + process_cinfo(&dir_cinfo, &dir_attr); + nfs_refresh_inode(dir, &dir_attr); + } + return status; +} + +static int +nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_fsinfo *info) +{ + struct nfs4_compound compound; + struct nfs4_op ops[2]; + + memset(info, 0, sizeof(*info)); + nfs4_setup_compound(&compound, ops, server, "statfs"); + nfs4_setup_putfh(&compound, fhandle); + nfs4_setup_getattr(&compound, nfs4_statfs_bitmap, NULL, info); + return nfs4_call_compound(&compound, NULL, 0); +} + +static void +nfs4_read_done(struct rpc_task *task) +{ + struct nfs_read_data *data = (struct nfs_read_data *) task->tk_calldata; + + process_lease(&data->u.v4.compound); + nfs_readpage_result(task, data->u.v4.res_count); +} + +static void +nfs4_proc_read_setup(struct nfs_read_data *data, unsigned int count) +{ + struct rpc_task *task = &data->task; + struct nfs4_compound *cp = &data->u.v4.compound; + struct rpc_message msg = { + rpc_proc: NFSPROC4_COMPOUND, + rpc_argp: cp, + rpc_resp: cp, + rpc_cred: data->cred, + }; + struct inode *inode = data->inode; + struct nfs_page *req = nfs_list_entry(data->pages.next); + int flags; + + nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "read [async]"); + nfs4_setup_putfh(cp, NFS_FH(inode)); + nfs4_setup_read(cp, page_offset(req->wb_page) + req->wb_offset, + count, data->pagevec, req->wb_offset, NULL, + &data->u.v4.res_count); + + /* N.B. Do we need to test? Never called for swapfile inode */ + flags = RPC_TASK_ASYNC | (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0); + + /* Finalize the task. */ + rpc_init_task(task, NFS_CLIENT(inode), nfs4_read_done, flags); + task->tk_calldata = data; + /* Release requests */ + task->tk_release = nfs_readdata_release; + + rpc_call_setup(task, &msg, 0); +} + +static void +nfs4_write_done(struct rpc_task *task) +{ + struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata; + + process_lease(&data->u.v4.compound); + nfs_writeback_done(task, data->u.v4.arg_stable, + data->u.v4.arg_count, data->u.v4.res_count); +} + +static void +nfs4_proc_write_setup(struct nfs_write_data *data, unsigned int count, int how) +{ + struct rpc_task *task = &data->task; + struct nfs4_compound *cp = &data->u.v4.compound; + struct rpc_message msg = { + rpc_proc: NFSPROC4_COMPOUND, + rpc_argp: cp, + rpc_resp: cp, + rpc_cred: data->cred, + }; + struct inode *inode = data->inode; + struct nfs_page *req = nfs_list_entry(data->pages.next); + int stable; + int flags; + + if (how & FLUSH_STABLE) { + if (!NFS_I(inode)->ncommit) + stable = NFS_FILE_SYNC; + else + stable = NFS_DATA_SYNC; + } else + stable = NFS_UNSTABLE; + + nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "write [async]"); + nfs4_setup_putfh(cp, NFS_FH(inode)); + nfs4_setup_write(cp, page_offset(req->wb_page) + req->wb_offset, + count, stable, data->pagevec, req->wb_offset, + &data->u.v4.res_count, &data->verf); + + /* Set the initial flags for the task. */ + flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC; + + /* Finalize the task. */ + rpc_init_task(task, NFS_CLIENT(inode), nfs4_write_done, flags); + task->tk_calldata = data; + /* Release requests */ + task->tk_release = nfs_writedata_release; + + rpc_call_setup(task, &msg, 0); +} + +static void +nfs4_commit_done(struct rpc_task *task) +{ + struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata; + + process_lease(&data->u.v4.compound); + nfs_commit_done(task); +} + +static void +nfs4_proc_commit_setup(struct nfs_write_data *data, u64 start, u32 len, int how) +{ + struct rpc_task *task = &data->task; + struct nfs4_compound *cp = &data->u.v4.compound; + struct rpc_message msg = { + rpc_proc: NFSPROC4_COMPOUND, + rpc_argp: cp, + rpc_resp: cp, + rpc_cred: data->cred, + }; + struct inode *inode = data->inode; + int flags; + + nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "commit [async]"); + nfs4_setup_putfh(cp, NFS_FH(inode)); + nfs4_setup_commit(cp, start, len, &data->verf); + + /* Set the initial flags for the task. */ + flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC; + + /* Finalize the task. */ + rpc_init_task(task, NFS_CLIENT(inode), nfs4_commit_done, flags); + task->tk_calldata = data; + /* Release requests */ + task->tk_release = nfs_writedata_release; + + rpc_call_setup(task, &msg, 0); +} + +/* + * nfs4_proc_renew(): This is not one of the nfs_rpc_ops; it is a special + * standalone procedure for queueing an asynchronous RENEW. + */ +struct renew_desc { + struct rpc_task task; + struct nfs4_compound compound; + struct nfs4_op ops[1]; +}; + +static void +renew_done(struct rpc_task *task) +{ + struct nfs4_compound *cp = (struct nfs4_compound *) task->tk_msg.rpc_argp; + process_lease(cp); +} + +static void +renew_release(struct rpc_task *task) +{ + kfree(task->tk_calldata); + task->tk_calldata = NULL; +} + +int +nfs4_proc_renew(struct nfs_server *server) +{ + struct renew_desc *rp; + struct rpc_task *task; + struct nfs4_compound *cp; + struct rpc_message msg; + + rp = (struct renew_desc *) kmalloc(sizeof(*rp), GFP_KERNEL); + if (!rp) + return -ENOMEM; + cp = &rp->compound; + task = &rp->task; + + nfs4_setup_compound(cp, rp->ops, server, "renew"); + nfs4_setup_renew(cp); + + msg.rpc_proc = NFSPROC4_COMPOUND; + msg.rpc_argp = cp; + msg.rpc_resp = cp; + msg.rpc_cred = NULL; + rpc_init_task(task, server->client, renew_done, RPC_TASK_ASYNC); + rpc_call_setup(task, &msg, 0); + task->tk_calldata = rp; + task->tk_release = renew_release; + + return rpc_execute(task); +} + +struct nfs_rpc_ops nfs_v4_clientops = { + version: 4, /* protocol version */ + getroot: nfs4_proc_get_root, + getattr: nfs4_proc_getattr, + setattr: nfs4_proc_setattr, + lookup: nfs4_proc_lookup, + access: nfs4_proc_access, + readlink: nfs4_proc_readlink, + read: nfs4_proc_read, + write: nfs4_proc_write, + commit: NULL, + create: nfs4_proc_create, + remove: nfs4_proc_remove, + unlink_setup: nfs4_proc_unlink_setup, + unlink_done: nfs4_proc_unlink_done, + rename: nfs4_proc_rename, + link: nfs4_proc_link, + symlink: nfs4_proc_symlink, + mkdir: nfs4_proc_mkdir, + rmdir: nfs4_proc_remove, + readdir: nfs4_proc_readdir, + mknod: nfs4_proc_mknod, + statfs: nfs4_proc_statfs, + decode_dirent: nfs4_decode_dirent, + read_setup: nfs4_proc_read_setup, + write_setup: nfs4_proc_write_setup, + commit_setup: nfs4_proc_commit_setup, +}; + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ --- old/fs/nfs/nfs4renewd.c Wed Dec 31 18:00:00 1969 +++ new/fs/nfs/nfs4renewd.c Mon Aug 12 11:45:35 2002 @@ -0,0 +1,110 @@ +/* + * fs/nfs/nfs4renewd.c + * + * Copyright (c) 2002 The Regents of the University of Michigan. + * All rights reserved. + * + * Kendrick Smith + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Implementation of the NFSv4 "renew daemon", which wakes up periodically to + * send a RENEW, to keep state alive on the server. The daemon is implemented + * as an rpc_task, not a real kernel thread, so it always runs in rpciod's + * context. There is one renewd per nfs_server. + * + * TODO: If the send queue gets backlogged (e.g., if the server goes down), + * we will keep filling the queue with periodic RENEW requests. We need a + * mechanism for ensuring that if renewd successfully sends off a request, + * then it only wakes up when the request is finished. Maybe use the + * child task framework of the RPC layer? + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static RPC_WAITQ(nfs4_renewd_queue, "nfs4_renewd_queue"); + +static void +renewd(struct rpc_task *task) +{ + struct nfs_server *server = (struct nfs_server *)task->tk_calldata; + unsigned long lease = server->lease_time; + unsigned long last = server->last_renewal; + unsigned long timeout; + + if (!server->nfs4_state) + timeout = (2 * lease) / 3; + else if (jiffies < last + lease/3) + timeout = (2 * lease) / 3 + last - jiffies; + else { + /* Queue an asynchronous RENEW. */ + nfs4_proc_renew(server); + timeout = (2 * lease) / 3; + } + + if (timeout < 5 * HZ) /* safeguard */ + timeout = 5 * HZ; + task->tk_timeout = timeout; + task->tk_action = renewd; + task->tk_exit = NULL; + rpc_sleep_on(&nfs4_renewd_queue, task, NULL, NULL); + return; +} + +int +nfs4_init_renewd(struct nfs_server *server) +{ + struct rpc_task *task; + int status; + + lock_kernel(); + status = -ENOMEM; + task = rpc_new_task(server->client, NULL, RPC_TASK_ASYNC); + if (!task) + goto out; + task->tk_calldata = server; + task->tk_action = renewd; + status = rpc_execute(task); + +out: + unlock_kernel(); + return status; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ --- old/fs/nfs/nfs4state.c Wed Dec 31 18:00:00 1969 +++ new/fs/nfs/nfs4state.c Mon Aug 12 11:45:35 2002 @@ -0,0 +1,81 @@ +/* + * fs/nfs/nfs4state.c + * + * Client-side XDR for NFSv4. + * + * Copyright (c) 2002 The Regents of the University of Michigan. + * All rights reserved. + * + * Kendrick Smith + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Implementation of the NFSv4 state model. For the time being, + * this is minimal, but will be made much more complex in a + * subsequent patch. + */ + +#include +#include +#include + +/* + * nfs4_get_client(): returns an empty client structure + * nfs4_put_client(): drops reference to client structure + * + * Since these are allocated/deallocated very rarely, we don't + * bother putting them in a slab cache... + */ +struct nfs4_client * +nfs4_get_client(void) +{ + struct nfs4_client *clp; + + if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL))) { + atomic_set(&clp->cl_count, 1); + clp->cl_clientid = 0; + INIT_LIST_HEAD(&clp->cl_lockowners); + } + return clp; +} + +void +nfs4_put_client(struct nfs4_client *clp) +{ + BUG_ON(!clp); + BUG_ON(!atomic_read(&clp->cl_count)); + + if (atomic_dec_and_test(&clp->cl_count)) { + BUG_ON(!list_empty(&clp->cl_lockowners)); + kfree(clp); + } +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ --- old/fs/nfs/nfs4xdr.c Wed Dec 31 18:00:00 1969 +++ new/fs/nfs/nfs4xdr.c Mon Aug 12 11:45:38 2002 @@ -0,0 +1,1712 @@ +/* + * fs/nfs/nfs4xdr.c + * + * Client-side XDR for NFSv4. + * + * Copyright (c) 2002 The Regents of the University of Michigan. + * All rights reserved. + * + * Kendrick Smith + * Andy Adamson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Emperically, it seems that the NFS client gets confused if + * cookies larger than this are returned -- presumably a + * signedness issue? + */ +#define COOKIE_MAX 0x7fffffff + +#define NFS4_CLIENTID(server) ((server)->nfs4_state->cl_clientid) + +#define NFSDBG_FACILITY NFSDBG_XDR + +/* Mapping from NFS error code to "errno" error code. */ +#define errno_NFSERR_IO EIO + +extern int nfs_stat_to_errno(int); + +#define NFS4_enc_void_sz 0 +#define NFS4_dec_void_sz 0 +#define NFS4_enc_compound_sz 1024 /* XXX: large enough? */ +#define NFS4_dec_compound_sz 1024 /* XXX: large enough? */ + +static struct { + unsigned int mode; + unsigned int nfs2type; +} nfs_type2fmt[] = { + { 0, NFNON }, + { S_IFREG, NFREG }, + { S_IFDIR, NFDIR }, + { S_IFBLK, NFBLK }, + { S_IFCHR, NFCHR }, + { S_IFLNK, NFLNK }, + { S_IFSOCK, NFSOCK }, + { S_IFIFO, NFFIFO }, + { 0, NFNON }, + { 0, NFNON }, +}; + +/* + * START OF "GENERIC" ENCODE ROUTINES. + * These may look a little ugly since they are imported from a "generic" + * set of XDR encode/decode routines which are intended to be shared by + * all of our NFSv4 implementations (OpenBSD, MacOS X...). + * + * If the pain of reading these is too great, it should be a straightforward + * task to translate them into Linux-specific versions which are more + * consistent with the style used in NFSv2/v3... + */ +#define ENCODE_HEAD \ + u32 *p; +#define ENCODE_TAIL \ + return 0 + +#define WRITE32(n) *p++ = htonl(n) +#define WRITE64(n) do { \ + *p++ = htonl((u32)((n) >> 32)); \ + *p++ = htonl((u32)(n)); \ +} while (0) +#define WRITEMEM(ptr,nbytes) do { \ + memcpy(p, ptr, nbytes); \ + p += XDR_QUADLEN(nbytes); \ +} while (0) + +#define RESERVE_SPACE(nbytes) do { BUG_ON(cp->p + XDR_QUADLEN(nbytes) > cp->end); p = cp->p; } while (0) +#define ADJUST_ARGS() cp->p = p + +static int +encode_attrs(struct nfs4_compound *cp, struct iattr *iap) +{ + u32 *q; + int len; + u32 bmval0 = 0; + u32 bmval1 = 0; + struct gss_cacheent *owner = NULL; + struct gss_cacheent *group = NULL; + int status; + ENCODE_HEAD; + + /* + * We reserve enough space to write the entire attribute buffer at once. + * In the worst-case, this would be + * 12(bitmap) + 4(attrlen) + 8(size) + 4(mode) + 4(atime) + 4(mtime) + * = 36 bytes, plus any contribution from variable-length fields + * such as owner/group/acl's. + */ + len = 36; + if (iap->ia_valid & ATTR_UID) { + if ((status = gss_get_name(GSS_OWNER, iap->ia_uid, &owner))) { + printk(KERN_WARNING "nfs: couldn't resolve uid %d to string\n", + iap->ia_uid); + goto out; + } + len += (XDR_QUADLEN(owner->name.len) << 2) + 4; + } + if (iap->ia_valid & ATTR_GID) { + if ((status = gss_get_name(GSS_GROUP, iap->ia_gid, &group))) { + printk(KERN_WARNING "nfs4: couldn't resolve uid %d to string\n", + iap->ia_gid); + goto out; + } + len += (XDR_QUADLEN(group->name.len) << 2) + 4; + } + RESERVE_SPACE(len); + + /* + * We write the bitmap length now, but leave the bitmap and the attribute + * buffer length to be backfilled at the end of this routine. + */ + WRITE32(2); + q = p; + p += 3; + + if (iap->ia_valid & ATTR_SIZE) { + bmval0 |= FATTR4_WORD0_SIZE; + WRITE64(iap->ia_size); + } + if (iap->ia_valid & ATTR_MODE) { + bmval1 |= FATTR4_WORD1_MODE; + WRITE32(iap->ia_mode); + } + if (iap->ia_valid & ATTR_UID) { + bmval1 |= FATTR4_WORD1_OWNER; + WRITE32(owner->name.len); + WRITEMEM(owner->name.name, owner->name.len); + } + if (iap->ia_valid & ATTR_GID) { + bmval1 |= FATTR4_WORD1_OWNER_GROUP; + WRITE32(group->name.len); + WRITEMEM(group->name.name, group->name.len); + } + if (iap->ia_valid & ATTR_ATIME_SET) { + bmval1 |= FATTR4_WORD1_TIME_ACCESS_SET; + WRITE32(NFS4_SET_TO_CLIENT_TIME); + WRITE32(0); + WRITE32(iap->ia_mtime); + WRITE32(0); + } + else if (iap->ia_valid & ATTR_ATIME) { + bmval1 |= FATTR4_WORD1_TIME_ACCESS_SET; + WRITE32(NFS4_SET_TO_SERVER_TIME); + } + if (iap->ia_valid & ATTR_MTIME_SET) { + bmval1 |= FATTR4_WORD1_TIME_MODIFY_SET; + WRITE32(NFS4_SET_TO_CLIENT_TIME); + WRITE32(0); + WRITE32(iap->ia_mtime); + WRITE32(0); + } + else if (iap->ia_valid & ATTR_MTIME) { + bmval1 |= FATTR4_WORD1_TIME_MODIFY_SET; + WRITE32(NFS4_SET_TO_SERVER_TIME); + } + + ADJUST_ARGS(); + + /* + * Now we backfill the bitmap and the attribute buffer length. + */ + len = (char *)p - (char *)q - 12; + *q++ = htonl(bmval0); + *q++ = htonl(bmval1); + *q++ = htonl(len); + + status = 0; +out: + if (owner) + gss_put(owner); + if (group) + gss_put(group); + return status; +} + +static int +encode_access(struct nfs4_compound *cp, struct nfs4_access *access) +{ + ENCODE_HEAD; + + RESERVE_SPACE(8); + WRITE32(OP_ACCESS); + WRITE32(access->ac_req_access); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_close(struct nfs4_compound *cp, struct nfs4_close *close) +{ + ENCODE_HEAD; + + RESERVE_SPACE(20); + WRITE32(OP_CLOSE); + WRITE32(close->cl_seqid); + WRITEMEM(close->cl_stateid, sizeof(nfs4_stateid)); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_commit(struct nfs4_compound *cp, struct nfs4_commit *commit) +{ + ENCODE_HEAD; + + RESERVE_SPACE(16); + WRITE32(OP_COMMIT); + WRITE64(commit->co_start); + WRITE32(commit->co_len); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_create(struct nfs4_compound *cp, struct nfs4_create *create) +{ + ENCODE_HEAD; + + RESERVE_SPACE(8); + WRITE32(OP_CREATE); + WRITE32(create->cr_ftype); + ADJUST_ARGS(); + + switch (create->cr_ftype) { + case NF4LNK: + RESERVE_SPACE(4 + create->cr_textlen); + WRITE32(create->cr_textlen); + WRITEMEM(create->cr_text, create->cr_textlen); + ADJUST_ARGS(); + break; + + case NF4BLK: case NF4CHR: + RESERVE_SPACE(8); + WRITE32(create->cr_specdata1); + WRITE32(create->cr_specdata2); + ADJUST_ARGS(); + break; + + default: + break; + } + + RESERVE_SPACE(4 + create->cr_namelen); + WRITE32(create->cr_namelen); + WRITEMEM(create->cr_name, create->cr_namelen); + ADJUST_ARGS(); + + return encode_attrs(cp, create->cr_attrs); +} + +static int +encode_getattr(struct nfs4_compound *cp, struct nfs4_getattr *getattr) +{ + ENCODE_HEAD; + + RESERVE_SPACE(16); + WRITE32(OP_GETATTR); + WRITE32(2); + WRITE32(getattr->gt_bmval[0]); + WRITE32(getattr->gt_bmval[1]); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_getfh(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(4); + WRITE32(OP_GETFH); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_link(struct nfs4_compound *cp, struct nfs4_link *link) +{ + ENCODE_HEAD; + + RESERVE_SPACE(8 + link->ln_namelen); + WRITE32(OP_LINK); + WRITE32(link->ln_namelen); + WRITEMEM(link->ln_name, link->ln_namelen); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_lookup(struct nfs4_compound *cp, struct nfs4_lookup *lookup) +{ + int len = lookup->lo_name->len; + ENCODE_HEAD; + + RESERVE_SPACE(8 + len); + WRITE32(OP_LOOKUP); + WRITE32(len); + WRITEMEM(lookup->lo_name->name, len); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_open(struct nfs4_compound *cp, struct nfs4_open *open) +{ + static int global_id = 0; + int id = global_id++; + int status; + ENCODE_HEAD; + + /* seqid, share_access, share_deny, clientid, ownerlen, owner, opentype */ + RESERVE_SPACE(52); + WRITE32(OP_OPEN); + WRITE32(0); /* seqid */ + WRITE32(open->op_share_access); + WRITE32(0); /* for us, share_deny== 0 always */ + WRITE64(NFS4_CLIENTID(cp->server)); + WRITE32(4); + WRITE32(id); + WRITE32(open->op_opentype); + ADJUST_ARGS(); + + if (open->op_opentype == NFS4_OPEN_CREATE) { + if (open->op_createmode == NFS4_CREATE_EXCLUSIVE) { + RESERVE_SPACE(12); + WRITE32(open->op_createmode); + WRITEMEM(open->op_verifier, sizeof(nfs4_verifier)); + ADJUST_ARGS(); + } + else if (open->op_attrs) { + RESERVE_SPACE(4); + WRITE32(open->op_createmode); + ADJUST_ARGS(); + if ((status = encode_attrs(cp, open->op_attrs))) + return status; + } + else { + RESERVE_SPACE(12); + WRITE32(open->op_createmode); + WRITE32(0); + WRITE32(0); + ADJUST_ARGS(); + } + } + + RESERVE_SPACE(8 + open->op_name->len); + WRITE32(NFS4_OPEN_CLAIM_NULL); + WRITE32(open->op_name->len); + WRITEMEM(open->op_name->name, open->op_name->len); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_open_confirm(struct nfs4_compound *cp, struct nfs4_open_confirm *open_confirm) +{ + ENCODE_HEAD; + + /* + * Note: In this "stateless" implementation, the OPEN_CONFIRM + * seqid is always equal to 1. + */ + RESERVE_SPACE(24); + WRITE32(OP_OPEN_CONFIRM); + WRITEMEM(open_confirm->oc_stateid, sizeof(nfs4_stateid)); + WRITE32(1); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_putfh(struct nfs4_compound *cp, struct nfs4_putfh *putfh) +{ + int len = putfh->pf_fhandle->size; + ENCODE_HEAD; + + RESERVE_SPACE(8 + len); + WRITE32(OP_PUTFH); + WRITE32(len); + WRITEMEM(putfh->pf_fhandle->data, len); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_putrootfh(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(4); + WRITE32(OP_PUTROOTFH); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_read(struct nfs4_compound *cp, struct nfs4_read *read, struct rpc_rqst *req) +{ + struct rpc_auth *auth = req->rq_task->tk_auth; + int replen; + ENCODE_HEAD; + + RESERVE_SPACE(32); + WRITE32(OP_READ); + WRITE32(0); /* all-zero stateid! */ + WRITE32(0); + WRITE32(0); + WRITE32(0); + WRITE64(read->rd_offset); + WRITE32(read->rd_length); + ADJUST_ARGS(); + + /* set up reply iovec + * toplevel status + taglen + rescount + OP_PUTFH + status + * + OP_READ + status + eof + datalen = 9 + */ + replen = (RPC_REPHDRSIZE + auth->au_rslack + 9 + XDR_QUADLEN(cp->taglen)) << 2; + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + xdr_inline_pages(&req->rq_rcv_buf, replen, + read->rd_pages, read->rd_pgbase, read->rd_length); + + ENCODE_TAIL; +} + +static int +encode_readdir(struct nfs4_compound *cp, struct nfs4_readdir *readdir, struct rpc_rqst *req) +{ + struct rpc_auth *auth = req->rq_task->tk_auth; + int replen; + ENCODE_HEAD; + + RESERVE_SPACE(40); + WRITE32(OP_READDIR); + WRITE64(readdir->rd_cookie); + WRITEMEM(readdir->rd_req_verifier, sizeof(nfs4_verifier)); + WRITE32(readdir->rd_count >> 5); /* meaningless "dircount" field */ + WRITE32(readdir->rd_count); + WRITE32(2); + WRITE32(readdir->rd_bmval[0]); + WRITE32(readdir->rd_bmval[1]); + ADJUST_ARGS(); + + /* set up reply iovec + * toplevel_status + taglen + rescount + OP_PUTFH + status + * + OP_READDIR + status + verifer(2) = 9 + */ + replen = (RPC_REPHDRSIZE + auth->au_rslack + 9 + XDR_QUADLEN(cp->taglen)) << 2; + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + xdr_inline_pages(&req->rq_rcv_buf, replen, readdir->rd_pages, + readdir->rd_pgbase, readdir->rd_count); + + ENCODE_TAIL; +} + +static int +encode_readlink(struct nfs4_compound *cp, struct nfs4_readlink *readlink, struct rpc_rqst *req) +{ + struct rpc_auth *auth = req->rq_task->tk_auth; + int replen; + ENCODE_HEAD; + + RESERVE_SPACE(4); + WRITE32(OP_READLINK); + ADJUST_ARGS(); + + /* set up reply iovec + * toplevel_status + taglen + rescount + OP_PUTFH + status + * + OP_READLINK + status = 7 + */ + replen = (RPC_REPHDRSIZE + auth->au_rslack + 7 + XDR_QUADLEN(cp->taglen)) << 2; + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + xdr_inline_pages(&req->rq_rcv_buf, replen, readlink->rl_pages, 0, readlink->rl_count); + + ENCODE_TAIL; +} + +static int +encode_remove(struct nfs4_compound *cp, struct nfs4_remove *remove) +{ + ENCODE_HEAD; + + RESERVE_SPACE(8 + remove->rm_namelen); + WRITE32(OP_REMOVE); + WRITE32(remove->rm_namelen); + WRITEMEM(remove->rm_name, remove->rm_namelen); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_rename(struct nfs4_compound *cp, struct nfs4_rename *rename) +{ + ENCODE_HEAD; + + RESERVE_SPACE(8 + rename->rn_oldnamelen); + WRITE32(OP_RENAME); + WRITE32(rename->rn_oldnamelen); + WRITEMEM(rename->rn_oldname, rename->rn_oldnamelen); + ADJUST_ARGS(); + + RESERVE_SPACE(8 + rename->rn_newnamelen); + WRITE32(rename->rn_newnamelen); + WRITEMEM(rename->rn_newname, rename->rn_newnamelen); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_renew(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(12); + WRITE32(OP_RENEW); + WRITE64(NFS4_CLIENTID(cp->server)); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_restorefh(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(4); + WRITE32(OP_RESTOREFH); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_savefh(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(4); + WRITE32(OP_SAVEFH); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_setattr(struct nfs4_compound *cp, struct nfs4_setattr *setattr) +{ + int status; + ENCODE_HEAD; + + RESERVE_SPACE(20); + WRITE32(OP_SETATTR); + WRITEMEM(setattr->st_stateid, sizeof(nfs4_stateid)); + ADJUST_ARGS(); + + if ((status = encode_attrs(cp, setattr->st_iap))) + return status; + + ENCODE_TAIL; +} + +static int +encode_setclientid(struct nfs4_compound *cp, struct nfs4_setclientid *setclientid) +{ + u32 total_len; + u32 len1, len2, len3; + ENCODE_HEAD; + + len1 = strlen(setclientid->sc_name); + len2 = strlen(setclientid->sc_netid); + len3 = strlen(setclientid->sc_uaddr); + total_len = XDR_QUADLEN(len1) + XDR_QUADLEN(len2) + XDR_QUADLEN(len3); + total_len = (total_len << 2) + 32; + + RESERVE_SPACE(total_len); + WRITE32(OP_SETCLIENTID); + WRITEMEM(setclientid->sc_verifier, sizeof(nfs4_verifier)); + WRITE32(len1); + WRITEMEM(setclientid->sc_name, len1); + WRITE32(setclientid->sc_prog); + WRITE32(len2); + WRITEMEM(setclientid->sc_netid, len2); + WRITE32(len3); + WRITEMEM(setclientid->sc_uaddr, len3); + WRITE32(setclientid->sc_cb_ident); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_setclientid_confirm(struct nfs4_compound *cp) +{ + ENCODE_HEAD; + + RESERVE_SPACE(12); + WRITE32(OP_SETCLIENTID_CONFIRM); + WRITE64(cp->server->nfs4_state->cl_clientid); + ADJUST_ARGS(); + + ENCODE_TAIL; +} + +static int +encode_write(struct nfs4_compound *cp, struct nfs4_write *write, struct rpc_rqst *req) +{ + struct xdr_buf *sndbuf = &req->rq_snd_buf; + ENCODE_HEAD; + + RESERVE_SPACE(36); + WRITE32(OP_WRITE); + WRITE32(0xffffffff); /* magic stateid -1 */ + WRITE32(0xffffffff); + WRITE32(0xffffffff); + WRITE32(0xffffffff); + WRITE64(write->wr_offset); + WRITE32(write->wr_stable_how); + WRITE32(write->wr_len); + ADJUST_ARGS(); + + sndbuf->len = xdr_adjust_iovec(sndbuf->head, p); + xdr_encode_pages(sndbuf, write->wr_pages, write->wr_pgbase, write->wr_len); + + ENCODE_TAIL; +} + +static int +encode_compound(struct nfs4_compound *cp, struct rpc_rqst *req) +{ + int i, status = 0; + ENCODE_HEAD; + + dprintk("encode_compound: tag=%.*s\n", (int)cp->taglen, cp->tag); + + RESERVE_SPACE(12 + cp->taglen); + WRITE32(cp->taglen); + WRITEMEM(cp->tag, cp->taglen); + WRITE32(NFS4_MINOR_VERSION); + WRITE32(cp->req_nops); + ADJUST_ARGS(); + + for (i = 0; i < cp->req_nops; i++) { + switch (cp->ops[i].opnum) { + case OP_ACCESS: + status = encode_access(cp, &cp->ops[i].u.access); + break; + case OP_CLOSE: + status = encode_close(cp, &cp->ops[i].u.close); + break; + case OP_COMMIT: + status = encode_commit(cp, &cp->ops[i].u.commit); + break; + case OP_CREATE: + status = encode_create(cp, &cp->ops[i].u.create); + break; + case OP_GETATTR: + status = encode_getattr(cp, &cp->ops[i].u.getattr); + break; + case OP_GETFH: + status = encode_getfh(cp); + break; + case OP_LINK: + status = encode_link(cp, &cp->ops[i].u.link); + break; + case OP_LOOKUP: + status = encode_lookup(cp, &cp->ops[i].u.lookup); + break; + case OP_OPEN: + status = encode_open(cp, &cp->ops[i].u.open); + break; + case OP_OPEN_CONFIRM: + status = encode_open_confirm(cp, &cp->ops[i].u.open_confirm); + break; + case OP_PUTFH: + status = encode_putfh(cp, &cp->ops[i].u.putfh); + break; + case OP_PUTROOTFH: + status = encode_putrootfh(cp); + break; + case OP_READ: + status = encode_read(cp, &cp->ops[i].u.read, req); + break; + case OP_READDIR: + status = encode_readdir(cp, &cp->ops[i].u.readdir, req); + break; + case OP_READLINK: + status = encode_readlink(cp, &cp->ops[i].u.readlink, req); + break; + case OP_REMOVE: + status = encode_remove(cp, &cp->ops[i].u.remove); + break; + case OP_RENAME: + status = encode_rename(cp, &cp->ops[i].u.rename); + break; + case OP_RENEW: + status = encode_renew(cp); + break; + case OP_RESTOREFH: + status = encode_restorefh(cp); + break; + case OP_SAVEFH: + status = encode_savefh(cp); + break; + case OP_SETATTR: + status = encode_setattr(cp, &cp->ops[i].u.setattr); + break; + case OP_SETCLIENTID: + status = encode_setclientid(cp, &cp->ops[i].u.setclientid); + break; + case OP_SETCLIENTID_CONFIRM: + status = encode_setclientid_confirm(cp); + break; + case OP_WRITE: + status = encode_write(cp, &cp->ops[i].u.write, req); + break; + default: + BUG(); + } + if (status) + return status; + } + + ENCODE_TAIL; +} +/* + * END OF "GENERIC" ENCODE ROUTINES. + */ + + +/* + * Encode void argument + */ +static int +nfs4_xdr_enc_void(struct rpc_rqst *req, u32 *p, void *dummy) +{ + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + return 0; +} + +/* + * Encode COMPOUND argument + */ +static int +nfs4_xdr_enc_compound(struct rpc_rqst *req, u32 *p, struct nfs4_compound *cp) +{ + int status; + struct xdr_buf *sndbuf = &req->rq_snd_buf; + + cp->p = p; + cp->end = (u32 *) ((char *)req->rq_svec[0].iov_base + req->rq_svec[0].iov_len); + status = encode_compound(cp, req); + cp->timestamp = jiffies; + + if (!status && !sndbuf->page_len) + req->rq_slen = xdr_adjust_iovec(sndbuf->head, cp->p); + return status; +} + + +/* + * START OF "GENERIC" DECODE ROUTINES. + * These may look a little ugly since they are imported from a "generic" + * set of XDR encode/decode routines which are intended to be shared by + * all of our NFSv4 implementations (OpenBSD, MacOS X...). + * + * If the pain of reading these is too great, it should be a straightforward + * task to translate them into Linux-specific versions which are more + * consistent with the style used in NFSv2/v3... + */ +#define DECODE_HEAD \ + u32 *p; \ + int status +#define DECODE_TAIL \ + status = 0; \ +out: \ + return status; \ +xdr_error: \ + printk(KERN_NOTICE "xdr error! (%s:%d)\n", __FILE__, __LINE__); \ + status = -EIO; \ + goto out + +#define READ32(x) (x) = ntohl(*p++) +#define READ64(x) do { \ + (x) = (u64)ntohl(*p++) << 32; \ + (x) |= ntohl(*p++); \ +} while (0) +#define READTIME(x) do { \ + p++; \ + (x) = (u64)ntohl(*p++) << 32; \ + (x) |= ntohl(*p++); \ +} while (0) +#define COPYMEM(x,nbytes) do { \ + memcpy((x), p, nbytes); \ + p += XDR_QUADLEN(nbytes); \ +} while (0) + +#define READ_BUF(nbytes) do { \ + if (nbytes > (u32)((char *)cp->end - (char *)cp->p)) \ + goto xdr_error; \ + p = cp->p; \ + cp->p += XDR_QUADLEN(nbytes); \ +} while (0) + +static int +decode_change_info(struct nfs4_compound *cp, struct nfs4_change_info *cinfo) +{ + DECODE_HEAD; + + READ_BUF(20); + READ32(cinfo->atomic); + READ64(cinfo->before); + READ64(cinfo->after); + + DECODE_TAIL; +} + +static int +decode_access(struct nfs4_compound *cp, int nfserr, struct nfs4_access *access) +{ + u32 supp, acc; + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(8); + READ32(supp); + READ32(acc); + + status = -EIO; + if ((supp & ~access->ac_req_access) || (acc & ~supp)) { + printk(KERN_NOTICE "NFS: server returned bad bits in access call!\n"); + goto out; + } + *access->ac_resp_supported = supp; + *access->ac_resp_access = acc; + } + + DECODE_TAIL; +} + +static int +decode_close(struct nfs4_compound *cp, int nfserr, struct nfs4_close *close) +{ + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(sizeof(nfs4_stateid)); + COPYMEM(close->cl_stateid, sizeof(nfs4_stateid)); + } + + DECODE_TAIL; +} + +static int +decode_commit(struct nfs4_compound *cp, int nfserr, struct nfs4_commit *commit) +{ + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(8); + COPYMEM(commit->co_verifier->verifier, 8); + } + + DECODE_TAIL; +} + +static int +decode_create(struct nfs4_compound *cp, int nfserr, struct nfs4_create *create) +{ + u32 bmlen; + DECODE_HEAD; + + if (!nfserr) { + if ((status = decode_change_info(cp, create->cr_cinfo))) + goto out; + READ_BUF(4); + READ32(bmlen); + if (bmlen > 2) + goto xdr_error; + READ_BUF(bmlen << 2); + } + + DECODE_TAIL; +} + +static int +decode_getattr(struct nfs4_compound *cp, int nfserr, struct nfs4_getattr *getattr) +{ + struct nfs_fattr *nfp = getattr->gt_attrs; + struct nfs_fsinfo *fsinfo = getattr->gt_fsinfo; + u32 bmlen; + u32 bmval0 = 0; + u32 bmval1 = 0; + u32 attrlen; + u32 dummy32; + u32 len = 0; + unsigned int type; + int fmode = 0; + DECODE_HEAD; + + if (nfserr) + goto success; + + READ_BUF(4); + READ32(bmlen); + if (bmlen > 2) + goto xdr_error; + + READ_BUF((bmlen << 2) + 4); + if (bmlen > 0) + READ32(bmval0); + if (bmlen > 1) + READ32(bmval1); + READ32(attrlen); + + if ((bmval0 & ~getattr->gt_bmval[0]) || + (bmval1 & ~getattr->gt_bmval[1])) { + dprintk("read_attrs: server returned bad attributes!\n"); + goto xdr_error; + } + + /* + * In case the server doesn't return some attributes, + * we initialize them here to some nominal values.. + */ + if (nfp) { + memset(nfp, 0, sizeof(*nfp)); + nfp->valid = NFS_ATTR_FATTR | NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4; + nfp->nlink = 1; + nfp->timestamp = jiffies; + } + if (fsinfo) { + memset(fsinfo, 0, sizeof(*fsinfo)); + fsinfo->bsize = fsinfo->rtmult = fsinfo->wtmult = 512; /* ??? */ + fsinfo->lease_time = 60; + } + + if (bmval0 & FATTR4_WORD0_TYPE) { + READ_BUF(4); + len += 4; + READ32(type); + if (type < NF4REG || type > NF4NAMEDATTR) { + dprintk("read_attrs: bad type %d\n", type); + goto xdr_error; + } + nfp->type = nfs_type2fmt[type].nfs2type; + fmode = nfs_type2fmt[type].mode; + dprintk("read_attrs: type=%d\n", (u32)nfp->type); + } + if (bmval0 & FATTR4_WORD0_CHANGE) { + READ_BUF(8); + len += 8; + READ64(nfp->change_attr); + dprintk("read_attrs: changeid=%Ld\n", (u64)nfp->change_attr); + } + if (bmval0 & FATTR4_WORD0_SIZE) { + READ_BUF(8); + len += 8; + READ64(nfp->size); + dprintk("read_attrs: size=%Ld\n", (u64)nfp->size); + } + if (bmval0 & FATTR4_WORD0_FSID) { + READ_BUF(16); + len += 16; + READ64(nfp->fsid_u.nfs4.major); + READ64(nfp->fsid_u.nfs4.minor); + dprintk("read_attrs: fsid=0x%Lx/0x%Lx\n", + nfp->fsid_u.nfs4.major, nfp->fsid_u.nfs4.minor); + } + if (bmval0 & FATTR4_WORD0_LEASE_TIME) { + READ_BUF(4); + len += 4; + READ32(fsinfo->lease_time); + dprintk("read_attrs: lease_time=%d\n", fsinfo->lease_time); + } + if (bmval0 & FATTR4_WORD0_FILEID) { + READ_BUF(8); + len += 8; + READ64(nfp->fileid); + dprintk("read_attrs: fileid=%Ld\n", nfp->fileid); + } + if (bmval0 & FATTR4_WORD0_FILES_AVAIL) { + READ_BUF(8); + len += 8; + READ64(fsinfo->afiles); + dprintk("read_attrs: files_avail=0x%Lx\n", fsinfo->afiles); + } + if (bmval0 & FATTR4_WORD0_FILES_FREE) { + READ_BUF(8); + len += 8; + READ64(fsinfo->ffiles); + dprintk("read_attrs: files_free=0x%Lx\n", fsinfo->ffiles); + } + if (bmval0 & FATTR4_WORD0_FILES_TOTAL) { + READ_BUF(8); + len += 8; + READ64(fsinfo->tfiles); + dprintk("read_attrs: files_tot=0x%Lx\n", fsinfo->tfiles); + } + if (bmval0 & FATTR4_WORD0_MAXFILESIZE) { + READ_BUF(8); + len += 8; + READ64(fsinfo->maxfilesize); + dprintk("read_attrs: maxfilesize=0x%Lx\n", fsinfo->maxfilesize); + } + if (bmval0 & FATTR4_WORD0_MAXLINK) { + READ_BUF(4); + len += 4; + READ32(fsinfo->linkmax); + dprintk("read_attrs: maxlink=%d\n", fsinfo->linkmax); + } + if (bmval0 & FATTR4_WORD0_MAXNAME) { + READ_BUF(4); + len += 4; + READ32(fsinfo->namelen); + dprintk("read_attrs: maxname=%d\n", fsinfo->namelen); + } + if (bmval0 & FATTR4_WORD0_MAXREAD) { + READ_BUF(8); + len += 8; + READ64(fsinfo->rtmax); + fsinfo->rtpref = fsinfo->dtpref = fsinfo->rtmax; + dprintk("read_attrs: maxread=%d\n", fsinfo->rtmax); + } + if (bmval0 & FATTR4_WORD0_MAXWRITE) { + READ_BUF(8); + len += 8; + READ64(fsinfo->wtmax); + fsinfo->wtpref = fsinfo->wtmax; + dprintk("read_attrs: maxwrite=%d\n", fsinfo->wtmax); + } + + if (bmval1 & FATTR4_WORD1_MODE) { + READ_BUF(4); + len += 4; + READ32(dummy32); + nfp->mode = (dummy32 & ~S_IFMT) | fmode; + dprintk("read_attrs: mode=0%o\n", nfp->mode); + } + if (bmval1 & FATTR4_WORD1_NUMLINKS) { + READ_BUF(4); + len += 4; + READ32(nfp->nlink); + dprintk("read_attrs: nlinks=0%o\n", nfp->nlink); + } + if (bmval1 & FATTR4_WORD1_OWNER) { + READ_BUF(4); + len += 4; + READ32(dummy32); /* name length */ + if (dummy32 > XDR_MAX_NETOBJ) { + dprintk("read_attrs: name too long!\n"); + goto xdr_error; + } + READ_BUF(dummy32); + len += (XDR_QUADLEN(dummy32) << 2); + if ((status = gss_get_num(GSS_OWNER, dummy32, (char *)p, &nfp->uid))) { + dprintk("read_attrs: gss_get_num failed!\n"); + goto out; + } + dprintk("read_attrs: uid=%d\n", (int)nfp->uid); + } + if (bmval1 & FATTR4_WORD1_OWNER_GROUP) { + READ_BUF(4); + len += 4; + READ32(dummy32); + if (dummy32 > XDR_MAX_NETOBJ) { + dprintk("read_attrs: name too long!\n"); + goto xdr_error; + } + READ_BUF(dummy32); + len += (XDR_QUADLEN(dummy32) << 2); + if ((status = gss_get_num(GSS_GROUP, dummy32, (char *)p, &nfp->gid))) { + dprintk("read_attrs: gss_get_num failed!\n"); + goto out; + } + dprintk("read_attrs: gid=%d\n", (int)nfp->gid); + } + if (bmval1 & FATTR4_WORD1_RAWDEV) { + READ_BUF(8); + len += 8; + READ32(dummy32); + nfp->rdev = (dummy32 << MINORBITS); + READ32(dummy32); + nfp->rdev |= (dummy32 & MINORMASK); + dprintk("read_attrs: rdev=%d\n", nfp->rdev); + } + if (bmval1 & FATTR4_WORD1_SPACE_AVAIL) { + READ_BUF(8); + len += 8; + READ64(fsinfo->abytes); + dprintk("read_attrs: savail=0x%Lx\n", fsinfo->abytes); + } + if (bmval1 & FATTR4_WORD1_SPACE_FREE) { + READ_BUF(8); + len += 8; + READ64(fsinfo->fbytes); + dprintk("read_attrs: sfree=0x%Lx\n", fsinfo->fbytes); + } + if (bmval1 & FATTR4_WORD1_SPACE_TOTAL) { + READ_BUF(8); + len += 8; + READ64(fsinfo->tbytes); + dprintk("read_attrs: stotal=0x%Lx\n", fsinfo->tbytes); + } + if (bmval1 & FATTR4_WORD1_SPACE_USED) { + READ_BUF(8); + len += 8; + READ64(nfp->du.nfs3.used); + dprintk("read_attrs: sused=0x%Lx\n", nfp->du.nfs3.used); + } + if (bmval1 & FATTR4_WORD1_TIME_ACCESS) { + READ_BUF(12); + len += 12; + READTIME(nfp->atime); + dprintk("read_attrs: atime=%d\n", (int)nfp->atime); + } + if (bmval1 & FATTR4_WORD1_TIME_METADATA) { + READ_BUF(12); + len += 12; + READTIME(nfp->ctime); + dprintk("read_attrs: ctime=%d\n", (int)nfp->ctime); + } + if (bmval1 & FATTR4_WORD1_TIME_MODIFY) { + READ_BUF(12); + len += 12; + READTIME(nfp->mtime); + dprintk("read_attrs: mtime=%d\n", (int)nfp->mtime); + } + if (len != attrlen) + goto xdr_error; + +success: + DECODE_TAIL; +} + +static int +decode_getfh(struct nfs4_compound *cp, int nfserr, struct nfs4_getfh *getfh) +{ + struct nfs_fh *fh = getfh->gf_fhandle; + int len; + DECODE_HEAD; + + /* Zero handle first to allow comparisons */ + memset(fh, 0, sizeof(*fh)); + + if (!nfserr) { + READ_BUF(4); + READ32(len); + if (len > NFS_MAXFHSIZE) + goto xdr_error; + fh->size = len; + READ_BUF(len); + COPYMEM(fh->data, len); + } + + DECODE_TAIL; +} + +static int +decode_link(struct nfs4_compound *cp, int nfserr, struct nfs4_link *link) +{ + int status = 0; + + if (!nfserr) + status = decode_change_info(cp, link->ln_cinfo); + return status; +} + +static int +decode_open(struct nfs4_compound *cp, int nfserr, struct nfs4_open *open) +{ + u32 bmlen, delegation_type; + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(sizeof(nfs4_stateid)); + COPYMEM(open->op_stateid, sizeof(nfs4_stateid)); + + decode_change_info(cp, open->op_cinfo); + + READ_BUF(8); + READ32(*open->op_rflags); + READ32(bmlen); + if (bmlen > 10) + goto xdr_error; + + READ_BUF((bmlen << 2) + 4); + p += bmlen; + READ32(delegation_type); + if (delegation_type != NFS4_OPEN_DELEGATE_NONE) + goto xdr_error; + } + + DECODE_TAIL; +} + +static int +decode_open_confirm(struct nfs4_compound *cp, int nfserr, struct nfs4_open_confirm *open_confirm) +{ + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(sizeof(nfs4_stateid)); + COPYMEM(open_confirm->oc_stateid, sizeof(nfs4_stateid)); + } + + DECODE_TAIL; +} + +static int +decode_read(struct nfs4_compound *cp, int nfserr, struct nfs4_read *read) +{ + u32 throwaway; + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(8); + if (read->rd_eof) + READ32(*read->rd_eof); + else + READ32(throwaway); + READ32(*read->rd_bytes_read); + if (*read->rd_bytes_read > read->rd_length) + goto xdr_error; + } + + DECODE_TAIL; +} + +static int +decode_readdir(struct nfs4_compound *cp, int nfserr, struct rpc_rqst *req, struct nfs4_readdir *readdir) +{ + struct xdr_buf *rcvbuf = &req->rq_rcv_buf; + struct page *page = *rcvbuf->pages; + unsigned int pglen = rcvbuf->page_len; + u32 *end, *entry; + u32 len, attrlen, word; + int i; + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(8); + COPYMEM(readdir->rd_resp_verifier, 8); + + BUG_ON(pglen > PAGE_CACHE_SIZE); + p = (u32 *) kmap(page); + end = (u32 *) ((char *)p + pglen + readdir->rd_pgbase); + + while (*p++) { + entry = p - 1; + if (p + 3 > end) + goto short_pkt; + p += 2; /* cookie */ + len = ntohl(*p++); /* filename length */ + if (len > NFS4_MAXNAMLEN) { + printk(KERN_WARNING "NFS: giant filename in readdir (len 0x%x)\n", len); + goto err_unmap; + } + + p += XDR_QUADLEN(len); + if (p + 1 > end) + goto short_pkt; + len = ntohl(*p++); /* bitmap length */ + if (len > 10) { + printk(KERN_WARNING "NFS: giant bitmap in readdir (len 0x%x)\n", len); + goto err_unmap; + } + if (p + len + 1 > end) + goto short_pkt; + attrlen = 0; + for (i = 0; i < len; i++) { + word = ntohl(*p++); + if (!word) + continue; + else if (i == 0 && word == FATTR4_WORD0_FILEID) { + attrlen = 8; + continue; + } + printk(KERN_WARNING "NFS: unexpected bitmap word in readdir (0x%x)\n", word); + goto err_unmap; + } + if (ntohl(*p++) != attrlen) { + printk(KERN_WARNING "NFS: unexpected attrlen in readdir\n"); + goto err_unmap; + } + p += XDR_QUADLEN(attrlen); + if (p + 1 > end) + goto short_pkt; + } + kunmap(page); + } + + DECODE_TAIL; +short_pkt: + printk(KERN_NOTICE "NFS: short packet in readdir reply!\n"); + /* truncate listing */ + kunmap(page); + entry[0] = entry[1] = 0; + return 0; +err_unmap: + kunmap(page); + return -errno_NFSERR_IO; +} + +static int +decode_readlink(struct nfs4_compound *cp, int nfserr, struct rpc_rqst *req, struct nfs4_readlink *readlink) +{ + struct xdr_buf *rcvbuf = &req->rq_rcv_buf; + u32 *strlen; + u32 len; + char *string; + + if (!nfserr) { + /* + * The XDR encode routine has set things up so that + * the link text will be copied directly into the + * buffer. We just have to do overflow-checking, + * and and null-terminate the text (the VFS expects + * null-termination). + */ + strlen = (u32 *) kmap(rcvbuf->pages[0]); + len = ntohl(*strlen); + if (len > PAGE_CACHE_SIZE - 5) { + printk(KERN_WARNING "nfs: server returned giant symlink!\n"); + kunmap(rcvbuf->pages[0]); + return -EIO; + } + *strlen = len; + + string = (char *)(strlen + 1); + string[len] = '\0'; + kunmap(rcvbuf->pages[0]); + } + return 0; +} + +static int +decode_remove(struct nfs4_compound *cp, int nfserr, struct nfs4_remove *remove) +{ + int status; + + status = 0; + if (!nfserr) + status = decode_change_info(cp, remove->rm_cinfo); + return status; +} + +static int +decode_rename(struct nfs4_compound *cp, int nfserr, struct nfs4_rename *rename) +{ + int status = 0; + + if (!nfserr) { + if ((status = decode_change_info(cp, rename->rn_src_cinfo))) + goto out; + if ((status = decode_change_info(cp, rename->rn_dst_cinfo))) + goto out; + } +out: + return status; +} + +static int +decode_setattr(struct nfs4_compound *cp) +{ + u32 bmlen; + DECODE_HEAD; + + READ_BUF(4); + READ32(bmlen); + if (bmlen > 10) + goto xdr_error; + READ_BUF(bmlen << 2); + + DECODE_TAIL; +} + +static int +decode_setclientid(struct nfs4_compound *cp, int nfserr) +{ + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(8); + READ64(cp->server->nfs4_state->cl_clientid); + } + else if (nfserr == NFSERR_CLID_INUSE) { + u32 len; + + /* skip netid string */ + READ_BUF(4); + READ32(len); + READ_BUF(len); + + /* skip uaddr string */ + READ_BUF(4); + READ32(len); + READ_BUF(len); + } + + DECODE_TAIL; +} + +static int +decode_write(struct nfs4_compound *cp, int nfserr, struct nfs4_write *write) +{ + DECODE_HEAD; + + if (!nfserr) { + READ_BUF(16); + READ32(*write->wr_bytes_written); + if (*write->wr_bytes_written > write->wr_len) + goto xdr_error; + READ32(write->wr_verf->committed); + COPYMEM(write->wr_verf->verifier, 8); + } + + DECODE_TAIL; +} + +static int +decode_compound(struct nfs4_compound *cp, struct rpc_rqst *req) +{ + u32 taglen; + u32 opnum, nfserr; + DECODE_HEAD; + + READ_BUF(8); + READ32(cp->toplevel_status); + READ32(taglen); + + /* + * We need this if our zero-copy I/O is going to work. Rumor has + * it that the spec will soon mandate it... + */ + if (taglen != cp->taglen) { + dprintk("nfs4: tag length mismatch!\n"); + goto xdr_error; + } + + READ_BUF(taglen + 4); + p += XDR_QUADLEN(taglen); + READ32(cp->resp_nops); + if (cp->resp_nops > cp->req_nops) { + dprintk("nfs4: resp_nops > req_nops!\n"); + goto xdr_error; + } + + for (cp->nops = 0; cp->nops < cp->resp_nops; cp->nops++) { + READ_BUF(8); + READ32(opnum); + if (opnum != cp->ops[cp->nops].opnum) { + dprintk("nfs4: operation mismatch!\n"); + goto xdr_error; + } + READ32(nfserr); + if (cp->nops == cp->resp_nops - 1) { + if (nfserr != cp->toplevel_status) { + dprintk("nfs4: status mismatch!\n"); + goto xdr_error; + } + } + else if (nfserr) { + dprintk("nfs4: intermediate status nonzero!\n"); + goto xdr_error; + } + cp->ops[cp->nops].nfserr = nfserr; + + switch (opnum) { + case OP_ACCESS: + status = decode_access(cp, nfserr, &cp->ops[cp->nops].u.access); + break; + case OP_CLOSE: + status = decode_close(cp, nfserr, &cp->ops[cp->nops].u.close); + break; + case OP_COMMIT: + status = decode_commit(cp, nfserr, &cp->ops[cp->nops].u.commit); + break; + case OP_CREATE: + status = decode_create(cp, nfserr, &cp->ops[cp->nops].u.create); + break; + case OP_GETATTR: + status = decode_getattr(cp, nfserr, &cp->ops[cp->nops].u.getattr); + break; + case OP_GETFH: + status = decode_getfh(cp, nfserr, &cp->ops[cp->nops].u.getfh); + break; + case OP_LINK: + status = decode_link(cp, nfserr, &cp->ops[cp->nops].u.link); + break; + case OP_LOOKUP: + status = 0; + break; + case OP_OPEN: + status = decode_open(cp, nfserr, &cp->ops[cp->nops].u.open); + break; + case OP_OPEN_CONFIRM: + status = decode_open_confirm(cp, nfserr, &cp->ops[cp->nops].u.open_confirm); + break; + case OP_PUTFH: + status = 0; + break; + case OP_PUTROOTFH: + status = 0; + break; + case OP_READ: + status = decode_read(cp, nfserr, &cp->ops[cp->nops].u.read); + break; + case OP_READDIR: + status = decode_readdir(cp, nfserr, req, &cp->ops[cp->nops].u.readdir); + break; + case OP_READLINK: + status = decode_readlink(cp, nfserr, req, &cp->ops[cp->nops].u.readlink); + break; + case OP_RESTOREFH: + status = 0; + break; + case OP_REMOVE: + status = decode_remove(cp, nfserr, &cp->ops[cp->nops].u.remove); + break; + case OP_RENAME: + status = decode_rename(cp, nfserr, &cp->ops[cp->nops].u.rename); + break; + case OP_RENEW: + status = 0; + break; + case OP_SAVEFH: + status = 0; + break; + case OP_SETATTR: + status = decode_setattr(cp); + break; + case OP_SETCLIENTID: + status = decode_setclientid(cp, nfserr); + break; + case OP_SETCLIENTID_CONFIRM: + status = 0; + break; + case OP_WRITE: + status = decode_write(cp, nfserr, &cp->ops[cp->nops].u.write); + break; + default: + BUG(); + return -EIO; + } + if (status) + goto xdr_error; + } + + DECODE_TAIL; +} +/* + * END OF "GENERIC" DECODE ROUTINES. + */ + +/* + * Decode void reply + */ +static int +nfs4_xdr_dec_void(struct rpc_rqst *req, u32 *p, void *dummy) +{ + return 0; +} + +/* + * Decode COMPOUND response + */ +static int +nfs4_xdr_dec_compound(struct rpc_rqst *rqstp, u32 *p, struct nfs4_compound *cp) +{ + int status; + + cp->p = p; + cp->end = (u32 *) ((u8 *) rqstp->rq_rvec->iov_base + rqstp->rq_rvec->iov_len); + + if ((status = decode_compound(cp, rqstp))) + goto out; + + status = 0; + if (cp->toplevel_status) + status = -nfs_stat_to_errno(cp->toplevel_status); + +out: + return status; +} + +u32 * +nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus) +{ + u32 len; + + if (!*p++) { + if (!*p) + return ERR_PTR(-EAGAIN); + entry->eof = 1; + return ERR_PTR(-EBADCOOKIE); + } + + entry->prev_cookie = entry->cookie; + p = xdr_decode_hyper(p, &entry->cookie); + entry->len = ntohl(*p++); + entry->name = (const char *) p; + p += XDR_QUADLEN(entry->len); + + if (entry->cookie > COOKIE_MAX) + entry->cookie = COOKIE_MAX; + + /* + * In case the server doesn't return an inode number, + * we fake one here. (We don't use inode number 0, + * since glibc seems to choke on it...) + */ + entry->ino = 1; + + len = ntohl(*p++); /* bitmap length */ + p += len; + len = ntohl(*p++); /* attribute buffer length */ + if (len) + p = xdr_decode_hyper(p, &entry->ino); + + entry->eof = !p[0] && p[1]; + return p; +} + +#ifndef MAX +# define MAX(a, b) (((a) > (b))? (a) : (b)) +#endif + +#define PROC(proc, argtype, restype) \ + { "nfs4_" #proc, \ + (kxdrproc_t) nfs4_xdr_##argtype, \ + (kxdrproc_t) nfs4_xdr_##restype, \ + MAX(NFS4_##argtype##_sz,NFS4_##restype##_sz) << 2, \ + 0 \ + } + +static struct rpc_procinfo nfs4_procedures[] = { + PROC(null, enc_void, dec_void), + PROC(compound, enc_compound, dec_compound) +}; + +struct rpc_version nfs_version4 = { + number: 4, + nrprocs: sizeof(nfs4_procedures)/sizeof(nfs4_procedures[0]), + procs: nfs4_procedures +}; + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ ------------------------------------------------------- This sf.net email is sponsored by: Dice - The leading online job board for high-tech professionals. Search and apply for tech jobs today! http://seeker.dice.com/seeker.epl?rel_code=31 _______________________________________________ NFS maillist - NFS@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/nfs