Skip to content
Snippets Groups Projects
nfs4proc.c 99.4 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"
    
    #include "iostat.h"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #define NFSDBG_FACILITY		NFSDBG_PROC
    
    #define NFS4_POLL_RETRY_MIN	(1*HZ)
    #define NFS4_POLL_RETRY_MAX	(15*HZ)
    
    
    struct nfs4_opendata;
    
    static int _nfs4_proc_open(struct nfs4_opendata *data);
    
    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);
    
    static int nfs4_wait_clnt_recover(struct rpc_clnt *clnt, struct nfs_client *clp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* 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
    };
    
    
    const u32 nfs4_fs_locations_bitmap[2] = {
    	FATTR4_WORD0_TYPE
    	| FATTR4_WORD0_CHANGE
    	| FATTR4_WORD0_SIZE
    	| FATTR4_WORD0_FSID
    	| FATTR4_WORD0_FILEID
    	| FATTR4_WORD0_FS_LOCATIONS,
    	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
    	| FATTR4_WORD1_MOUNTED_ON_FILEID
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    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(const struct nfs_server *server, unsigned long timestamp)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_client *clp = server->nfs4_state;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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 *dir, struct nfs4_change_info *cinfo)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_inode *nfsi = NFS_I(dir);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	spin_lock(&dir->i_lock);
    	nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE|NFS_INO_INVALID_DATA;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (cinfo->before == nfsi->change_attr && cinfo->atomic)
    		nfsi->change_attr = cinfo->after;
    
    	spin_unlock(&dir->i_lock);
    
    	struct nfs_openargs o_arg;
    	struct nfs_openres o_res;
    
    	struct nfs_open_confirmargs c_arg;
    	struct nfs_open_confirmres c_res;
    
    	struct nfs_fattr f_attr;
    	struct nfs_fattr dir_attr;
    	struct dentry *dentry;
    	struct dentry *dir;
    	struct nfs4_state_owner *owner;
    	struct iattr attrs;
    
    	int rpc_status;
    	int cancelled;
    
    };
    
    static struct nfs4_opendata *nfs4_opendata_alloc(struct dentry *dentry,
    		struct nfs4_state_owner *sp, int flags,
    		const struct iattr *attrs)
    {
    	struct dentry *parent = dget_parent(dentry);
    	struct inode *dir = parent->d_inode;
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs4_opendata *p;
    
    	p = kzalloc(sizeof(*p), GFP_KERNEL);
    	if (p == NULL)
    		goto err;
    	p->o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
    	if (p->o_arg.seqid == NULL)
    		goto err_free;
    
    	atomic_set(&p->count, 1);
    
    	p->dentry = dget(dentry);
    	p->dir = parent;
    	p->owner = sp;
    	atomic_inc(&sp->so_count);
    	p->o_arg.fh = NFS_FH(dir);
    	p->o_arg.open_flags = flags,
    	p->o_arg.clientid = server->nfs4_state->cl_clientid;
    	p->o_arg.id = sp->so_id;
    	p->o_arg.name = &dentry->d_name;
    	p->o_arg.server = server;
    	p->o_arg.bitmask = server->attr_bitmask;
    	p->o_arg.claim = NFS4_OPEN_CLAIM_NULL;
    	p->o_res.f_attr = &p->f_attr;
    	p->o_res.dir_attr = &p->dir_attr;
    	p->o_res.server = server;
    	nfs_fattr_init(&p->f_attr);
    	nfs_fattr_init(&p->dir_attr);
    	if (flags & O_EXCL) {
    		u32 *s = (u32 *) p->o_arg.u.verifier.data;
    		s[0] = jiffies;
    		s[1] = current->pid;
    	} else if (flags & O_CREAT) {
    		p->o_arg.u.attrs = &p->attrs;
    		memcpy(&p->attrs, attrs, sizeof(p->attrs));
    	}
    
    	p->c_arg.fh = &p->o_res.fh;
    	p->c_arg.stateid = &p->o_res.stateid;
    	p->c_arg.seqid = p->o_arg.seqid;
    
    	return p;
    err_free:
    	kfree(p);
    err:
    	dput(parent);
    	return NULL;
    }
    
    static void nfs4_opendata_free(struct nfs4_opendata *p)
    {
    
    	if (p != NULL && atomic_dec_and_test(&p->count)) {
    
    		nfs_free_seqid(p->o_arg.seqid);
    		nfs4_put_state_owner(p->owner);
    		dput(p->dir);
    		dput(p->dentry);
    		kfree(p);
    	}
    }
    
    
    /* 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;
    }
    
    
    static inline void update_open_stateflags(struct nfs4_state *state, mode_t open_flags)
    {
    	switch (open_flags) {
    		case FMODE_WRITE:
    			state->n_wronly++;
    			break;
    		case FMODE_READ:
    			state->n_rdonly++;
    			break;
    		case FMODE_READ|FMODE_WRITE:
    			state->n_rdwr++;
    	}
    }
    
    
    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));
    
    	update_open_stateflags(state, open_flags);
    
    	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);
    
    static struct nfs4_state *nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data)
    {
    	struct inode *inode;
    	struct nfs4_state *state = NULL;
    
    	if (!(data->f_attr.valid & NFS_ATTR_FATTR))
    		goto out;
    	inode = nfs_fhget(data->dir->d_sb, &data->o_res.fh, &data->f_attr);
    
    		goto out;
    	state = nfs4_get_open_state(inode, data->owner);
    	if (state == NULL)
    		goto put_inode;
    	update_open_stateid(state, &data->o_res.stateid, data->o_arg.open_flags);
    put_inode:
    	iput(inode);
    out:
    	return state;
    }
    
    
    static struct nfs_open_context *nfs4_state_find_open_context(struct nfs4_state *state)
    {
    	struct nfs_inode *nfsi = NFS_I(state->inode);
    	struct nfs_open_context *ctx;
    
    	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);
    		return ctx;
    	}
    	spin_unlock(&state->inode->i_lock);
    	return ERR_PTR(-ENOENT);
    }
    
    static int nfs4_open_recover_helper(struct nfs4_opendata *opendata, mode_t openflags, nfs4_stateid *stateid)
    {
    	int ret;
    
    	opendata->o_arg.open_flags = openflags;
    	ret = _nfs4_proc_open(opendata);
    	if (ret != 0)
    		return ret; 
    	memcpy(stateid->data, opendata->o_res.stateid.data,
    			sizeof(stateid->data));
    	return 0;
    }
    
    static int nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state)
    {
    	nfs4_stateid stateid;
    	struct nfs4_state *newstate;
    	int mode = 0;
    	int delegation = 0;
    	int ret;
    
    	/* memory barrier prior to reading state->n_* */
    	smp_rmb();
    	if (state->n_rdwr != 0) {
    		ret = nfs4_open_recover_helper(opendata, FMODE_READ|FMODE_WRITE, &stateid);
    		if (ret != 0)
    			return ret;
    		mode |= FMODE_READ|FMODE_WRITE;
    		if (opendata->o_res.delegation_type != 0)
    			delegation = opendata->o_res.delegation_type;
    		smp_rmb();
    	}
    	if (state->n_wronly != 0) {
    		ret = nfs4_open_recover_helper(opendata, FMODE_WRITE, &stateid);
    		if (ret != 0)
    			return ret;
    		mode |= FMODE_WRITE;
    		if (opendata->o_res.delegation_type != 0)
    			delegation = opendata->o_res.delegation_type;
    		smp_rmb();
    	}
    	if (state->n_rdonly != 0) {
    		ret = nfs4_open_recover_helper(opendata, FMODE_READ, &stateid);
    		if (ret != 0)
    			return ret;
    		mode |= FMODE_READ;
    	}
    	clear_bit(NFS_DELEGATED_STATE, &state->flags);
    	if (mode == 0)
    		return 0;
    	if (opendata->o_res.delegation_type == 0)
    		opendata->o_res.delegation_type = delegation;
    	opendata->o_arg.open_flags |= mode;
    	newstate = nfs4_opendata_to_nfs4_state(opendata);
    	if (newstate != NULL) {
    		if (opendata->o_res.delegation_type != 0) {
    			struct nfs_inode *nfsi = NFS_I(newstate->inode);
    			int delegation_flags = 0;
    			if (nfsi->delegation)
    				delegation_flags = nfsi->delegation->flags;
    			if (!(delegation_flags & NFS_DELEGATION_NEED_RECLAIM))
    				nfs_inode_set_delegation(newstate->inode,
    						opendata->owner->so_cred,
    						&opendata->o_res);
    			else
    				nfs_inode_reclaim_delegation(newstate->inode,
    						opendata->owner->so_cred,
    						&opendata->o_res);
    		}
    		nfs4_close_state(newstate, opendata->o_arg.open_flags);
    	}
    	if (newstate != state)
    		return -ESTALE;
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * OPEN_RECLAIM:
     * 	reclaim state on the server after a reboot.
     */
    
    static int _nfs4_do_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_delegation *delegation = NFS_I(state->inode)->delegation;
    	struct nfs4_opendata *opendata;
    	int delegation_type = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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;
    		}
    
    		delegation_type = delegation->type;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	opendata = nfs4_opendata_alloc(dentry, sp, 0, NULL);
    	if (opendata == NULL)
    
    		return -ENOMEM;
    
    	opendata->o_arg.claim = NFS4_OPEN_CLAIM_PREVIOUS;
    	opendata->o_arg.fh = NFS_FH(state->inode);
    	nfs_copy_fh(&opendata->o_res.fh, opendata->o_arg.fh);
    	opendata->o_arg.u.delegation_type = delegation_type;
    	status = nfs4_open_recover(opendata, state);
    	nfs4_opendata_free(opendata);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static int nfs4_do_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_server *server = NFS_SERVER(state->inode);
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    
    		err = _nfs4_do_open_reclaim(sp, state, dentry);
    
    		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_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state)
    {
    	struct nfs_open_context *ctx;
    	int ret;
    
    	ctx = nfs4_state_find_open_context(state);
    	if (IS_ERR(ctx))
    		return PTR_ERR(ctx);
    	ret = nfs4_do_open_reclaim(sp, state, ctx->dentry);
    	put_nfs_open_context(ctx);
    	return ret;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int _nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state)
    {
    	struct nfs4_state_owner  *sp  = state->owner;
    
    	struct nfs4_opendata *opendata;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
    
    		return 0;
    	opendata = nfs4_opendata_alloc(dentry, sp, 0, NULL);
    
    	opendata->o_arg.claim = NFS4_OPEN_CLAIM_DELEGATE_CUR;
    	memcpy(opendata->o_arg.u.delegation.data, state->stateid.data,
    			sizeof(opendata->o_arg.u.delegation.data));
    
    	ret = nfs4_open_recover(opendata, state);
    
    	nfs4_opendata_free(opendata);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    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 void nfs4_open_confirm_prepare(struct rpc_task *task, void *calldata)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_opendata *data = calldata;
    	struct  rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_CONFIRM],
    		.rpc_argp = &data->c_arg,
    		.rpc_resp = &data->c_res,
    		.rpc_cred = data->owner->so_cred,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	rpc_call_setup(task, &msg, 0);
    }
    
    static void nfs4_open_confirm_done(struct rpc_task *task, void *calldata)
    {
    	struct nfs4_opendata *data = calldata;
    
    	data->rpc_status = task->tk_status;
    	if (RPC_ASSASSINATED(task))
    		return;
    
    		memcpy(data->o_res.stateid.data, data->c_res.stateid.data,
    				sizeof(data->o_res.stateid.data));
    
    		renew_lease(data->o_res.server, data->timestamp);
    	}
    
    	nfs_increment_open_seqid(data->rpc_status, data->c_arg.seqid);
    	nfs_confirm_seqid(&data->owner->so_seqid, data->rpc_status);
    }
    
    static void nfs4_open_confirm_release(void *calldata)
    {
    	struct nfs4_opendata *data = calldata;
    	struct nfs4_state *state = NULL;
    
    	/* If this request hasn't been cancelled, do nothing */
    	if (data->cancelled == 0)
    		goto out_free;
    	/* In case of error, no cleanup! */
    	if (data->rpc_status != 0)
    		goto out_free;
    	nfs_confirm_seqid(&data->owner->so_seqid, 0);
    	state = nfs4_opendata_to_nfs4_state(data);
    	if (state != NULL)
    		nfs4_close_state(state, data->o_arg.open_flags);
    out_free:
    	nfs4_opendata_free(data);
    }
    
    static const struct rpc_call_ops nfs4_open_confirm_ops = {
    	.rpc_call_prepare = nfs4_open_confirm_prepare,
    	.rpc_call_done = nfs4_open_confirm_done,
    	.rpc_release = nfs4_open_confirm_release,
    };
    
    /*
     * Note: On error, nfs4_proc_open_confirm will free the struct nfs4_opendata
     */
    static int _nfs4_proc_open_confirm(struct nfs4_opendata *data)
    {
    	struct nfs_server *server = NFS_SERVER(data->dir->d_inode);
    	struct rpc_task *task;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int status;
    
    
    	atomic_inc(&data->count);
    
    	/*
    	 * If rpc_run_task() ends up calling ->rpc_release(), we
    	 * want to ensure that it takes the 'error' code path.
    	 */
    	data->rpc_status = -ENOMEM;
    
    	task = rpc_run_task(server->client, RPC_TASK_ASYNC, &nfs4_open_confirm_ops, data);
    
    		return PTR_ERR(task);
    	status = nfs4_wait_for_completion_rpc_task(task);
    	if (status != 0) {
    		data->cancelled = 1;
    		smp_wmb();
    	} else
    		status = data->rpc_status;
    	rpc_release_task(task);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static void nfs4_open_prepare(struct rpc_task *task, void *calldata)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_opendata *data = calldata;
    	struct nfs4_state_owner *sp = data->owner;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN],
    
    		.rpc_argp = &data->o_arg,
    		.rpc_resp = &data->o_res,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.rpc_cred = sp->so_cred,
    	};
    
    	
    	if (nfs_wait_on_sequence(data->o_arg.seqid, task) != 0)
    		return;
    	/* Update sequence id. */
    	data->o_arg.id = sp->so_id;
    	data->o_arg.clientid = sp->so_client->cl_clientid;
    
    	if (data->o_arg.claim == NFS4_OPEN_CLAIM_PREVIOUS)
    		msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR];
    
    	rpc_call_setup(task, &msg, 0);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void nfs4_open_done(struct rpc_task *task, void *calldata)
    {
    	struct nfs4_opendata *data = calldata;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	data->rpc_status = task->tk_status;
    	if (RPC_ASSASSINATED(task))
    		return;
    	if (task->tk_status == 0) {
    		switch (data->o_res.f_attr->mode & S_IFMT) {
    
    				data->rpc_status = -ELOOP;
    
    				data->rpc_status = -EISDIR;
    
    				data->rpc_status = -ENOTDIR;
    
    		renew_lease(data->o_res.server, data->timestamp);
    
    	nfs_increment_open_seqid(data->rpc_status, data->o_arg.seqid);
    }
    
    static void nfs4_open_release(void *calldata)
    {
    	struct nfs4_opendata *data = calldata;
    	struct nfs4_state *state = NULL;
    
    	/* If this request hasn't been cancelled, do nothing */
    	if (data->cancelled == 0)
    		goto out_free;
    	/* In case of error, no cleanup! */
    	if (data->rpc_status != 0)
    		goto out_free;
    	/* In case we need an open_confirm, no cleanup! */
    	if (data->o_res.rflags & NFS4_OPEN_RESULT_CONFIRM)
    		goto out_free;
    	nfs_confirm_seqid(&data->owner->so_seqid, 0);
    	state = nfs4_opendata_to_nfs4_state(data);
    	if (state != NULL)
    		nfs4_close_state(state, data->o_arg.open_flags);
    out_free:
    	nfs4_opendata_free(data);
    }
    
    static const struct rpc_call_ops nfs4_open_ops = {
    	.rpc_call_prepare = nfs4_open_prepare,
    	.rpc_call_done = nfs4_open_done,
    	.rpc_release = nfs4_open_release,
    };
    
    /*
     * Note: On error, nfs4_proc_open will free the struct nfs4_opendata
     */
    static int _nfs4_proc_open(struct nfs4_opendata *data)
    {
    	struct inode *dir = data->dir->d_inode;
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs_openargs *o_arg = &data->o_arg;
    	struct nfs_openres *o_res = &data->o_res;
    	struct rpc_task *task;
    	int status;
    
    	atomic_inc(&data->count);
    
    	/*
    	 * If rpc_run_task() ends up calling ->rpc_release(), we
    	 * want to ensure that it takes the 'error' code path.
    	 */
    	data->rpc_status = -ENOMEM;
    
    	task = rpc_run_task(server->client, RPC_TASK_ASYNC, &nfs4_open_ops, data);
    
    		return PTR_ERR(task);
    	status = nfs4_wait_for_completion_rpc_task(task);
    	if (status != 0) {
    		data->cancelled = 1;
    		smp_wmb();
    	} else
    		status = data->rpc_status;
    	rpc_release_task(task);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status != 0)
    
    	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(data);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (status != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	nfs_confirm_seqid(&data->owner->so_seqid, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!(o_res->f_attr->valid & NFS_ATTR_FATTR))
    
    		return server->rpc_ops->getattr(server, &o_res->fh, o_res->f_attr);
    	return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    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;
    }
    
    
    int nfs4_recover_expired_lease(struct nfs_server *server)
    {
    
    	struct nfs_client *clp = server->nfs4_state;
    
    
    	if (test_and_clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state))
    		nfs4_schedule_state_recovery(clp);
    	return nfs4_wait_clnt_recover(server->client, clp);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * 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 inode *inode = state->inode;
    	struct nfs_delegation *delegation = NFS_I(inode)->delegation;
    
    	struct nfs4_opendata *opendata;
    
    	int openflags = state->state & (FMODE_READ|FMODE_WRITE);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (delegation != NULL && !(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) {
    
    		ret = _nfs4_do_access(inode, sp->so_cred, openflags);
    		if (ret < 0)
    			return ret;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		memcpy(&state->stateid, &delegation->stateid, sizeof(state->stateid));
    		set_bit(NFS_DELEGATED_STATE, &state->flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	opendata = nfs4_opendata_alloc(dentry, sp, openflags, NULL);
    
    		return -ENOMEM;
    	ret = nfs4_open_recover(opendata, state);
    	if (ret == -ESTALE) {
    		/* Invalidate the state owner so we don't ever use it again */
    		nfs4_drop_state_owner(sp);
    		d_drop(dentry);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	nfs4_opendata_free(opendata);
    
    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_open_context *ctx;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	ctx = nfs4_state_find_open_context(state);
    	if (IS_ERR(ctx))
    		return PTR_ERR(ctx);
    	ret = nfs4_do_open_expired(sp, state, ctx->dentry);
    	put_nfs_open_context(ctx);
    	return ret;
    
     * Returns a referenced nfs4_state if there is an open delegation on the file
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    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 nfs_client *clp = server->nfs4_state;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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;
    
    
    	err = -ENOMEM;
    	if (!(sp = nfs4_get_state_owner(server, cred))) {
    		dprintk("%s: nfs4_get_state_owner failed!\n", __FUNCTION__);
    		return err;
    	}
    
    	err = nfs4_recover_expired_lease(server);
    	if (err != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* 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;
    	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);
    
    		update_open_stateflags(state, open_flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		spin_unlock(&inode->i_lock);
    		goto out_ok;
    	} else if (state->state != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	lock_kernel();
    	err = _nfs4_do_access(inode, cred, open_flags);
    	unlock_kernel();
    	if (err != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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);
    	*res = state;
    
    	return 0;
    out_put_open_state:
    	nfs4_put_open_state(state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out_err:
    	up_read(&nfsi->rwsem);
    	up_read(&clp->cl_sem);
    
    	if (err != -EACCES)
    		nfs_inode_return_delegation(inode);
    
    out_put_state_owner:
    	nfs4_put_state_owner(sp);
    
    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 = ERR_PTR(-EIO);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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 a referenced nfs4_state
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    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 nfs_client *clp = server->nfs4_state;
    
    	struct nfs4_opendata *opendata;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int                     status;
    
    	/* Protect against reboot recovery conflicts */
    	status = -ENOMEM;
    	if (!(sp = nfs4_get_state_owner(server, cred))) {
    		dprintk("nfs4_do_open: nfs4_get_state_owner failed!\n");
    		goto out_err;
    	}
    
    	status = nfs4_recover_expired_lease(server);
    	if (status != 0)
    
    	down_read(&clp->cl_sem);
    	status = -ENOMEM;
    
    	opendata = nfs4_opendata_alloc(dentry, sp, flags, sattr);
    	if (opendata == NULL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = _nfs4_proc_open(opendata);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status != 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	status = -ENOMEM;
    
    	state = nfs4_opendata_to_nfs4_state(opendata);
    	if (state == NULL)
    
    		goto err_opendata_free;
    	if (opendata->o_res.delegation_type != 0)
    
    		nfs_inode_set_delegation(state->inode, cred, &opendata->o_res);
    
    	nfs4_opendata_free(opendata);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	nfs4_put_state_owner(sp);
    	up_read(&clp->cl_sem);
    	*res = state;
    	return 0;
    
    err_opendata_free:
    	nfs4_opendata_free(opendata);
    
    err_release_rwsem:
    	up_read(&clp->cl_sem);
    
    err_put_state_owner:
    	nfs4_put_state_owner(sp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out_err:
    	*res = NULL;
    	return status;
    }