Skip to content
Snippets Groups Projects
nfs4proc.c 161 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/delay.h>
    #include <linux/errno.h>
    #include <linux/string.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/sunrpc/clnt.h>
    
    #include <linux/sunrpc/gss_api.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/nfs.h>
    #include <linux/nfs4.h>
    #include <linux/nfs_fs.h>
    #include <linux/nfs_page.h>
    
    #include <linux/nfs_mount.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/namei.h>
    
    #include <linux/mount.h>
    
    #include <linux/module.h>
    
    #include <linux/sunrpc/bc_xprt.h>
    
    #include <linux/xattr.h>
    
    #include <linux/utsname.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"
    
    #include "callback.h"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #define NFSDBG_FACILITY		NFSDBG_PROC
    
    
    #define NFS4_POLL_RETRY_MIN	(HZ/10)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define NFS4_POLL_RETRY_MAX	(15*HZ)
    
    
    #define NFS4_MAX_LOOP_ON_RECOVER (10)
    
    
    struct nfs4_opendata;
    
    static int _nfs4_proc_open(struct nfs4_opendata *data);
    
    static int _nfs4_recover_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 *, struct nfs4_state *);
    
    static int _nfs4_proc_lookup(struct rpc_clnt *client, struct inode *dir,
    			     const struct qstr *name, struct nfs_fh *fhandle,
    			     struct nfs_fattr *fattr);
    
    static int _nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr);
    
    static int nfs4_do_setattr(struct inode *inode, struct rpc_cred *cred,
    			    struct nfs_fattr *fattr, struct iattr *sattr,
    			    struct nfs4_state *state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* Prevent leaks of NFSv4 errors into userland */
    
    static int nfs4_map_errors(int err)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (err >= -1000)
    		return err;
    	switch (err) {
    	case -NFS4ERR_RESOURCE:
    		return -EREMOTEIO;
    
    	case -NFS4ERR_WRONGSEC:
    		return -EPERM;
    
    	case -NFS4ERR_BADOWNER:
    	case -NFS4ERR_BADNAME:
    		return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		dprintk("%s could not handle NFSv4 error %d\n",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     * 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,
    
    			FATTR4_WORD1_TIME_DELTA
    
    			| FATTR4_WORD1_FS_LAYOUT_TYPES
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    
    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
    };
    
    
    static void nfs4_setup_readdir(u64 cookie, __be32 *verifier, struct dentry *dentry,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		struct nfs4_readdir_arg *readdir)
    {
    
    	__be32 *start, *p;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	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 = kmap_atomic(*readdir->pages, KM_USER0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	
    	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));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	
    	*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));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	readdir->pgbase = (char *)p - (char *)start;
    	readdir->count -= readdir->pgbase;
    	kunmap_atomic(start, KM_USER0);
    }
    
    
    static int nfs4_wait_clnt_recover(struct nfs_client *clp)
    {
    	int res;
    
    	might_sleep();
    
    
    	res = wait_on_bit(&clp->cl_state, NFS4CLNT_MANAGER_RUNNING,
    
    			nfs_wait_bit_killable, TASK_KILLABLE);
    
    	return res;
    }
    
    static int nfs4_delay(struct rpc_clnt *clnt, long *timeout)
    {
    	int res = 0;
    
    	might_sleep();
    
    	if (*timeout <= 0)
    		*timeout = NFS4_POLL_RETRY_MIN;
    	if (*timeout > NFS4_POLL_RETRY_MAX)
    		*timeout = NFS4_POLL_RETRY_MAX;
    	schedule_timeout_killable(*timeout);
    	if (fatal_signal_pending(current))
    		res = -ERESTARTSYS;
    	*timeout <<= 1;
    	return res;
    }
    
    /* This is the error handling routine for processes that are allowed
     * to sleep.
     */
    
    static int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_exception *exception)
    
    {
    	struct nfs_client *clp = server->nfs_client;
    
    	struct nfs4_state *state = exception->state;
    
    	int ret = errorcode;
    
    	exception->retry = 0;
    	switch(errorcode) {
    		case 0:
    			return 0;
    
    		case -NFS4ERR_ADMIN_REVOKED:
    		case -NFS4ERR_BAD_STATEID:
    		case -NFS4ERR_OPENMODE:
    			if (state == NULL)
    				break;
    
    			nfs4_schedule_stateid_recovery(server, state);
    			goto wait_on_recovery;
    
    		case -NFS4ERR_STALE_CLIENTID:
    
    			nfs4_schedule_lease_recovery(clp);
    			goto wait_on_recovery;
    
    		case -NFS4ERR_BADSESSION:
    		case -NFS4ERR_BADSLOT:
    		case -NFS4ERR_BAD_HIGH_SLOT:
    		case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    		case -NFS4ERR_DEADSESSION:
    		case -NFS4ERR_SEQ_FALSE_RETRY:
    		case -NFS4ERR_SEQ_MISORDERED:
    			dprintk("%s ERROR: %d Reset session\n", __func__,
    				errorcode);
    
    			nfs4_schedule_session_recovery(clp->cl_session);
    
    #endif /* defined(CONFIG_NFS_V4_1) */
    
    			if (exception->timeout > HZ) {
    				/* We have retried a decent amount, time to
    				 * fail
    				 */
    				ret = -EBUSY;
    				break;
    			}
    
    		case -NFS4ERR_GRACE:
    		case -NFS4ERR_DELAY:
    
    		case -EKEYEXPIRED:
    
    			ret = nfs4_delay(server->client, &exception->timeout);
    			if (ret != 0)
    				break;
    
    		case -NFS4ERR_RETRY_UNCACHED_REP:
    
    		case -NFS4ERR_OLD_STATEID:
    			exception->retry = 1;
    
    			break;
    		case -NFS4ERR_BADOWNER:
    			/* The following works around a Linux server bug! */
    		case -NFS4ERR_BADNAME:
    			if (server->caps & NFS_CAP_UIDGID_NOMAP) {
    				server->caps &= ~NFS_CAP_UIDGID_NOMAP;
    				exception->retry = 1;
    				printk(KERN_WARNING "NFS: v4 server %s "
    						"does not accept raw "
    						"uid/gids. "
    						"Reenabling the idmapper.\n",
    						server->nfs_client->cl_hostname);
    			}
    
    	}
    	/* We failed to handle the error */
    	return nfs4_map_errors(ret);
    
    	ret = nfs4_wait_clnt_recover(clp);
    	if (ret == 0)
    		exception->retry = 1;
    	return ret;
    
    static void do_renew_lease(struct nfs_client *clp, unsigned long timestamp)
    
    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 renew_lease(const struct nfs_server *server, unsigned long timestamp)
    {
    	do_renew_lease(server->nfs_client, timestamp);
    }
    
    
    #if defined(CONFIG_NFS_V4_1)
    
    
    Andy Adamson's avatar
    Andy Adamson committed
    /*
     * nfs4_free_slot - free a slot and efficiently update slot table.
     *
     * freeing a slot is trivially done by clearing its respective bit
     * in the bitmap.
     * If the freed slotid equals highest_used_slotid we want to update it
     * so that the server would be able to size down the slot table if needed,
     * otherwise we know that the highest_used_slotid is still in use.
     * When updating highest_used_slotid there may be "holes" in the bitmap
     * so we need to scan down from highest_used_slotid to 0 looking for the now
     * highest slotid in use.
     * If none found, highest_used_slotid is set to -1.
    
     *
     * Must be called while holding tbl->slot_tbl_lock
    
    Andy Adamson's avatar
    Andy Adamson committed
     */
    static void
    
    nfs4_free_slot(struct nfs4_slot_table *tbl, struct nfs4_slot *free_slot)
    
    Andy Adamson's avatar
    Andy Adamson committed
    {
    
    	int free_slotid = free_slot - tbl->slots;
    
    Andy Adamson's avatar
    Andy Adamson committed
    	int slotid = free_slotid;
    
    
    	BUG_ON(slotid < 0 || slotid >= NFS4_MAX_SLOT_TABLE);
    
    Andy Adamson's avatar
    Andy Adamson committed
    	/* clear used bit in bitmap */
    	__clear_bit(slotid, tbl->used_slots);
    
    	/* update highest_used_slotid when it is freed */
    	if (slotid == tbl->highest_used_slotid) {
    		slotid = find_last_bit(tbl->used_slots, tbl->max_slots);
    
    Trond Myklebust's avatar
    Trond Myklebust committed
    		if (slotid < tbl->max_slots)
    
    Andy Adamson's avatar
    Andy Adamson committed
    			tbl->highest_used_slotid = slotid;
    		else
    			tbl->highest_used_slotid = -1;
    	}
    	dprintk("%s: free_slotid %u highest_used_slotid %d\n", __func__,
    		free_slotid, tbl->highest_used_slotid);
    }
    
    
     * Signal state manager thread if session fore channel is drained
    
    static void nfs4_check_drain_fc_complete(struct nfs4_session *ses)
    
    	if (!test_bit(NFS4_SESSION_DRAINING, &ses->session_state)) {
    
    		task = rpc_wake_up_next(&ses->fc_slot_table.slot_tbl_waitq);
    		if (task)
    			rpc_task_set_priority(task, RPC_PRIORITY_PRIVILEGED);
    
    		return;
    	}
    
    	if (ses->fc_slot_table.highest_used_slotid != -1)
    		return;
    
    
    	dprintk("%s COMPLETE: Session Fore Channel Drained\n", __func__);
    	complete(&ses->fc_slot_table.complete);
    }
    
    /*
     * Signal state manager thread if session back channel is drained
     */
    void nfs4_check_drain_bc_complete(struct nfs4_session *ses)
    {
    	if (!test_bit(NFS4_SESSION_DRAINING, &ses->session_state) ||
    	    ses->bc_slot_table.highest_used_slotid != -1)
    		return;
    	dprintk("%s COMPLETE: Session Back Channel Drained\n", __func__);
    	complete(&ses->bc_slot_table.complete);
    
    static void nfs41_sequence_free_slot(struct nfs4_sequence_res *res)
    
    {
    	struct nfs4_slot_table *tbl;
    
    
    	tbl = &res->sr_session->fc_slot_table;
    
    		/* just wake up the next guy waiting since
    		 * we may have not consumed a slot after all */
    
    		dprintk("%s: No slot\n", __func__);
    
    	spin_lock(&tbl->slot_tbl_lock);
    
    	nfs4_free_slot(tbl, res->sr_slot);
    
    	nfs4_check_drain_fc_complete(res->sr_session);
    
    	spin_unlock(&tbl->slot_tbl_lock);
    
    static int nfs41_sequence_done(struct rpc_task *task, struct nfs4_sequence_res *res)
    
    Andy Adamson's avatar
    Andy Adamson committed
    {
    	unsigned long timestamp;
    
    	struct nfs_client *clp;
    
    Andy Adamson's avatar
    Andy Adamson committed
    
    	/*
    	 * sr_status remains 1 if an RPC level error occurred. The server
    	 * may or may not have processed the sequence operation..
    	 * Proceed as if the server received and processed the sequence
    	 * operation.
    	 */
    	if (res->sr_status == 1)
    		res->sr_status = NFS_OK;
    
    
    	/* don't increment the sequence number if the task wasn't sent */
    	if (!RPC_WAS_SENT(task))
    
    Andy Adamson's avatar
    Andy Adamson committed
    		goto out;
    
    
    	/* Check the SEQUENCE operation status */
    
    	switch (res->sr_status) {
    	case 0:
    
    Andy Adamson's avatar
    Andy Adamson committed
    		/* Update the slot's sequence and clientid lease timer */
    
    Andy Adamson's avatar
    Andy Adamson committed
    		timestamp = res->sr_renewal_time;
    
    		clp = res->sr_session->clp;
    
    		do_renew_lease(clp, timestamp);
    
    		/* Check sequence flags */
    
    		if (res->sr_status_flags != 0)
    			nfs4_schedule_lease_recovery(clp);
    
    		break;
    	case -NFS4ERR_DELAY:
    		/* The server detected a resend of the RPC call and
    		 * returned NFS4ERR_DELAY as per Section 2.10.6.2
    		 * of RFC5661.
    		 */
    
    		dprintk("%s: slot=%td seq=%d: Operation in progress\n",
    
    			__func__,
    			res->sr_slot - res->sr_session->fc_slot_table.slots,
    			res->sr_slot->seq_nr);
    
    		goto out_retry;
    	default:
    		/* Just update the slot sequence no. */
    
    Andy Adamson's avatar
    Andy Adamson committed
    	}
    out:
    	/* The session may be reset by one of the error handlers. */
    	dprintk("%s: Error %d free the slot \n", __func__, res->sr_status);
    
    	nfs41_sequence_free_slot(res);
    
    	if (!rpc_restart_call(task))
    
    		goto out;
    	rpc_delay(task, NFS4_POLL_RETRY_MAX);
    	return 0;
    
    static int nfs4_sequence_done(struct rpc_task *task,
    			       struct nfs4_sequence_res *res)
    
    	if (res->sr_session == NULL)
    		return 1;
    	return nfs41_sequence_done(task, res);
    
    Benny Halevy's avatar
    Benny Halevy committed
    /*
     * nfs4_find_slot - efficiently look for a free slot
     *
     * nfs4_find_slot looks for an unset bit in the used_slots bitmap.
     * If found, we mark the slot as used, update the highest_used_slotid,
     * and respectively set up the sequence operation args.
     * The slot number is returned if found, or NFS4_MAX_SLOT_TABLE otherwise.
    
     *
     * Note: must be called with under the slot_tbl_lock.
    
    Benny Halevy's avatar
    Benny Halevy committed
     */
    static u8
    
    nfs4_find_slot(struct nfs4_slot_table *tbl)
    
    Benny Halevy's avatar
    Benny Halevy committed
    {
    	int slotid;
    	u8 ret_id = NFS4_MAX_SLOT_TABLE;
    	BUILD_BUG_ON((u8)NFS4_MAX_SLOT_TABLE != (int)NFS4_MAX_SLOT_TABLE);
    
    	dprintk("--> %s used_slots=%04lx highest_used=%d max_slots=%d\n",
    		__func__, tbl->used_slots[0], tbl->highest_used_slotid,
    		tbl->max_slots);
    	slotid = find_first_zero_bit(tbl->used_slots, tbl->max_slots);
    	if (slotid >= tbl->max_slots)
    		goto out;
    	__set_bit(slotid, tbl->used_slots);
    	if (slotid > tbl->highest_used_slotid)
    		tbl->highest_used_slotid = slotid;
    	ret_id = slotid;
    out:
    	dprintk("<-- %s used_slots=%04lx highest_used=%d slotid=%d \n",
    		__func__, tbl->used_slots[0], tbl->highest_used_slotid, ret_id);
    	return ret_id;
    }
    
    
    Andy Adamson's avatar
    Andy Adamson committed
    int nfs41_setup_sequence(struct nfs4_session *session,
    
    Andy Adamson's avatar
    Andy Adamson committed
    				struct nfs4_sequence_args *args,
    				struct nfs4_sequence_res *res,
    				int cache_reply,
    				struct rpc_task *task)
    {
    
    	struct nfs4_slot *slot;
    	struct nfs4_slot_table *tbl;
    	u8 slotid;
    
    	dprintk("--> %s\n", __func__);
    
    Andy Adamson's avatar
    Andy Adamson committed
    	/* slot already allocated? */
    
    Andy Adamson's avatar
    Andy Adamson committed
    		return 0;
    
    
    	tbl = &session->fc_slot_table;
    
    	spin_lock(&tbl->slot_tbl_lock);
    
    	if (test_bit(NFS4_SESSION_DRAINING, &session->session_state) &&
    
    	    !rpc_task_has_priority(task, RPC_PRIORITY_PRIVILEGED)) {
    
    		/*
    		 * The state manager will wait until the slot table is empty.
    		 * Schedule the reset thread
    		 */
    
    		rpc_sleep_on(&tbl->slot_tbl_waitq, task, NULL);
    
    		spin_unlock(&tbl->slot_tbl_lock);
    
    Trond Myklebust's avatar
    Trond Myklebust committed
    		dprintk("%s Schedule Session Reset\n", __func__);
    
    		return -EAGAIN;
    
    	if (!rpc_queue_empty(&tbl->slot_tbl_waitq) &&
    	    !rpc_task_has_priority(task, RPC_PRIORITY_PRIVILEGED)) {
    		rpc_sleep_on(&tbl->slot_tbl_waitq, task, NULL);
    		spin_unlock(&tbl->slot_tbl_lock);
    		dprintk("%s enforce FIFO order\n", __func__);
    		return -EAGAIN;
    	}
    
    
    	slotid = nfs4_find_slot(tbl);
    
    	if (slotid == NFS4_MAX_SLOT_TABLE) {
    		rpc_sleep_on(&tbl->slot_tbl_waitq, task, NULL);
    		spin_unlock(&tbl->slot_tbl_lock);
    		dprintk("<-- %s: no free slots\n", __func__);
    		return -EAGAIN;
    	}
    	spin_unlock(&tbl->slot_tbl_lock);
    
    
    	rpc_task_set_priority(task, RPC_PRIORITY_NORMAL);
    
    	slot = tbl->slots + slotid;
    
    	args->sa_session = session;
    
    	args->sa_slotid = slotid;
    	args->sa_cache_this = cache_reply;
    
    	dprintk("<-- %s slotid=%d seqid=%d\n", __func__, slotid, slot->seq_nr);
    
    
    	res->sr_session = session;
    
    	res->sr_renewal_time = jiffies;
    
    	res->sr_status_flags = 0;
    
    	/*
    	 * sr_status is only set in decode_sequence, and so will remain
    	 * set to 1 if an rpc level failure occurs.
    	 */
    	res->sr_status = 1;
    
    Andy Adamson's avatar
    Andy Adamson committed
    	return 0;
    }
    
    Andy Adamson's avatar
    Andy Adamson committed
    EXPORT_SYMBOL_GPL(nfs41_setup_sequence);
    
    int nfs4_setup_sequence(const struct nfs_server *server,
    
    Andy Adamson's avatar
    Andy Adamson committed
    			struct nfs4_sequence_args *args,
    			struct nfs4_sequence_res *res,
    			int cache_reply,
    			struct rpc_task *task)
    {
    
    	struct nfs4_session *session = nfs4_get_session(server);
    
    Andy Adamson's avatar
    Andy Adamson committed
    	int ret = 0;
    
    
    	if (session == NULL) {
    		args->sa_session = NULL;
    		res->sr_session = NULL;
    		goto out;
    	}
    
    
    	dprintk("--> %s clp %p session %p sr_slot %td\n",
    
    		__func__, session->clp, session, res->sr_slot ?
    			res->sr_slot - session->fc_slot_table.slots : -1);
    
    	ret = nfs41_setup_sequence(session, args, res, cache_reply,
    
    Andy Adamson's avatar
    Andy Adamson committed
    				   task);
    out:
    	dprintk("<-- %s status=%d\n", __func__, ret);
    	return ret;
    }
    
    struct nfs41_call_sync_data {
    
    	const struct nfs_server *seq_server;
    
    Andy Adamson's avatar
    Andy Adamson committed
    	struct nfs4_sequence_args *seq_args;
    	struct nfs4_sequence_res *seq_res;
    	int cache_reply;
    };
    
    static void nfs41_call_sync_prepare(struct rpc_task *task, void *calldata)
    {
    	struct nfs41_call_sync_data *data = calldata;
    
    
    	dprintk("--> %s data->seq_server %p\n", __func__, data->seq_server);
    
    	if (nfs4_setup_sequence(data->seq_server, data->seq_args,
    
    Andy Adamson's avatar
    Andy Adamson committed
    				data->seq_res, data->cache_reply, task))
    		return;
    	rpc_call_start(task);
    }
    
    
    static void nfs41_call_priv_sync_prepare(struct rpc_task *task, void *calldata)
    {
    	rpc_task_set_priority(task, RPC_PRIORITY_PRIVILEGED);
    	nfs41_call_sync_prepare(task, calldata);
    }
    
    
    static void nfs41_call_sync_done(struct rpc_task *task, void *calldata)
    {
    	struct nfs41_call_sync_data *data = calldata;
    
    
    	nfs41_sequence_done(task, data->seq_res);
    
    Andy Adamson's avatar
    Andy Adamson committed
    struct rpc_call_ops nfs41_call_sync_ops = {
    	.rpc_call_prepare = nfs41_call_sync_prepare,
    
    	.rpc_call_done = nfs41_call_sync_done,
    
    struct rpc_call_ops nfs41_call_priv_sync_ops = {
    	.rpc_call_prepare = nfs41_call_priv_sync_prepare,
    	.rpc_call_done = nfs41_call_sync_done,
    };
    
    
    static int nfs4_call_sync_sequence(struct rpc_clnt *clnt,
    				   struct nfs_server *server,
    
    Andy Adamson's avatar
    Andy Adamson committed
    				   struct rpc_message *msg,
    				   struct nfs4_sequence_args *args,
    				   struct nfs4_sequence_res *res,
    
    				   int cache_reply,
    				   int privileged)
    
    Andy Adamson's avatar
    Andy Adamson committed
    {
    	int ret;
    	struct rpc_task *task;
    	struct nfs41_call_sync_data data = {
    
    Andy Adamson's avatar
    Andy Adamson committed
    		.seq_args = args,
    		.seq_res = res,
    		.cache_reply = cache_reply,
    	};
    	struct rpc_task_setup task_setup = {
    
    		.rpc_client = clnt,
    
    Andy Adamson's avatar
    Andy Adamson committed
    		.rpc_message = msg,
    		.callback_ops = &nfs41_call_sync_ops,
    		.callback_data = &data
    	};
    
    
    	if (privileged)
    		task_setup.callback_ops = &nfs41_call_priv_sync_ops;
    
    Andy Adamson's avatar
    Andy Adamson committed
    	task = rpc_run_task(&task_setup);
    	if (IS_ERR(task))
    		ret = PTR_ERR(task);
    	else {
    		ret = task->tk_status;
    		rpc_put_task(task);
    	}
    	return ret;
    }
    
    
    int _nfs4_call_sync_session(struct rpc_clnt *clnt,
    			    struct nfs_server *server,
    
    			    struct rpc_message *msg,
    			    struct nfs4_sequence_args *args,
    			    struct nfs4_sequence_res *res,
    			    int cache_reply)
    {
    
    	return nfs4_call_sync_sequence(clnt, server, msg, args, res, cache_reply, 0);
    
    static int nfs4_sequence_done(struct rpc_task *task,
    			       struct nfs4_sequence_res *res)
    
    #endif /* CONFIG_NFS_V4_1 */
    
    
    int _nfs4_call_sync(struct rpc_clnt *clnt,
    		    struct nfs_server *server,
    
    		    struct rpc_message *msg,
    		    struct nfs4_sequence_args *args,
    		    struct nfs4_sequence_res *res,
    		    int cache_reply)
    {
    
    	args->sa_session = res->sr_session = NULL;
    
    	return rpc_call_sync(clnt, msg, 0);
    
    static inline
    
    int nfs4_call_sync(struct rpc_clnt *clnt,
    		   struct nfs_server *server,
    
    		   struct rpc_message *msg,
    		   struct nfs4_sequence_args *args,
    		   struct nfs4_sequence_res *res,
    		   int cache_reply)
    {
    
    	return server->nfs_client->cl_mvops->call_sync(clnt, server, msg,
    						args, res, cache_reply);
    
    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;
    	if (!cinfo->atomic || cinfo->before != nfsi->change_attr)
    
    		nfs_force_lookup_revalidate(dir);
    
    	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 *dir;
    	struct nfs4_state_owner *owner;
    
    	unsigned int rpc_done : 1;
    
    	int rpc_status;
    	int cancelled;
    
    
    static void nfs4_init_opendata_res(struct nfs4_opendata *p)
    {
    	p->o_res.f_attr = &p->f_attr;
    	p->o_res.dir_attr = &p->dir_attr;
    
    	p->o_res.seqid = p->o_arg.seqid;
    	p->c_res.seqid = p->c_arg.seqid;
    
    	p->o_res.server = p->o_arg.server;
    	nfs_fattr_init(&p->f_attr);
    	nfs_fattr_init(&p->dir_attr);
    }
    
    
    static struct nfs4_opendata *nfs4_opendata_alloc(struct path *path,
    
    		struct nfs4_state_owner *sp, fmode_t fmode, int flags,
    
    		const struct iattr *attrs,
    		gfp_t gfp_mask)
    
    	struct dentry *parent = dget_parent(path->dentry);
    
    	struct inode *dir = parent->d_inode;
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs4_opendata *p;
    
    
    	p = kzalloc(sizeof(*p), gfp_mask);
    
    	p->o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid, gfp_mask);
    
    	if (p->o_arg.seqid == NULL)
    		goto err_free;
    
    	path_get(path);
    	p->path = *path;
    
    	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.fmode = fmode & (FMODE_READ|FMODE_WRITE);
    
    	p->o_arg.clientid = server->nfs_client->cl_clientid;
    
    	p->o_arg.id = sp->so_owner_id.id;
    
    	p->o_arg.name = &p->path.dentry->d_name;
    
    	p->o_arg.server = server;
    	p->o_arg.bitmask = server->attr_bitmask;
    	p->o_arg.claim = NFS4_OPEN_CLAIM_NULL;
    
    	if (flags & O_CREAT) {
    		u32 *s;
    
    
    		p->o_arg.u.attrs = &p->attrs;
    		memcpy(&p->attrs, attrs, sizeof(p->attrs));
    
    		s = (u32 *) p->o_arg.u.verifier.data;
    		s[0] = jiffies;
    		s[1] = current->pid;
    
    	p->c_arg.fh = &p->o_res.fh;
    	p->c_arg.stateid = &p->o_res.stateid;
    	p->c_arg.seqid = p->o_arg.seqid;
    
    	nfs4_init_opendata_res(p);
    
    	return p;
    err_free:
    	kfree(p);
    err:
    	dput(parent);
    	return NULL;
    }
    
    
    static void nfs4_opendata_free(struct kref *kref)
    
    	struct nfs4_opendata *p = container_of(kref,
    			struct nfs4_opendata, kref);
    
    	nfs_free_seqid(p->o_arg.seqid);
    
    	if (p->state != NULL)
    		nfs4_put_open_state(p->state);
    
    	nfs4_put_state_owner(p->owner);
    	dput(p->dir);
    
    	path_put(&p->path);
    
    	kfree(p);
    }
    
    static void nfs4_opendata_put(struct nfs4_opendata *p)
    {
    	if (p != NULL)
    		kref_put(&p->kref, nfs4_opendata_free);
    
    static int nfs4_wait_for_completion_rpc_task(struct rpc_task *task)
    {
    	int ret;
    
    	ret = rpc_wait_for_completion_task(task);
    	return ret;
    }
    
    
    static int can_open_cached(struct nfs4_state *state, fmode_t mode, int open_mode)
    
    
    	if (open_mode & O_EXCL)
    		goto out;
    	switch (mode & (FMODE_READ|FMODE_WRITE)) {
    
    			ret |= test_bit(NFS_O_RDONLY_STATE, &state->flags) != 0
    				&& state->n_rdonly != 0;
    
    			ret |= test_bit(NFS_O_WRONLY_STATE, &state->flags) != 0
    				&& state->n_wronly != 0;
    
    			ret |= test_bit(NFS_O_RDWR_STATE, &state->flags) != 0
    				&& state->n_rdwr != 0;
    
    static int can_open_delegated(struct nfs_delegation *delegation, fmode_t fmode)
    
    	if ((delegation->type & fmode) != fmode)
    
    	if (test_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags))
    
    	nfs_mark_delegation_referenced(delegation);
    
    static void update_open_stateflags(struct nfs4_state *state, fmode_t fmode)
    
    		case FMODE_WRITE:
    			state->n_wronly++;
    			break;
    		case FMODE_READ:
    			state->n_rdonly++;
    			break;
    		case FMODE_READ|FMODE_WRITE:
    			state->n_rdwr++;
    	}
    
    	nfs4_state_set_mode_locked(state, state->state | fmode);
    
    static void nfs_set_open_stateid_locked(struct nfs4_state *state, nfs4_stateid *stateid, fmode_t fmode)
    
    {
    	if (test_bit(NFS_DELEGATED_STATE, &state->flags) == 0)
    		memcpy(state->stateid.data, stateid->data, sizeof(state->stateid.data));
    	memcpy(state->open_stateid.data, stateid->data, sizeof(state->open_stateid.data));
    
    		case FMODE_READ:
    			set_bit(NFS_O_RDONLY_STATE, &state->flags);
    			break;
    		case FMODE_WRITE:
    			set_bit(NFS_O_WRONLY_STATE, &state->flags);
    			break;
    		case FMODE_READ|FMODE_WRITE:
    			set_bit(NFS_O_RDWR_STATE, &state->flags);
    	}
    
    static void nfs_set_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, fmode_t fmode)
    
    	write_seqlock(&state->seqlock);
    
    	nfs_set_open_stateid_locked(state, stateid, fmode);
    
    	write_sequnlock(&state->seqlock);
    
    static void __update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, const nfs4_stateid *deleg_stateid, fmode_t fmode)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	/*
    	 * Protect the call to nfs4_state_set_mode_locked and
    	 * serialise the stateid update
    	 */
    	write_seqlock(&state->seqlock);
    
    	if (deleg_stateid != NULL) {
    		memcpy(state->stateid.data, deleg_stateid->data, sizeof(state->stateid.data));
    		set_bit(NFS_DELEGATED_STATE, &state->flags);
    	}
    	if (open_stateid != NULL)
    
    		nfs_set_open_stateid_locked(state, open_stateid, fmode);
    
    	write_sequnlock(&state->seqlock);
    	spin_lock(&state->owner->so_lock);
    
    	update_open_stateflags(state, fmode);
    
    	spin_unlock(&state->owner->so_lock);
    
    static int update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, nfs4_stateid *delegation, fmode_t fmode)
    
    {
    	struct nfs_inode *nfsi = NFS_I(state->inode);
    	struct nfs_delegation *deleg_cur;
    	int ret = 0;
    
    
    	fmode &= (FMODE_READ|FMODE_WRITE);
    
    
    	rcu_read_lock();
    	deleg_cur = rcu_dereference(nfsi->delegation);
    	if (deleg_cur == NULL)
    		goto no_delegation;
    
    	spin_lock(&deleg_cur->lock);
    	if (nfsi->delegation != deleg_cur ||
    
    	    (deleg_cur->type & fmode) != fmode)
    
    		goto no_delegation_unlock;
    
    	if (delegation == NULL)
    		delegation = &deleg_cur->stateid;
    	else if (memcmp(deleg_cur->stateid.data, delegation->data, NFS4_STATEID_SIZE) != 0)
    		goto no_delegation_unlock;
    
    
    	nfs_mark_delegation_referenced(deleg_cur);
    
    	__update_open_stateid(state, open_stateid, &deleg_cur->stateid, fmode);
    
    	ret = 1;
    no_delegation_unlock:
    	spin_unlock(&deleg_cur->lock);
    no_delegation:
    	rcu_read_unlock();
    
    	if (!ret && open_stateid != NULL) {
    
    		__update_open_stateid(state, open_stateid, NULL, fmode);
    
    static void nfs4_return_incompatible_delegation(struct inode *inode, fmode_t fmode)