Skip to content
Snippets Groups Projects
nfs4state.c 58.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_lock_state *lsp;
    
    	fl_owner_t fl_owner;
    	pid_t fl_pid;
    
    	bool ret = false;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (test_bit(LK_STATE_IN_USE, &state->flags) == 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	fl_owner = lockowner->l_owner;
    	fl_pid = lockowner->l_pid;
    
    	spin_lock(&state->state_lock);
    
    	lsp = __nfs4_find_lock_state(state, fl_owner, fl_pid, NFS4_ANY_LOCK_TYPE);
    
    	if (lsp != NULL && test_bit(NFS_LOCK_INITIALIZED, &lsp->ls_flags) != 0) {
    
    		nfs4_stateid_copy(dst, &lsp->ls_stateid);
    
    	spin_unlock(&state->state_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	nfs4_put_lock_state(lsp);
    
    out:
    	return ret;
    }
    
    static void nfs4_copy_open_stateid(nfs4_stateid *dst, struct nfs4_state *state)
    {
    	int seq;
    
    	do {
    		seq = read_seqbegin(&state->seqlock);
    		nfs4_stateid_copy(dst, &state->stateid);
    	} while (read_seqretry(&state->seqlock, seq));
    }
    
    /*
     * Byte-range lock aware utility to initialize the stateid of read/write
     * requests.
     */
    void nfs4_select_rw_stateid(nfs4_stateid *dst, struct nfs4_state *state,
    
    		fmode_t fmode, const struct nfs_lockowner *lockowner)
    
    {
    	if (nfs4_copy_delegation_stateid(dst, state->inode, fmode))
    		return;
    
    	if (nfs4_copy_lock_stateid(dst, state, lockowner))
    
    		return;
    	nfs4_copy_open_stateid(dst, state);
    
    struct nfs_seqid *nfs_alloc_seqid(struct nfs_seqid_counter *counter, gfp_t gfp_mask)
    
    	new = kmalloc(sizeof(*new), gfp_mask);
    
    	if (new != NULL) {
    		new->sequence = counter;
    
    		INIT_LIST_HEAD(&new->list);
    
    void nfs_release_seqid(struct nfs_seqid *seqid)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_seqid_counter *sequence;
    
    	if (list_empty(&seqid->list))
    		return;
    	sequence = seqid->sequence;
    	spin_lock(&sequence->lock);
    	list_del_init(&seqid->list);
    	if (!list_empty(&sequence->list)) {
    		struct nfs_seqid *next;
    
    		next = list_first_entry(&sequence->list,
    				struct nfs_seqid, list);
    		rpc_wake_up_queued_task(&sequence->wait, next->task);
    
    	spin_unlock(&sequence->lock);
    
    }
    
    void nfs_free_seqid(struct nfs_seqid *seqid)
    {
    	nfs_release_seqid(seqid);
    
     * Increment the seqid if the OPEN/OPEN_DOWNGRADE/CLOSE succeeded, or
     * failed with a seqid incrementing error -
     * see comments nfs_fs.h:seqid_mutating_error()
     */
    
    static void nfs_increment_seqid(int status, struct nfs_seqid *seqid)
    
    {
    	switch (status) {
    		case 0:
    			break;
    		case -NFS4ERR_BAD_SEQID:
    
    			if (seqid->sequence->flags & NFS_SEQID_CONFIRMED)
    				return;
    
    			pr_warn_ratelimited("NFS: v4 server returned a bad"
    
    					" sequence-id error on an"
    					" unconfirmed sequence %p!\n",
    
    		case -NFS4ERR_STALE_CLIENTID:
    		case -NFS4ERR_STALE_STATEID:
    		case -NFS4ERR_BAD_STATEID:
    		case -NFS4ERR_BADXDR:
    		case -NFS4ERR_RESOURCE:
    		case -NFS4ERR_NOFILEHANDLE:
    			/* Non-seqid mutating errors */
    			return;
    	};
    	/*
    	 * Note: no locking needed as we are guaranteed to be first
    	 * on the sequence list
    	 */
    	seqid->sequence->counter++;
    }
    
    void nfs_increment_open_seqid(int status, struct nfs_seqid *seqid)
    {
    
    	struct nfs4_state_owner *sp = container_of(seqid->sequence,
    					struct nfs4_state_owner, so_seqid);
    	struct nfs_server *server = sp->so_server;
    
    	if (status == -NFS4ERR_BAD_SEQID)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		nfs4_drop_state_owner(sp);
    
    	if (!nfs4_has_session(server->nfs_client))
    		nfs_increment_seqid(status, seqid);
    
    }
    
    /*
     * Increment the seqid if the LOCK/LOCKU succeeded, or
     * failed with a seqid incrementing error -
     * see comments nfs_fs.h:seqid_mutating_error()
     */
    void nfs_increment_lock_seqid(int status, struct nfs_seqid *seqid)
    {
    
    	nfs_increment_seqid(status, seqid);
    
    }
    
    int nfs_wait_on_sequence(struct nfs_seqid *seqid, struct rpc_task *task)
    {
    
    	struct nfs_seqid_counter *sequence = seqid->sequence;
    
    	int status = 0;
    
    	spin_lock(&sequence->lock);
    
    	if (list_empty(&seqid->list))
    		list_add_tail(&seqid->list, &sequence->list);
    	if (list_first_entry(&sequence->list, struct nfs_seqid, list) == seqid)
    		goto unlock;
    
    	rpc_sleep_on(&sequence->wait, task, NULL);
    
    	spin_unlock(&sequence->lock);
    	return status;
    
    static int nfs4_run_state_manager(void *);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void nfs4_clear_state_manager_bit(struct nfs_client *clp)
    
    {
    	smp_mb__before_clear_bit();
    
    	clear_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state);
    
    	smp_mb__after_clear_bit();
    
    	wake_up_bit(&clp->cl_state, NFS4CLNT_MANAGER_RUNNING);
    
    	rpc_wake_up(&clp->cl_rpcwaitq);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
    
     * Schedule the nfs_client asynchronous state management routine
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    void nfs4_schedule_state_manager(struct nfs_client *clp)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct task_struct *task;
    
    	char buf[INET6_ADDRSTRLEN + sizeof("-manager") + 1];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (test_and_set_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state) != 0)
    		return;
    
    	__module_get(THIS_MODULE);
    	atomic_inc(&clp->cl_count);
    
    
    	/* The rcu_read_lock() is not strictly necessary, as the state
    	 * manager is the only thread that ever changes the rpc_xprt
    	 * after it's initialized.  At this point, we're single threaded. */
    	rcu_read_lock();
    	snprintf(buf, sizeof(buf), "%s-manager",
    			rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_ADDR));
    	rcu_read_unlock();
    	task = kthread_run(nfs4_run_state_manager, clp, buf);
    	if (IS_ERR(task)) {
    		printk(KERN_ERR "%s: kthread_run: %ld\n",
    			__func__, PTR_ERR(task));
    		nfs4_clear_state_manager_bit(clp);
    		nfs_put_client(clp);
    		module_put(THIS_MODULE);
    	}
    
     * Schedule a lease recovery attempt
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    void nfs4_schedule_lease_recovery(struct nfs_client *clp)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (!clp)
    		return;
    
    	if (!test_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state))
    		set_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state);
    
    	dprintk("%s: scheduling lease recovery for server %s\n", __func__,
    			clp->cl_hostname);
    
    	nfs4_schedule_state_manager(clp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    EXPORT_SYMBOL_GPL(nfs4_schedule_lease_recovery);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    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);
    	if (res)
    		return res;
    
    	if (clp->cl_cons_state < 0)
    		return clp->cl_cons_state;
    	return 0;
    }
    
    int nfs4_client_recover_expired_lease(struct nfs_client *clp)
    {
    	unsigned int loop;
    	int ret;
    
    	for (loop = NFS4_MAX_LOOP_ON_RECOVER; loop != 0; loop--) {
    		ret = nfs4_wait_clnt_recover(clp);
    		if (ret != 0)
    			break;
    		if (!test_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state) &&
    		    !test_bit(NFS4CLNT_CHECK_LEASE,&clp->cl_state))
    			break;
    		nfs4_schedule_state_manager(clp);
    		ret = -EIO;
    	}
    	return ret;
    }
    
    
    /*
     * nfs40_handle_cb_pathdown - return all delegations after NFS4ERR_CB_PATH_DOWN
     * @clp: client to process
     *
     * Set the NFS4CLNT_LEASE_EXPIRED state in order to force a
     * resend of the SETCLIENTID and hence re-establish the
     * callback channel. Then return all existing delegations.
     */
    static void nfs40_handle_cb_pathdown(struct nfs_client *clp)
    {
    	set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    	nfs_expire_all_delegations(clp);
    
    	dprintk("%s: handling CB_PATHDOWN recovery for server %s\n", __func__,
    			clp->cl_hostname);
    
    void nfs4_schedule_path_down_recovery(struct nfs_client *clp)
    {
    
    static int nfs4_state_mark_reclaim_reboot(struct nfs_client *clp, struct nfs4_state *state)
    
    {
    
    	set_bit(NFS_STATE_RECLAIM_REBOOT, &state->flags);
    	/* Don't recover state that expired before the reboot */
    	if (test_bit(NFS_STATE_RECLAIM_NOGRACE, &state->flags)) {
    		clear_bit(NFS_STATE_RECLAIM_REBOOT, &state->flags);
    		return 0;
    	}
    
    	set_bit(NFS_OWNER_RECLAIM_REBOOT, &state->owner->so_flags);
    
    	set_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state);
    	return 1;
    }
    
    
    static int nfs4_state_mark_reclaim_nograce(struct nfs_client *clp, struct nfs4_state *state)
    
    {
    	set_bit(NFS_STATE_RECLAIM_NOGRACE, &state->flags);
    	clear_bit(NFS_STATE_RECLAIM_REBOOT, &state->flags);
    
    	set_bit(NFS_OWNER_RECLAIM_NOGRACE, &state->owner->so_flags);
    
    	set_bit(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state);
    	return 1;
    }
    
    
    void nfs4_schedule_stateid_recovery(const struct nfs_server *server, struct nfs4_state *state)
    {
    	struct nfs_client *clp = server->nfs_client;
    
    	nfs4_state_mark_reclaim_nograce(clp, state);
    
    	dprintk("%s: scheduling stateid recovery for server %s\n", __func__,
    			clp->cl_hostname);
    
    	nfs4_schedule_state_manager(clp);
    }
    
    EXPORT_SYMBOL_GPL(nfs4_schedule_stateid_recovery);
    
    void nfs_inode_find_state_and_recover(struct inode *inode,
    		const nfs4_stateid *stateid)
    {
    	struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
    	struct nfs_inode *nfsi = NFS_I(inode);
    	struct nfs_open_context *ctx;
    	struct nfs4_state *state;
    	bool found = false;
    
    	spin_lock(&inode->i_lock);
    	list_for_each_entry(ctx, &nfsi->open_files, list) {
    		state = ctx->state;
    		if (state == NULL)
    			continue;
    		if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
    			continue;
    
    		if (!nfs4_stateid_match(&state->stateid, stateid))
    
    			continue;
    		nfs4_state_mark_reclaim_nograce(clp, state);
    		found = true;
    	}
    	spin_unlock(&inode->i_lock);
    	if (found)
    		nfs4_schedule_state_manager(clp);
    }
    
    
    
    static int nfs4_reclaim_locks(struct nfs4_state *state, const struct nfs4_state_recovery_ops *ops)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inode *inode = state->inode;
    
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct file_lock *fl;
    	int status = 0;
    
    
    	if (inode->i_flock == NULL)
    		return 0;
    
    	/* Guard against delegation returns and new lock/unlock calls */
    
    	/* Protect inode->i_flock using the BKL */
    
    	lock_flocks();
    
    	for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {
    
    		if (!(fl->fl_flags & (FL_POSIX|FL_FLOCK)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			continue;
    
    		if (nfs_file_open_context(fl->fl_file)->state != state)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			continue;
    
    		unlock_flocks();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		status = ops->recover_lock(state, fl);
    		switch (status) {
    
    			case 0:
    				break;
    			case -ESTALE:
    			case -NFS4ERR_ADMIN_REVOKED:
    			case -NFS4ERR_STALE_STATEID:
    			case -NFS4ERR_BAD_STATEID:
    			case -NFS4ERR_EXPIRED:
    			case -NFS4ERR_NO_GRACE:
    			case -NFS4ERR_STALE_CLIENTID:
    
    			case -NFS4ERR_BADSESSION:
    			case -NFS4ERR_BADSLOT:
    			case -NFS4ERR_BAD_HIGH_SLOT:
    			case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			default:
    
    				printk(KERN_ERR "NFS: %s: unhandled error %d. "
    					"Zeroing state\n", __func__, status);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			case -NFS4ERR_RECLAIM_BAD:
    			case -NFS4ERR_RECLAIM_CONFLICT:
    
    				/* kill_proc(fl->fl_pid, SIGLOST, 1); */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    		lock_flocks();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	unlock_flocks();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static int nfs4_reclaim_open_state(struct nfs4_state_owner *sp, const struct nfs4_state_recovery_ops *ops)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs4_state *state;
    	struct nfs4_lock_state *lock;
    	int status = 0;
    
    	/* Note: we rely on the sp->so_states list being ordered 
    	 * so that we always reclaim open(O_RDWR) and/or open(O_WRITE)
    	 * states first.
    	 * This is needed to ensure that the server won't give us any
    	 * read delegations that we have to return if, say, we are
    	 * recovering after a network partition or a reboot from a
    	 * server that doesn't support a grace period.
    	 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	list_for_each_entry(state, &sp->so_states, open_states) {
    
    		if (!test_and_clear_bit(ops->state_flag_bit, &state->flags))
    			continue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (state->state == 0)
    			continue;
    
    		atomic_inc(&state->count);
    		spin_unlock(&sp->so_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		status = ops->recover_open(sp, state);
    		if (status >= 0) {
    
    			status = nfs4_reclaim_locks(state, ops);
    			if (status >= 0) {
    
    				list_for_each_entry(lock, &state->lock_states, ls_locks) {
    
    					if (!test_bit(NFS_LOCK_INITIALIZED, &lock->ls_flags))
    
    						pr_warn_ratelimited("NFS: "
    							"%s: Lock reclaim "
    
    				nfs4_put_open_state(state);
    				goto restart;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			}
    		}
    		switch (status) {
    			default:
    
    				printk(KERN_ERR "NFS: %s: unhandled error %d. "
    					"Zeroing state\n", __func__, status);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			case -ENOENT:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				/*
    				 * Open state on this file cannot be recovered
    				 * All we can do is revert to using the zero stateid.
    				 */
    
    				memset(&state->stateid, 0,
    					sizeof(state->stateid));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				/* Mark the file as being 'closed' */
    				state->state = 0;
    				break;
    
    			case -EKEYEXPIRED:
    				/*
    				 * User RPCSEC_GSS context has expired.
    				 * We cannot recover this stateid now, so
    				 * skip it and allow recovery thread to
    				 * proceed.
    				 */
    				break;
    
    			case -NFS4ERR_ADMIN_REVOKED:
    			case -NFS4ERR_STALE_STATEID:
    			case -NFS4ERR_BAD_STATEID:
    
    			case -NFS4ERR_RECLAIM_BAD:
    			case -NFS4ERR_RECLAIM_CONFLICT:
    
    				nfs4_state_mark_reclaim_nograce(sp->so_server->nfs_client, state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			case -NFS4ERR_EXPIRED:
    			case -NFS4ERR_NO_GRACE:
    
    				nfs4_state_mark_reclaim_nograce(sp->so_server->nfs_client, state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			case -NFS4ERR_STALE_CLIENTID:
    
    			case -NFS4ERR_BADSESSION:
    			case -NFS4ERR_BADSLOT:
    			case -NFS4ERR_BAD_HIGH_SLOT:
    			case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				goto out_err;
    		}
    
    		nfs4_put_open_state(state);
    		goto restart;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    out_err:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static void nfs4_clear_open_state(struct nfs4_state *state)
    {
    	struct nfs4_lock_state *lock;
    
    	clear_bit(NFS_DELEGATED_STATE, &state->flags);
    	clear_bit(NFS_O_RDONLY_STATE, &state->flags);
    	clear_bit(NFS_O_WRONLY_STATE, &state->flags);
    	clear_bit(NFS_O_RDWR_STATE, &state->flags);
    
    	list_for_each_entry(lock, &state->lock_states, ls_locks) {
    		lock->ls_seqid.flags = 0;
    
    		clear_bit(NFS_LOCK_INITIALIZED, &lock->ls_flags);
    
    static void nfs4_reset_seqids(struct nfs_server *server,
    	int (*mark_reclaim)(struct nfs_client *clp, struct nfs4_state *state))
    
    	struct nfs_client *clp = server->nfs_client;
    
    	struct nfs4_state_owner *sp;
    
    	struct nfs4_state *state;
    
    
    	spin_lock(&clp->cl_lock);
    	for (pos = rb_first(&server->state_owners);
    	     pos != NULL;
    	     pos = rb_next(pos)) {
    		sp = rb_entry(pos, struct nfs4_state_owner, so_server_node);
    
    		sp->so_seqid.flags = 0;
    
    		spin_lock(&sp->so_lock);
    
    		list_for_each_entry(state, &sp->so_states, open_states) {
    
    			if (mark_reclaim(clp, state))
    				nfs4_clear_open_state(state);
    
    		spin_unlock(&sp->so_lock);
    
    	spin_unlock(&clp->cl_lock);
    }
    
    static void nfs4_state_mark_reclaim_helper(struct nfs_client *clp,
    	int (*mark_reclaim)(struct nfs_client *clp, struct nfs4_state *state))
    {
    	struct nfs_server *server;
    
    	rcu_read_lock();
    	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
    		nfs4_reset_seqids(server, mark_reclaim);
    	rcu_read_unlock();
    
    static void nfs4_state_start_reclaim_reboot(struct nfs_client *clp)
    {
    	/* Mark all delegations for reclaim */
    	nfs_delegation_mark_reclaim(clp);
    	nfs4_state_mark_reclaim_helper(clp, nfs4_state_mark_reclaim_reboot);
    }
    
    
    static void nfs4_reclaim_complete(struct nfs_client *clp,
    				 const struct nfs4_state_recovery_ops *ops)
    {
    	/* Notify the server we're done reclaiming our state */
    	if (ops->reclaim_complete)
    		(void)ops->reclaim_complete(clp);
    }
    
    
    static void nfs4_clear_reclaim_server(struct nfs_server *server)
    
    	struct nfs_client *clp = server->nfs_client;
    
    	struct nfs4_state_owner *sp;
    	struct rb_node *pos;
    	struct nfs4_state *state;
    
    
    	spin_lock(&clp->cl_lock);
    	for (pos = rb_first(&server->state_owners);
    	     pos != NULL;
    	     pos = rb_next(pos)) {
    		sp = rb_entry(pos, struct nfs4_state_owner, so_server_node);
    
    		spin_lock(&sp->so_lock);
    		list_for_each_entry(state, &sp->so_states, open_states) {
    
    			if (!test_and_clear_bit(NFS_STATE_RECLAIM_REBOOT,
    						&state->flags))
    
    				continue;
    			nfs4_state_mark_reclaim_nograce(clp, state);
    		}
    		spin_unlock(&sp->so_lock);
    	}
    
    	spin_unlock(&clp->cl_lock);
    }
    
    static int nfs4_state_clear_reclaim_reboot(struct nfs_client *clp)
    {
    	struct nfs_server *server;
    
    	if (!test_and_clear_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state))
    		return 0;
    
    	rcu_read_lock();
    	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
    		nfs4_clear_reclaim_server(server);
    	rcu_read_unlock();
    
    	return 1;
    }
    
    static void nfs4_state_end_reclaim_reboot(struct nfs_client *clp)
    {
    	if (!nfs4_state_clear_reclaim_reboot(clp))
    		return;
    	nfs4_reclaim_complete(clp, clp->cl_mvops->reboot_recovery_ops);
    
    }
    
    static void nfs_delegation_clear_all(struct nfs_client *clp)
    {
    	nfs_delegation_mark_reclaim(clp);
    	nfs_delegation_reap_unclaimed(clp);
    }
    
    static void nfs4_state_start_reclaim_nograce(struct nfs_client *clp)
    {
    	nfs_delegation_clear_all(clp);
    	nfs4_state_mark_reclaim_helper(clp, nfs4_state_mark_reclaim_nograce);
    }
    
    
    static void nfs4_warn_keyexpired(const char *s)
    {
    	printk_ratelimited(KERN_WARNING "Error: state manager"
    			" encountered RPCSEC_GSS session"
    			" expired against NFSv4 server %s.\n",
    			s);
    }
    
    
    static int nfs4_recovery_handle_error(struct nfs_client *clp, int error)
    
    		case -NFS4ERR_NO_GRACE:
    			nfs4_state_end_reclaim_reboot(clp);
    
    		case -NFS4ERR_STALE_CLIENTID:
    		case -NFS4ERR_LEASE_MOVED:
    			set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    
    			nfs4_state_start_reclaim_reboot(clp);
    			break;
    		case -NFS4ERR_EXPIRED:
    			set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    			nfs4_state_start_reclaim_nograce(clp);
    
    		case -NFS4ERR_BADSESSION:
    		case -NFS4ERR_BADSLOT:
    		case -NFS4ERR_BAD_HIGH_SLOT:
    		case -NFS4ERR_DEADSESSION:
    		case -NFS4ERR_SEQ_FALSE_RETRY:
    		case -NFS4ERR_SEQ_MISORDERED:
    
    			set_bit(NFS4CLNT_SESSION_RESET, &clp->cl_state);
    
    			/* Zero session reset errors */
    
    		case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    			set_bit(NFS4CLNT_BIND_CONN_TO_SESSION, &clp->cl_state);
    			break;
    
    		case -EKEYEXPIRED:
    			/* Nothing we can do */
    			nfs4_warn_keyexpired(clp->cl_hostname);
    
    			dprintk("%s: failed to handle error %d for server %s\n",
    					__func__, error, clp->cl_hostname);
    
    	dprintk("%s: handled error %d for server %s\n", __func__, error,
    			clp->cl_hostname);
    
    static int nfs4_do_reclaim(struct nfs_client *clp, const struct nfs4_state_recovery_ops *ops)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_state_owner *sp;
    	struct nfs_server *server;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int status = 0;
    
    
    	rcu_read_lock();
    	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
    
    		nfs4_purge_state_owners(server);
    
    		spin_lock(&clp->cl_lock);
    		for (pos = rb_first(&server->state_owners);
    		     pos != NULL;
    		     pos = rb_next(pos)) {
    			sp = rb_entry(pos,
    				struct nfs4_state_owner, so_server_node);
    			if (!test_and_clear_bit(ops->owner_flag_bit,
    							&sp->so_flags))
    				continue;
    			atomic_inc(&sp->so_count);
    			spin_unlock(&clp->cl_lock);
    			rcu_read_unlock();
    
    			status = nfs4_reclaim_open_state(sp, ops);
    			if (status < 0) {
    				set_bit(ops->owner_flag_bit, &sp->so_flags);
    				nfs4_put_state_owner(sp);
    				return nfs4_recovery_handle_error(clp, status);
    			}
    
    
    			nfs4_put_state_owner(sp);
    
    	return status;
    }
    
    static int nfs4_check_lease(struct nfs_client *clp)
    {
    	struct rpc_cred *cred;
    
    	const struct nfs4_state_maintenance_ops *ops =
    		clp->cl_mvops->state_renewal_ops;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Is the client already known to have an expired lease? */
    	if (test_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state))
    		return 0;
    
    	spin_lock(&clp->cl_lock);
    	cred = ops->get_state_renewal_cred_locked(clp);
    	spin_unlock(&clp->cl_lock);
    
    	if (cred == NULL) {
    		cred = nfs4_get_setclientid_cred(clp);
    
    	status = ops->renew_lease(clp, cred);
    
    	return nfs4_recovery_handle_error(clp, status);
    
    /* Set NFS4CLNT_LEASE_EXPIRED and reclaim reboot state for all v4.0 errors
     * and for recoverable errors on EXCHANGE_ID for v4.1
    
     */
    static int nfs4_handle_reclaim_lease_error(struct nfs_client *clp, int status)
    {
    	switch (status) {
    
    	case -NFS4ERR_SEQ_MISORDERED:
    		if (test_and_set_bit(NFS4CLNT_PURGE_STATE, &clp->cl_state))
    			return -ESERVERFAULT;
    		/* Lease confirmation error: retry after purging the lease */
    		ssleep(1);
    
    		clear_bit(NFS4CLNT_LEASE_CONFIRM, &clp->cl_state);
    		break;
    
    	case -NFS4ERR_STALE_CLIENTID:
    		clear_bit(NFS4CLNT_LEASE_CONFIRM, &clp->cl_state);
    
    		nfs4_state_clear_reclaim_reboot(clp);
    		nfs4_state_start_reclaim_reboot(clp);
    
    	case -NFS4ERR_CLID_INUSE:
    		pr_err("NFS: Server %s reports our clientid is in use\n",
    			clp->cl_hostname);
    		nfs_mark_client_ready(clp, -EPERM);
    		clear_bit(NFS4CLNT_LEASE_CONFIRM, &clp->cl_state);
    		return -EPERM;
    
    	case -EACCES:
    		if (clp->cl_machine_cred == NULL)
    			return -EACCES;
    		/* Handle case where the user hasn't set up machine creds */
    		nfs4_clear_machine_cred(clp);
    	case -NFS4ERR_DELAY:
    	case -ETIMEDOUT:
    	case -EAGAIN:
    		ssleep(1);
    		break;
    
    	case -NFS4ERR_MINOR_VERS_MISMATCH:
    		if (clp->cl_cons_state == NFS_CS_SESSION_INITING)
    			nfs_mark_client_ready(clp, -EPROTONOSUPPORT);
    
    		dprintk("%s: exit with error %d for server %s\n",
    				__func__, -EPROTONOSUPPORT, clp->cl_hostname);
    
    		return -EPROTONOSUPPORT;
    	case -EKEYEXPIRED:
    		nfs4_warn_keyexpired(clp->cl_hostname);
    	case -NFS4ERR_NOT_SAME: /* FixMe: implement recovery
    				 * in nfs4_exchange_id */
    	default:
    
    		dprintk("%s: exit with error %d for server %s\n", __func__,
    				status, clp->cl_hostname);
    
    		return status;
    	}
    	set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    
    	dprintk("%s: handled error %d for server %s\n", __func__, status,
    			clp->cl_hostname);
    
    static int nfs4_establish_lease(struct nfs_client *clp)
    
    {
    	struct rpc_cred *cred;
    
    	const struct nfs4_state_recovery_ops *ops =
    		clp->cl_mvops->reboot_recovery_ops;
    
    	cred = ops->get_clid_cred(clp);
    
    	if (cred == NULL)
    		return -ENOENT;
    	status = ops->establish_clid(clp, cred);
    	put_rpccred(cred);
    	if (status != 0)
    
    		return status;
    	pnfs_destroy_all_layouts(clp);
    	return 0;
    }
    
    
    /*
     * Returns zero or a negative errno.  NFS4ERR values are converted
     * to local errno values.
     */
    
    static int nfs4_reclaim_lease(struct nfs_client *clp)
    {
    	int status;
    
    	status = nfs4_establish_lease(clp);
    	if (status < 0)
    		return nfs4_handle_reclaim_lease_error(clp, status);
    	if (test_and_clear_bit(NFS4CLNT_SERVER_SCOPE_MISMATCH, &clp->cl_state))
    		nfs4_state_start_reclaim_nograce(clp);
    	if (!test_bit(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state))
    		set_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state);
    	clear_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state);
    	clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    	return 0;
    }
    
    static int nfs4_purge_lease(struct nfs_client *clp)
    {
    	int status;
    
    	status = nfs4_establish_lease(clp);
    	if (status < 0)
    
    		return nfs4_handle_reclaim_lease_error(clp, status);
    
    	clear_bit(NFS4CLNT_PURGE_STATE, &clp->cl_state);
    	set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
    	nfs4_state_start_reclaim_nograce(clp);
    
    /**
     * nfs4_discover_server_trunking - Detect server IP address trunking
     *
     * @clp: nfs_client under test
     * @result: OUT: found nfs_client, or clp
     *
     * Returns zero or a negative errno.  If zero is returned,
     * an nfs_client pointer is planted in "result".
     *
     * Note: since we are invoked in process context, and
     * not from inside the state manager, we cannot use
     * nfs4_handle_reclaim_lease_error().
     */
    int nfs4_discover_server_trunking(struct nfs_client *clp,
    				  struct nfs_client **result)
    {
    	const struct nfs4_state_recovery_ops *ops =
    				clp->cl_mvops->reboot_recovery_ops;
    	rpc_authflavor_t *flavors, flav, save;
    	struct rpc_clnt *clnt;
    	struct rpc_cred *cred;
    	int i, len, status;
    
    	dprintk("NFS: %s: testing '%s'\n", __func__, clp->cl_hostname);
    
    	len = NFS_MAX_SECFLAVORS;
    	flavors = kcalloc(len, sizeof(*flavors), GFP_KERNEL);
    	if (flavors == NULL) {
    		status = -ENOMEM;
    		goto out;
    	}
    	len = rpcauth_list_flavors(flavors, len);
    	if (len < 0) {
    		status = len;
    		goto out_free;
    	}
    	clnt = clp->cl_rpcclient;
    	save = clnt->cl_auth->au_flavor;
    	i = 0;
    
    	mutex_lock(&nfs_clid_init_mutex);
    	status  = -ENOENT;
    again:
    	cred = ops->get_clid_cred(clp);
    	if (cred == NULL)
    		goto out_unlock;
    
    	status = ops->detect_trunking(clp, result, cred);
    	put_rpccred(cred);
    	switch (status) {
    	case 0:
    		break;
    
    	case -EACCES:
    		if (clp->cl_machine_cred == NULL)
    			break;
    		/* Handle case where the user hasn't set up machine creds */
    		nfs4_clear_machine_cred(clp);
    	case -NFS4ERR_DELAY:
    	case -ETIMEDOUT:
    	case -EAGAIN:
    		ssleep(1);
    		dprintk("NFS: %s after status %d, retrying\n",
    			__func__, status);
    		goto again;
    
    	case -NFS4ERR_CLID_INUSE:
    	case -NFS4ERR_WRONGSEC:
    		status = -EPERM;
    		if (i >= len)
    			break;
    
    		flav = flavors[i++];
    		if (flav == save)
    			flav = flavors[i++];
    		clnt = rpc_clone_client_set_auth(clnt, flav);
    		if (IS_ERR(clnt)) {
    			status = PTR_ERR(clnt);
    			break;
    		}
    		clp->cl_rpcclient = clnt;
    		goto again;
    
    	case -NFS4ERR_MINOR_VERS_MISMATCH:
    		status = -EPROTONOSUPPORT;
    		break;
    
    	case -EKEYEXPIRED:
    		nfs4_warn_keyexpired(clp->cl_hostname);
    	case -NFS4ERR_NOT_SAME: /* FixMe: implement recovery
    				 * in nfs4_exchange_id */
    		status = -EKEYEXPIRED;
    	}
    
    out_unlock:
    	mutex_unlock(&nfs_clid_init_mutex);
    out_free:
    	kfree(flavors);
    out:
    	dprintk("NFS: %s: status = %d\n", __func__, status);
    	return status;
    }
    
    
    #ifdef CONFIG_NFS_V4_1
    
    void nfs4_schedule_session_recovery(struct nfs4_session *session, int err)
    
    	struct nfs_client *clp = session->clp;
    
    
    	switch (err) {
    	default:
    		set_bit(NFS4CLNT_SESSION_RESET, &clp->cl_state);
    		break;
    	case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    		set_bit(NFS4CLNT_BIND_CONN_TO_SESSION, &clp->cl_state);
    	}
    
    	nfs4_schedule_lease_recovery(clp);
    
    EXPORT_SYMBOL_GPL(nfs4_schedule_session_recovery);
    
    static void nfs41_ping_server(struct nfs_client *clp)
    {
    	/* Use CHECK_LEASE to ping the server with a SEQUENCE */
    	set_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state);
    	nfs4_schedule_state_manager(clp);
    }
    
    void nfs41_server_notify_target_slotid_update(struct nfs_client *clp)
    {
    	nfs41_ping_server(clp);
    }
    
    
    static void nfs4_reset_all_state(struct nfs_client *clp)
    {
    	if (test_and_set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state) == 0) {
    
    		set_bit(NFS4CLNT_PURGE_STATE, &clp->cl_state);
    
    		clear_bit(NFS4CLNT_LEASE_CONFIRM, &clp->cl_state);
    
    		nfs4_state_start_reclaim_nograce(clp);
    
    		dprintk("%s: scheduling reset of all state for server %s!\n",
    				__func__, clp->cl_hostname);
    
    		nfs4_schedule_state_manager(clp);
    
    	}
    }
    
    static void nfs41_handle_server_reboot(struct nfs_client *clp)
    {
    	if (test_and_set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state) == 0) {
    		nfs4_state_start_reclaim_reboot(clp);
    
    		dprintk("%s: server %s rebooted!\n", __func__,
    				clp->cl_hostname);
    
    		nfs4_schedule_state_manager(clp);
    
    	}
    }
    
    static void nfs41_handle_state_revoked(struct nfs_client *clp)
    {
    	nfs4_reset_all_state(clp);
    
    	dprintk("%s: state revoked on server %s\n", __func__, clp->cl_hostname);
    
    }
    
    static void nfs41_handle_recallable_state_revoked(struct nfs_client *clp)
    {
    	/* This will need to handle layouts too */
    	nfs_expire_all_delegations(clp);
    
    	dprintk("%s: Recallable state revoked on server %s!\n", __func__,
    			clp->cl_hostname);
    
    static void nfs41_handle_backchannel_fault(struct nfs_client *clp)