Skip to content
Snippets Groups Projects
nfs4proc.c 87.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *  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 <kmsmith@umich.edu>
     *  Andy Adamson   <andros@umich.edu>
     *
     *  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 <linux/mm.h>
    #include <linux/utsname.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/string.h>
    #include <linux/sunrpc/clnt.h>
    #include <linux/nfs.h>
    #include <linux/nfs4.h>
    #include <linux/nfs_fs.h>
    #include <linux/nfs_page.h>
    #include <linux/smp_lock.h>
    #include <linux/namei.h>
    
    #include <linux/mount.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #include "nfs4_fs.h"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include "delegation.h"
    
    #define NFSDBG_FACILITY		NFSDBG_PROC
    
    #define NFS4_POLL_RETRY_MIN	(1*HZ)
    #define NFS4_POLL_RETRY_MAX	(15*HZ)
    
    
    static int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *fh, struct nfs4_state_owner *sp, nfs4_stateid *stateid, struct nfs_seqid *seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int nfs4_do_fsinfo(struct nfs_server *, struct nfs_fh *, struct nfs_fsinfo *);
    
    static int nfs4_async_handle_error(struct rpc_task *, const struct nfs_server *);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry);
    
    static int nfs4_handle_exception(const struct nfs_server *server, int errorcode, struct nfs4_exception *exception);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    extern u32 *nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus);
    extern struct rpc_procinfo nfs4_procedures[];
    
    /* Prevent leaks of NFSv4 errors into userland */
    int nfs4_map_errors(int err)
    {
    	if (err < -1000) {
    		dprintk("%s could not handle NFSv4 error %d\n",
    				__FUNCTION__, -err);
    		return -EIO;
    	}
    	return err;
    }
    
    /*
     * This is our standard bitmap for GETATTR requests.
     */
    const u32 nfs4_fattr_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
    };
    
    const u32 nfs4_statfs_bitmap[2] = {
    	FATTR4_WORD0_FILES_AVAIL
    	| FATTR4_WORD0_FILES_FREE
    	| FATTR4_WORD0_FILES_TOTAL,
    	FATTR4_WORD1_SPACE_AVAIL
    	| FATTR4_WORD1_SPACE_FREE
    	| FATTR4_WORD1_SPACE_TOTAL
    };
    
    
    const u32 nfs4_pathconf_bitmap[2] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	FATTR4_WORD0_MAXLINK
    	| FATTR4_WORD0_MAXNAME,
    	0
    };
    
    const u32 nfs4_fsinfo_bitmap[2] = { FATTR4_WORD0_MAXFILESIZE
    			| FATTR4_WORD0_MAXREAD
    			| FATTR4_WORD0_MAXWRITE
    			| FATTR4_WORD0_LEASE_TIME,
    			0
    };
    
    static void nfs4_setup_readdir(u64 cookie, u32 *verifier, struct dentry *dentry,
    		struct nfs4_readdir_arg *readdir)
    {
    	u32 *start, *p;
    
    	BUG_ON(readdir->count < 80);
    	if (cookie > 2) {
    
    		readdir->cookie = cookie;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		memcpy(&readdir->verifier, verifier, sizeof(readdir->verifier));
    		return;
    	}
    
    	readdir->cookie = 0;
    	memset(&readdir->verifier, 0, sizeof(readdir->verifier));
    	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_atomic(*readdir->pages, KM_USER0);
    	
    	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, dentry->d_inode->i_ino);
    	}
    	
    	*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, dentry->d_parent->d_inode->i_ino);
    
    	readdir->pgbase = (char *)p - (char *)start;
    	readdir->count -= readdir->pgbase;
    	kunmap_atomic(start, KM_USER0);
    }
    
    static void
    renew_lease(struct nfs_server *server, unsigned long timestamp)
    {
    	struct nfs4_client *clp = server->nfs4_state;
    	spin_lock(&clp->cl_lock);
    	if (time_before(clp->cl_last_renewal,timestamp))
    		clp->cl_last_renewal = timestamp;
    	spin_unlock(&clp->cl_lock);
    }
    
    static void update_changeattr(struct inode *inode, struct nfs4_change_info *cinfo)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    
    	spin_lock(&inode->i_lock);
    	nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (cinfo->before == nfsi->change_attr && cinfo->atomic)
    		nfsi->change_attr = cinfo->after;
    
    /* Helper for asynchronous RPC calls */
    
    static int nfs4_call_async(struct rpc_clnt *clnt,
    
    		const struct rpc_call_ops *tk_ops, void *calldata)
    
    {
    	struct rpc_task *task;
    
    
    	if (!(task = rpc_new_task(clnt, RPC_TASK_ASYNC, tk_ops, calldata)))
    
    		return -ENOMEM;
    	rpc_execute(task);
    	return 0;
    }
    
    
    static int nfs4_wait_for_completion_rpc_task(struct rpc_task *task)
    {
    	sigset_t oldset;
    	int ret;
    
    	rpc_clnt_sigmask(task->tk_client, &oldset);
    	ret = rpc_wait_for_completion_task(task);
    	rpc_clnt_sigunmask(task->tk_client, &oldset);
    	return ret;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, int open_flags)
    {
    	struct inode *inode = state->inode;
    
    	open_flags &= (FMODE_READ|FMODE_WRITE);
    
    	/* Protect against nfs4_find_state_byowner() */
    
    	spin_lock(&state->owner->so_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	spin_lock(&inode->i_lock);
    
    	memcpy(&state->stateid, stateid, sizeof(state->stateid));
    	if ((open_flags & FMODE_WRITE))
    		state->nwriters++;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (open_flags & FMODE_READ)
    		state->nreaders++;
    
    	nfs4_state_set_mode_locked(state, state->state | open_flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	spin_unlock(&inode->i_lock);
    
    	spin_unlock(&state->owner->so_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     * OPEN_RECLAIM:
     * 	reclaim state on the server after a reboot.
     */
    static int _nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state)
    {
    	struct inode *inode = state->inode;
    	struct nfs_server *server = NFS_SERVER(inode);
    	struct nfs_delegation *delegation = NFS_I(inode)->delegation;
    	struct nfs_openargs o_arg = {
    		.fh = NFS_FH(inode),
    		.id = sp->so_id,
    		.open_flags = state->state,
    		.clientid = server->nfs4_state->cl_clientid,
    		.claim = NFS4_OPEN_CLAIM_PREVIOUS,
    		.bitmask = server->attr_bitmask,
    	};
    	struct nfs_openres o_res = {
    		.server = server,	/* Grrr */
    	};
    	struct rpc_message msg = {
    		.rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR],
    		.rpc_argp       = &o_arg,
    		.rpc_resp	= &o_res,
    		.rpc_cred	= sp->so_cred,
    	};
    	int status;
    
    	if (delegation != NULL) {
    		if (!(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) {
    			memcpy(&state->stateid, &delegation->stateid,
    					sizeof(state->stateid));
    			set_bit(NFS_DELEGATED_STATE, &state->flags);
    			return 0;
    		}
    		o_arg.u.delegation_type = delegation->type;
    	}
    
    	o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
    	if (o_arg.seqid == NULL)
    		return -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR);
    
    	/* Confirm the sequence as being established */
    	nfs_confirm_seqid(&sp->so_seqid, status);
    	nfs_increment_open_seqid(status, o_arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status == 0) {
    		memcpy(&state->stateid, &o_res.stateid, sizeof(state->stateid));
    		if (o_res.delegation_type != 0) {
    			nfs_inode_reclaim_delegation(inode, sp->so_cred, &o_res);
    			/* Did the server issue an immediate delegation recall? */
    			if (o_res.do_recall)
    				nfs_async_inode_return_delegation(inode, &o_res.stateid);
    		}
    	}
    
    	nfs_free_seqid(o_arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	clear_bit(NFS_DELEGATED_STATE, &state->flags);
    	/* Ensure we update the inode attributes */
    	NFS_CACHEINV(inode);
    	return status;
    }
    
    static int nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state)
    {
    	struct nfs_server *server = NFS_SERVER(state->inode);
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = _nfs4_open_reclaim(sp, state);
    
    		if (err != -NFS4ERR_DELAY)
    			break;
    		nfs4_handle_exception(server, err, &exception);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state)
    {
    	struct nfs4_state_owner  *sp  = state->owner;
    	struct inode *inode = dentry->d_inode;
    	struct nfs_server *server = NFS_SERVER(inode);
    	struct dentry *parent = dget_parent(dentry);
    	struct nfs_openargs arg = {
    		.fh = NFS_FH(parent->d_inode),
    		.clientid = server->nfs4_state->cl_clientid,
    		.name = &dentry->d_name,
    		.id = sp->so_id,
    		.server = server,
    		.bitmask = server->attr_bitmask,
    		.claim = NFS4_OPEN_CLAIM_DELEGATE_CUR,
    	};
    	struct nfs_openres res = {
    		.server = server,
    	};
    	struct 	rpc_message msg = {
    		.rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR],
    		.rpc_argp       = &arg,
    		.rpc_resp       = &res,
    		.rpc_cred	= sp->so_cred,
    	};
    	int status = 0;
    
    	if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
    		goto out;
    	if (state->state == 0)
    		goto out;
    
    	arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
    	status = -ENOMEM;
    	if (arg.seqid == NULL)
    		goto out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	arg.open_flags = state->state;
    	memcpy(arg.u.delegation.data, state->stateid.data, sizeof(arg.u.delegation.data));
    	status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR);
    
    	nfs_increment_open_seqid(status, arg.seqid);
    
    	if (status != 0)
    		goto out_free;
    	if(res.rflags & NFS4_OPEN_RESULT_CONFIRM) {
    		status = _nfs4_proc_open_confirm(server->client, NFS_FH(inode),
    				sp, &res.stateid, arg.seqid);
    		if (status != 0)
    			goto out_free;
    	}
    	nfs_confirm_seqid(&sp->so_seqid, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status >= 0) {
    		memcpy(state->stateid.data, res.stateid.data,
    				sizeof(state->stateid.data));
    		clear_bit(NFS_DELEGATED_STATE, &state->flags);
    	}
    
    	nfs_free_seqid(arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out:
    	dput(parent);
    	return status;
    }
    
    int nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state)
    {
    	struct nfs4_exception exception = { };
    	struct nfs_server *server = NFS_SERVER(dentry->d_inode);
    	int err;
    	do {
    		err = _nfs4_open_delegation_recall(dentry, state);
    		switch (err) {
    			case 0:
    				return err;
    			case -NFS4ERR_STALE_CLIENTID:
    			case -NFS4ERR_STALE_STATEID:
    			case -NFS4ERR_EXPIRED:
    				/* Don't recall a delegation if it was lost */
    				nfs4_schedule_state_recovery(server->nfs4_state);
    				return err;
    		}
    		err = nfs4_handle_exception(server, err, &exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    static int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *fh, struct nfs4_state_owner *sp, nfs4_stateid *stateid, struct nfs_seqid *seqid)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_open_confirmargs arg = {
    		.fh             = fh,
    
    		.seqid          = seqid,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.stateid	= *stateid,
    	};
    	struct nfs_open_confirmres res;
    	struct 	rpc_message msg = {
    		.rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_OPEN_CONFIRM],
    		.rpc_argp       = &arg,
    		.rpc_resp       = &res,
    		.rpc_cred	= sp->so_cred,
    	};
    	int status;
    
    	status = rpc_call_sync(clnt, &msg, RPC_TASK_NOINTR);
    
    	/* Confirm the sequence as being established */
    	nfs_confirm_seqid(&sp->so_seqid, status);
    	nfs_increment_open_seqid(status, seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status >= 0)
    		memcpy(stateid, &res.stateid, sizeof(*stateid));
    	return status;
    }
    
    static int _nfs4_proc_open(struct inode *dir, struct nfs4_state_owner  *sp, struct nfs_openargs *o_arg, struct nfs_openres *o_res)
    {
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN],
    		.rpc_argp = o_arg,
    		.rpc_resp = o_res,
    		.rpc_cred = sp->so_cred,
    	};
    	int status;
    
    	/* Update sequence id. The caller must serialize! */
    	o_arg->id = sp->so_id;
    	o_arg->clientid = sp->so_client->cl_clientid;
    
    	status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR);
    
    	if (status == 0) {
    		/* OPEN on anything except a regular file is disallowed in NFSv4 */
    		switch (o_res->f_attr->mode & S_IFMT) {
    			case S_IFREG:
    				break;
    			case S_IFLNK:
    				status = -ELOOP;
    				break;
    			case S_IFDIR:
    				status = -EISDIR;
    				break;
    			default:
    				status = -ENOTDIR;
    		}
    	}
    
    
    	nfs_increment_open_seqid(status, o_arg->seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status != 0)
    		goto out;
    
    	if (o_arg->open_flags & O_CREAT) {
    		update_changeattr(dir, &o_res->cinfo);
    		nfs_post_op_update_inode(dir, o_res->dir_attr);
    	} else
    		nfs_refresh_inode(dir, o_res->dir_attr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if(o_res->rflags & NFS4_OPEN_RESULT_CONFIRM) {
    		status = _nfs4_proc_open_confirm(server->client, &o_res->fh,
    
    				sp, &o_res->stateid, o_arg->seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (status != 0)
    			goto out;
    	}
    
    	nfs_confirm_seqid(&sp->so_seqid, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!(o_res->f_attr->valid & NFS_ATTR_FATTR))
    		status = server->rpc_ops->getattr(server, &o_res->fh, o_res->f_attr);
    out:
    	return status;
    }
    
    static int _nfs4_do_access(struct inode *inode, struct rpc_cred *cred, int openflags)
    {
    	struct nfs_access_entry cache;
    	int mask = 0;
    	int status;
    
    	if (openflags & FMODE_READ)
    		mask |= MAY_READ;
    	if (openflags & FMODE_WRITE)
    		mask |= MAY_WRITE;
    	status = nfs_access_get_cached(inode, cred, &cache);
    	if (status == 0)
    		goto out;
    
    	/* Be clever: ask server to check for all possible rights */
    	cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;
    	cache.cred = cred;
    	cache.jiffies = jiffies;
    	status = _nfs4_proc_access(inode, &cache);
    	if (status != 0)
    		return status;
    	nfs_access_add_cache(inode, &cache);
    out:
    	if ((cache.mask & mask) == mask)
    		return 0;
    	return -EACCES;
    }
    
    /*
     * OPEN_EXPIRED:
     * 	reclaim state on the server after a network partition.
     * 	Assumes caller holds the appropriate lock
     */
    static int _nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry)
    {
    	struct dentry *parent = dget_parent(dentry);
    	struct inode *dir = parent->d_inode;
    	struct inode *inode = state->inode;
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs_delegation *delegation = NFS_I(inode)->delegation;
    
    	struct nfs_fattr f_attr, dir_attr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs_openargs o_arg = {
    		.fh = NFS_FH(dir),
    		.open_flags = state->state,
    		.name = &dentry->d_name,
    		.bitmask = server->attr_bitmask,
    		.claim = NFS4_OPEN_CLAIM_NULL,
    	};
    	struct nfs_openres o_res = {
    		.f_attr = &f_attr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.server = server,
    	};
    	int status = 0;
    
    	if (delegation != NULL && !(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) {
    		status = _nfs4_do_access(inode, sp->so_cred, state->state);
    		if (status < 0)
    			goto out;
    		memcpy(&state->stateid, &delegation->stateid, sizeof(state->stateid));
    		set_bit(NFS_DELEGATED_STATE, &state->flags);
    		goto out;
    	}
    
    	o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
    	status = -ENOMEM;
    	if (o_arg.seqid == NULL)
    		goto out;
    
    	nfs_fattr_init(&f_attr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	status = _nfs4_proc_open(dir, sp, &o_arg, &o_res);
    	if (status != 0)
    		goto out_nodeleg;
    	/* Check if files differ */
    	if ((f_attr.mode & S_IFMT) != (inode->i_mode & S_IFMT))
    		goto out_stale;
    	/* Has the file handle changed? */
    	if (nfs_compare_fh(&o_res.fh, NFS_FH(inode)) != 0) {
    		/* Verify if the change attributes are the same */
    		if (f_attr.change_attr != NFS_I(inode)->change_attr)
    			goto out_stale;
    		if (nfs_size_to_loff_t(f_attr.size) != inode->i_size)
    			goto out_stale;
    		/* Lets just pretend that this is the same file */
    		nfs_copy_fh(NFS_FH(inode), &o_res.fh);
    		NFS_I(inode)->fileid = f_attr.fileid;
    	}
    	memcpy(&state->stateid, &o_res.stateid, sizeof(state->stateid));
    	if (o_res.delegation_type != 0) {
    		if (!(delegation->flags & NFS_DELEGATION_NEED_RECLAIM))
    			nfs_inode_set_delegation(inode, sp->so_cred, &o_res);
    		else
    			nfs_inode_reclaim_delegation(inode, sp->so_cred, &o_res);
    	}
    out_nodeleg:
    
    	nfs_free_seqid(o_arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	clear_bit(NFS_DELEGATED_STATE, &state->flags);
    out:
    	dput(parent);
    	return status;
    out_stale:
    	status = -ESTALE;
    	/* Invalidate the state owner so we don't ever use it again */
    	nfs4_drop_state_owner(sp);
    	d_drop(dentry);
    	/* Should we be trying to close that stateid? */
    	goto out_nodeleg;
    }
    
    
    static inline int nfs4_do_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry)
    {
    	struct nfs_server *server = NFS_SERVER(dentry->d_inode);
    	struct nfs4_exception exception = { };
    	int err;
    
    	do {
    		err = _nfs4_open_expired(sp, state, dentry);
    		if (err == -NFS4ERR_DELAY)
    			nfs4_handle_exception(server, err, &exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state)
    {
    	struct nfs_inode *nfsi = NFS_I(state->inode);
    	struct nfs_open_context *ctx;
    	int status;
    
    	spin_lock(&state->inode->i_lock);
    	list_for_each_entry(ctx, &nfsi->open_files, list) {
    		if (ctx->state != state)
    			continue;
    		get_nfs_open_context(ctx);
    		spin_unlock(&state->inode->i_lock);
    
    		status = nfs4_do_open_expired(sp, state, ctx->dentry);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		put_nfs_open_context(ctx);
    		return status;
    	}
    	spin_unlock(&state->inode->i_lock);
    	return -ENOENT;
    }
    
    /*
     * Returns an nfs4_state + an extra reference to the inode
     */
    static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred *cred, struct nfs4_state **res)
    {
    	struct nfs_delegation *delegation;
    	struct nfs_server *server = NFS_SERVER(inode);
    	struct nfs4_client *clp = server->nfs4_state;
    	struct nfs_inode *nfsi = NFS_I(inode);
    	struct nfs4_state_owner *sp = NULL;
    	struct nfs4_state *state = NULL;
    	int open_flags = flags & (FMODE_READ|FMODE_WRITE);
    	int err;
    
    	/* Protect against reboot recovery - NOTE ORDER! */
    	down_read(&clp->cl_sem);
    	/* Protect against delegation recall */
    	down_read(&nfsi->rwsem);
    	delegation = NFS_I(inode)->delegation;
    	err = -ENOENT;
    	if (delegation == NULL || (delegation->type & open_flags) != open_flags)
    		goto out_err;
    	err = -ENOMEM;
    	if (!(sp = nfs4_get_state_owner(server, cred))) {
    		dprintk("%s: nfs4_get_state_owner failed!\n", __FUNCTION__);
    		goto out_err;
    	}
    	state = nfs4_get_open_state(inode, sp);
    	if (state == NULL)
    		goto out_err;
    
    	err = -ENOENT;
    	if ((state->state & open_flags) == open_flags) {
    		spin_lock(&inode->i_lock);
    		if (open_flags & FMODE_READ)
    			state->nreaders++;
    		if (open_flags & FMODE_WRITE)
    			state->nwriters++;
    		spin_unlock(&inode->i_lock);
    		goto out_ok;
    	} else if (state->state != 0)
    		goto out_err;
    
    	lock_kernel();
    	err = _nfs4_do_access(inode, cred, open_flags);
    	unlock_kernel();
    	if (err != 0)
    		goto out_err;
    	set_bit(NFS_DELEGATED_STATE, &state->flags);
    	update_open_stateid(state, &delegation->stateid, open_flags);
    out_ok:
    	nfs4_put_state_owner(sp);
    	up_read(&nfsi->rwsem);
    	up_read(&clp->cl_sem);
    	igrab(inode);
    	*res = state;
    	return 0; 
    out_err:
    	if (sp != NULL) {
    		if (state != NULL)
    			nfs4_put_open_state(state);
    		nfs4_put_state_owner(sp);
    	}
    	up_read(&nfsi->rwsem);
    	up_read(&clp->cl_sem);
    
    	if (err != -EACCES)
    		nfs_inode_return_delegation(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    static struct nfs4_state *nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred *cred)
    {
    	struct nfs4_exception exception = { };
    	struct nfs4_state *res;
    	int err;
    
    	do {
    		err = _nfs4_open_delegated(inode, flags, cred, &res);
    		if (err == 0)
    			break;
    		res = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
    					err, &exception));
    	} while (exception.retry);
    	return res;
    }
    
    /*
     * Returns an nfs4_state + an referenced inode
     */
    static int _nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, struct iattr *sattr, struct rpc_cred *cred, struct nfs4_state **res)
    {
    	struct nfs4_state_owner  *sp;
    	struct nfs4_state     *state = NULL;
    	struct nfs_server       *server = NFS_SERVER(dir);
    	struct nfs4_client *clp = server->nfs4_state;
    	struct inode *inode = NULL;
    	int                     status;
    
    	struct nfs_fattr f_attr, dir_attr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs_openargs o_arg = {
    		.fh             = NFS_FH(dir),
    		.open_flags	= flags,
    		.name           = &dentry->d_name,
    		.server         = server,
    		.bitmask = server->attr_bitmask,
    		.claim = NFS4_OPEN_CLAIM_NULL,
    	};
    	struct nfs_openres o_res = {
    		.f_attr         = &f_attr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.server         = server,
    	};
    
    	/* Protect against reboot recovery conflicts */
    	down_read(&clp->cl_sem);
    	status = -ENOMEM;
    	if (!(sp = nfs4_get_state_owner(server, cred))) {
    		dprintk("nfs4_do_open: nfs4_get_state_owner failed!\n");
    		goto out_err;
    	}
    	if (flags & O_EXCL) {
    		u32 *p = (u32 *) o_arg.u.verifier.data;
    		p[0] = jiffies;
    		p[1] = current->pid;
    	} else
    		o_arg.u.attrs = sattr;
    	/* Serialization for the sequence id */
    
    
    	o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
    	if (o_arg.seqid == NULL)
    		return -ENOMEM;
    
    	nfs_fattr_init(&f_attr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	status = _nfs4_proc_open(dir, sp, &o_arg, &o_res);
    	if (status != 0)
    		goto out_err;
    
    	status = -ENOMEM;
    	inode = nfs_fhget(dir->i_sb, &o_res.fh, &f_attr);
    	if (!inode)
    		goto out_err;
    	state = nfs4_get_open_state(inode, sp);
    	if (!state)
    		goto out_err;
    	update_open_stateid(state, &o_res.stateid, flags);
    	if (o_res.delegation_type != 0)
    		nfs_inode_set_delegation(inode, cred, &o_res);
    
    	nfs_free_seqid(o_arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	nfs4_put_state_owner(sp);
    	up_read(&clp->cl_sem);
    	*res = state;
    	return 0;
    out_err:
    	if (sp != NULL) {
    		if (state != NULL)
    			nfs4_put_open_state(state);
    
    		nfs_free_seqid(o_arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		nfs4_put_state_owner(sp);
    	}
    	/* Note: clp->cl_sem must be released before nfs4_put_open_state()! */
    	up_read(&clp->cl_sem);
    	if (inode != NULL)
    		iput(inode);
    	*res = NULL;
    	return status;
    }
    
    
    static struct nfs4_state *nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, struct iattr *sattr, struct rpc_cred *cred)
    {
    	struct nfs4_exception exception = { };
    	struct nfs4_state *res;
    	int status;
    
    	do {
    		status = _nfs4_do_open(dir, dentry, flags, sattr, cred, &res);
    		if (status == 0)
    			break;
    		/* NOTE: BAD_SEQID means the server and client disagree about the
    		 * book-keeping w.r.t. state-changing operations
    		 * (OPEN/CLOSE/LOCK/LOCKU...)
    		 * It is actually a sign of a bug on the client or on the server.
    		 *
    		 * If we receive a BAD_SEQID error in the particular case of
    
    		 * doing an OPEN, we assume that nfs_increment_open_seqid() will
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 * have unhashed the old state_owner for us, and that we can
    		 * therefore safely retry using a new one. We should still warn
    		 * the user though...
    		 */
    		if (status == -NFS4ERR_BAD_SEQID) {
    			printk(KERN_WARNING "NFS: v4 server returned a bad sequence-id error!\n");
    			exception.retry = 1;
    			continue;
    		}
    
    		/*
    		 * BAD_STATEID on OPEN means that the server cancelled our
    		 * state before it received the OPEN_CONFIRM.
    		 * Recover by retrying the request as per the discussion
    		 * on Page 181 of RFC3530.
    		 */
    		if (status == -NFS4ERR_BAD_STATEID) {
    			exception.retry = 1;
    			continue;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		res = ERR_PTR(nfs4_handle_exception(NFS_SERVER(dir),
    					status, &exception));
    	} while (exception.retry);
    	return res;
    }
    
    static int _nfs4_do_setattr(struct nfs_server *server, struct nfs_fattr *fattr,
                    struct nfs_fh *fhandle, struct iattr *sattr,
                    struct nfs4_state *state)
    {
            struct nfs_setattrargs  arg = {
                    .fh             = fhandle,
                    .iap            = sattr,
    		.server		= server,
    		.bitmask = server->attr_bitmask,
            };
            struct nfs_setattrres  res = {
    		.fattr		= fattr,
    		.server		= server,
            };
            struct rpc_message msg = {
                    .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
                    .rpc_argp       = &arg,
                    .rpc_resp       = &res,
            };
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	nfs_fattr_init(fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		msg.rpc_cred = state->owner->so_cred;
    
    		nfs4_copy_stateid(&arg.stateid, state, current->files);
    	} else
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		memcpy(&arg.stateid, &zero_stateid, sizeof(arg.stateid));
    
    
    	status = rpc_call_sync(server->client, &msg, 0);
    	return status;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int nfs4_do_setattr(struct nfs_server *server, struct nfs_fattr *fattr,
                    struct nfs_fh *fhandle, struct iattr *sattr,
                    struct nfs4_state *state)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_do_setattr(server, fattr, fhandle, sattr,
    					state),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    struct nfs4_closedata {
    	struct inode *inode;
    	struct nfs4_state *state;
    	struct nfs_closeargs arg;
    	struct nfs_closeres res;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    
    static void nfs4_free_closedata(void *data)
    
    	struct nfs4_closedata *calldata = data;
    	struct nfs4_state_owner *sp = calldata->state->owner;
    
    
    	nfs4_put_open_state(calldata->state);
    	nfs_free_seqid(calldata->arg.seqid);
    	nfs4_put_state_owner(sp);
    	kfree(calldata);
    }
    
    
    static void nfs4_close_done(struct rpc_task *task, void *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_closedata *calldata = data;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_state *state = calldata->state;
    	struct nfs_server *server = NFS_SERVER(calldata->inode);
    
    
    	if (RPC_ASSASSINATED(task))
    		return;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
            /* hmm. we are done with the inode, and in the process of freeing
    	 * the state_owner. we keep this around to process errors
    	 */
    
    	nfs_increment_open_seqid(task->tk_status, calldata->arg.seqid);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	switch (task->tk_status) {
    		case 0:
    			memcpy(&state->stateid, &calldata->res.stateid,
    					sizeof(state->stateid));
    			break;
    		case -NFS4ERR_STALE_STATEID:
    		case -NFS4ERR_EXPIRED:
    			nfs4_schedule_state_recovery(server->nfs4_state);
    			break;
    		default:
    			if (nfs4_async_handle_error(task, server) == -EAGAIN) {
    				rpc_restart_call(task);
    				return;
    			}
    	}
    
    	nfs_refresh_inode(calldata->inode, calldata->res.fattr);
    
    static void nfs4_close_prepare(struct rpc_task *task, void *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_closedata *calldata = data;
    
    	struct nfs4_state *state = calldata->state;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
    		.rpc_argp = &calldata->arg,
    		.rpc_resp = &calldata->res,
    
    		.rpc_cred = state->owner->so_cred,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	int mode = 0, old_mode;
    
    	if (nfs_wait_on_sequence(calldata->arg.seqid, task) != 0)
    
    		return;
    	/* Recalculate the new open mode in case someone reopened the file
    	 * while we were waiting in line to be scheduled.
    	 */
    
    	spin_lock(&state->owner->so_lock);
    	spin_lock(&calldata->inode->i_lock);
    	mode = old_mode = state->state;
    	if (state->nreaders == 0)
    		mode &= ~FMODE_READ;
    	if (state->nwriters == 0)
    		mode &= ~FMODE_WRITE;
    	nfs4_state_set_mode_locked(state, mode);
    	spin_unlock(&calldata->inode->i_lock);
    	spin_unlock(&state->owner->so_lock);
    	if (mode == old_mode || test_bit(NFS_DELEGATED_STATE, &state->flags)) {
    
    		/* Note: exit _without_ calling nfs4_close_done */
    		task->tk_action = NULL;
    
    	nfs_fattr_init(calldata->res.fattr);
    
    	if (mode != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
    
    	calldata->arg.open_flags = mode;
    	rpc_call_setup(task, &msg, 0);
    
    static const struct rpc_call_ops nfs4_close_ops = {
    
    	.rpc_call_prepare = nfs4_close_prepare,
    
    	.rpc_call_done = nfs4_close_done,
    	.rpc_release = nfs4_free_closedata,
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* 
     * It is possible for data to be read/written from a mem-mapped file 
     * after the sys_close call (which hits the vfs layer as a flush).
     * This means that we can't safely call nfsv4 close on a file until 
     * the inode is cleared. This in turn means that we are not good
     * NFSv4 citizens - we do not indicate to the server to update the file's 
     * share state even when we are done with one of the three share 
     * stateid's in the inode.
     *
     * NOTE: Caller must be holding the sp->so_owner semaphore!
     */
    
    int nfs4_do_close(struct inode *inode, struct nfs4_state *state) 
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_closedata *calldata;
    
    	int status = -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	calldata = kmalloc(sizeof(*calldata), GFP_KERNEL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (calldata == NULL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	calldata->inode = inode;
    	calldata->state = state;
    	calldata->arg.fh = NFS_FH(inode);
    
    	calldata->arg.stateid = &state->stateid;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Serialization for the sequence id */
    
    	calldata->arg.seqid = nfs_alloc_seqid(&state->owner->so_seqid);
    
    	if (calldata->arg.seqid == NULL)
    		goto out_free_calldata;
    
    	calldata->arg.bitmask = server->attr_bitmask;
    	calldata->res.fattr = &calldata->fattr;
    	calldata->res.server = server;
    
    	status = nfs4_call_async(server->client, &nfs4_close_ops, calldata);
    
    	if (status == 0)
    		goto out;
    
    	nfs_free_seqid(calldata->arg.seqid);