+static int update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, nfs4_stateid *delegation, int open_flags)
+{
+ struct nfs_inode *nfsi = NFS_I(state->inode);
+ struct nfs_delegation *deleg_cur;
+ int ret = 0;
+
+ open_flags &= (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 & open_flags) != open_flags)
+ 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;
+
+ __update_open_stateid(state, open_stateid, &deleg_cur->stateid, open_flags);
+ 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, open_flags);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+
+static void nfs4_return_incompatible_delegation(struct inode *inode, mode_t open_flags)
+{
+ struct nfs_delegation *delegation;
+
+ rcu_read_lock();
+ delegation = rcu_dereference(NFS_I(inode)->delegation);
+ if (delegation == NULL || (delegation->type & open_flags) == open_flags) {
+ rcu_read_unlock();
+ return;
+ }
+ rcu_read_unlock();
+ nfs_inode_return_delegation(inode);
+}
+
+static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
+{
+ struct nfs4_state *state = opendata->state;
+ struct nfs_inode *nfsi = NFS_I(state->inode);
+ struct nfs_delegation *delegation;
+ int open_mode = opendata->o_arg.open_flags & (FMODE_READ|FMODE_WRITE|O_EXCL);
+ nfs4_stateid stateid;
+ int ret = -EAGAIN;
+
+ for (;;) {
+ if (can_open_cached(state, open_mode)) {
+ spin_lock(&state->owner->so_lock);
+ if (can_open_cached(state, open_mode)) {
+ update_open_stateflags(state, open_mode);
+ spin_unlock(&state->owner->so_lock);
+ goto out_return_state;
+ }
+ spin_unlock(&state->owner->so_lock);
+ }
+ rcu_read_lock();
+ delegation = rcu_dereference(nfsi->delegation);
+ if (delegation == NULL ||
+ !can_open_delegated(delegation, open_mode)) {
+ rcu_read_unlock();
+ break;
+ }
+ /* Save the delegation */
+ memcpy(stateid.data, delegation->stateid.data, sizeof(stateid.data));
+ rcu_read_unlock();
+ ret = nfs_may_open(state->inode, state->owner->so_cred, open_mode);
+ if (ret != 0)
+ goto out;
+ ret = -EAGAIN;
+
+ /* Try to update the stateid using the delegation */
+ if (update_open_stateid(state, NULL, &stateid, open_mode))
+ goto out_return_state;
+ }
+out:
+ return ERR_PTR(ret);
+out_return_state:
+ atomic_inc(&state->count);
+ return state;
+}
+