X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=fs%2Fnfs%2Fnfs4state.c;h=3cc88a5d9881932e2c7be76c1b297811c3dfcb20;hb=e598d843c08a7ab6bdfa8098de49afb017fc6c6a;hp=23834c8fb740d587fecf91b16372b822d9a78c77;hpb=faf5f49c2d9c0af2847837c232a432cc146e203b;p=safe%2Fjmp%2Flinux-2.6 diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c index 23834c8..3cc88a5 100644 --- a/fs/nfs/nfs4state.c +++ b/fs/nfs/nfs4state.c @@ -38,220 +38,234 @@ * subsequent patch. */ -#include +#include #include #include #include #include +#include +#include +#include #include #include #include "nfs4_fs.h" #include "callback.h" #include "delegation.h" +#include "internal.h" #define OPENOWNER_POOL_SIZE 8 const nfs4_stateid zero_stateid; -static DEFINE_SPINLOCK(state_spinlock); static LIST_HEAD(nfs4_clientid_list); -static void nfs4_recover_state(void *); - -void -init_nfsv4_state(struct nfs_server *server) +static int nfs4_init_client(struct nfs_client *clp, struct rpc_cred *cred) { - server->nfs4_state = NULL; - INIT_LIST_HEAD(&server->nfs4_siblings); + int status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, + nfs_callback_tcpport, cred); + if (status == 0) + status = nfs4_proc_setclientid_confirm(clp, cred); + if (status == 0) + nfs4_schedule_state_renewal(clp); + return status; } -void -destroy_nfsv4_state(struct nfs_server *server) +static struct rpc_cred *nfs4_get_machine_cred_locked(struct nfs_client *clp) { - if (server->mnt_path) { - kfree(server->mnt_path); - server->mnt_path = NULL; - } - if (server->nfs4_state) { - nfs4_put_client(server->nfs4_state); - server->nfs4_state = NULL; - } + struct rpc_cred *cred = NULL; + + if (clp->cl_machine_cred != NULL) + cred = get_rpccred(clp->cl_machine_cred); + return cred; } -/* - * nfs4_get_client(): returns an empty client structure - * nfs4_put_client(): drops reference to client structure - * - * Since these are allocated/deallocated very rarely, we don't - * bother putting them in a slab cache... - */ -static struct nfs4_client * -nfs4_alloc_client(struct in_addr *addr) +static void nfs4_clear_machine_cred(struct nfs_client *clp) { - struct nfs4_client *clp; + struct rpc_cred *cred; - if (nfs_callback_up() < 0) - return NULL; - if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL)) == NULL) { - nfs_callback_down(); - return NULL; - } - memset(clp, 0, sizeof(*clp)); - memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr)); - init_rwsem(&clp->cl_sem); - INIT_LIST_HEAD(&clp->cl_delegations); - INIT_LIST_HEAD(&clp->cl_state_owners); - INIT_LIST_HEAD(&clp->cl_unused); - spin_lock_init(&clp->cl_lock); - atomic_set(&clp->cl_count, 1); - INIT_WORK(&clp->cl_recoverd, nfs4_recover_state, clp); - INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp); - INIT_LIST_HEAD(&clp->cl_superblocks); - init_waitqueue_head(&clp->cl_waitq); - rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS4 client"); - clp->cl_rpcclient = ERR_PTR(-EINVAL); - clp->cl_boot_time = CURRENT_TIME; - clp->cl_state = 1 << NFS4CLNT_OK; - return clp; + spin_lock(&clp->cl_lock); + cred = clp->cl_machine_cred; + clp->cl_machine_cred = NULL; + spin_unlock(&clp->cl_lock); + if (cred != NULL) + put_rpccred(cred); } -static void -nfs4_free_client(struct nfs4_client *clp) +struct rpc_cred *nfs4_get_renew_cred_locked(struct nfs_client *clp) { struct nfs4_state_owner *sp; + struct rb_node *pos; + struct rpc_cred *cred = NULL; - while (!list_empty(&clp->cl_unused)) { - sp = list_entry(clp->cl_unused.next, - struct nfs4_state_owner, - so_list); - list_del(&sp->so_list); - kfree(sp); - } - BUG_ON(!list_empty(&clp->cl_state_owners)); - if (clp->cl_cred) - put_rpccred(clp->cl_cred); - nfs_idmap_delete(clp); - if (!IS_ERR(clp->cl_rpcclient)) - rpc_shutdown_client(clp->cl_rpcclient); - kfree(clp); - nfs_callback_down(); -} - -static struct nfs4_client *__nfs4_find_client(struct in_addr *addr) -{ - struct nfs4_client *clp; - list_for_each_entry(clp, &nfs4_clientid_list, cl_servers) { - if (memcmp(&clp->cl_addr, addr, sizeof(clp->cl_addr)) == 0) { - atomic_inc(&clp->cl_count); - return clp; - } + for (pos = rb_first(&clp->cl_state_owners); pos != NULL; pos = rb_next(pos)) { + sp = rb_entry(pos, struct nfs4_state_owner, so_client_node); + if (list_empty(&sp->so_states)) + continue; + cred = get_rpccred(sp->so_cred); + break; } - return NULL; + return cred; } -struct nfs4_client *nfs4_find_client(struct in_addr *addr) +static struct rpc_cred *nfs4_get_renew_cred(struct nfs_client *clp) { - struct nfs4_client *clp; - spin_lock(&state_spinlock); - clp = __nfs4_find_client(addr); - spin_unlock(&state_spinlock); - return clp; + struct rpc_cred *cred; + + spin_lock(&clp->cl_lock); + cred = nfs4_get_renew_cred_locked(clp); + spin_unlock(&clp->cl_lock); + return cred; } -struct nfs4_client * -nfs4_get_client(struct in_addr *addr) +static struct rpc_cred *nfs4_get_setclientid_cred(struct nfs_client *clp) { - struct nfs4_client *clp, *new = NULL; + struct nfs4_state_owner *sp; + struct rb_node *pos; + struct rpc_cred *cred; + + spin_lock(&clp->cl_lock); + cred = nfs4_get_machine_cred_locked(clp); + if (cred != NULL) + goto out; + pos = rb_first(&clp->cl_state_owners); + if (pos != NULL) { + sp = rb_entry(pos, struct nfs4_state_owner, so_client_node); + cred = get_rpccred(sp->so_cred); + } +out: + spin_unlock(&clp->cl_lock); + return cred; +} - spin_lock(&state_spinlock); +static void nfs_alloc_unique_id(struct rb_root *root, struct nfs_unique_id *new, + __u64 minval, int maxbits) +{ + struct rb_node **p, *parent; + struct nfs_unique_id *pos; + __u64 mask = ~0ULL; + + if (maxbits < 64) + mask = (1ULL << maxbits) - 1ULL; + + /* Ensure distribution is more or less flat */ + get_random_bytes(&new->id, sizeof(new->id)); + new->id &= mask; + if (new->id < minval) + new->id += minval; +retry: + p = &root->rb_node; + parent = NULL; + + while (*p != NULL) { + parent = *p; + pos = rb_entry(parent, struct nfs_unique_id, rb_node); + + if (new->id < pos->id) + p = &(*p)->rb_left; + else if (new->id > pos->id) + p = &(*p)->rb_right; + else + goto id_exists; + } + rb_link_node(&new->rb_node, parent, p); + rb_insert_color(&new->rb_node, root); + return; +id_exists: for (;;) { - clp = __nfs4_find_client(addr); - if (clp != NULL) - break; - clp = new; - if (clp != NULL) { - list_add(&clp->cl_servers, &nfs4_clientid_list); - new = NULL; + new->id++; + if (new->id < minval || (new->id & mask) != new->id) { + new->id = minval; break; } - spin_unlock(&state_spinlock); - new = nfs4_alloc_client(addr); - spin_lock(&state_spinlock); - if (new == NULL) + parent = rb_next(parent); + if (parent == NULL) + break; + pos = rb_entry(parent, struct nfs_unique_id, rb_node); + if (new->id < pos->id) break; } - spin_unlock(&state_spinlock); - if (new) - nfs4_free_client(new); - return clp; + goto retry; } -void -nfs4_put_client(struct nfs4_client *clp) +static void nfs_free_unique_id(struct rb_root *root, struct nfs_unique_id *id) { - if (!atomic_dec_and_lock(&clp->cl_count, &state_spinlock)) - return; - list_del(&clp->cl_servers); - spin_unlock(&state_spinlock); - BUG_ON(!list_empty(&clp->cl_superblocks)); - wake_up_all(&clp->cl_waitq); - rpc_wake_up(&clp->cl_rpcwaitq); - nfs4_kill_renewd(clp); - nfs4_free_client(clp); + rb_erase(&id->rb_node, root); } -static int __nfs4_init_client(struct nfs4_client *clp) +static struct nfs4_state_owner * +nfs4_find_state_owner(struct nfs_server *server, struct rpc_cred *cred) { - int status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, nfs_callback_tcpport); - if (status == 0) - status = nfs4_proc_setclientid_confirm(clp); - if (status == 0) - nfs4_schedule_state_renewal(clp); - return status; -} + struct nfs_client *clp = server->nfs_client; + struct rb_node **p = &clp->cl_state_owners.rb_node, + *parent = NULL; + struct nfs4_state_owner *sp, *res = NULL; -int nfs4_init_client(struct nfs4_client *clp) -{ - return nfs4_map_errors(__nfs4_init_client(clp)); -} + while (*p != NULL) { + parent = *p; + sp = rb_entry(parent, struct nfs4_state_owner, so_client_node); -u32 -nfs4_alloc_lockowner_id(struct nfs4_client *clp) -{ - return clp->cl_lockowner_id ++; + if (server < sp->so_server) { + p = &parent->rb_left; + continue; + } + if (server > sp->so_server) { + p = &parent->rb_right; + continue; + } + if (cred < sp->so_cred) + p = &parent->rb_left; + else if (cred > sp->so_cred) + p = &parent->rb_right; + else { + atomic_inc(&sp->so_count); + res = sp; + break; + } + } + return res; } static struct nfs4_state_owner * -nfs4_client_grab_unused(struct nfs4_client *clp, struct rpc_cred *cred) +nfs4_insert_state_owner(struct nfs_client *clp, struct nfs4_state_owner *new) { - struct nfs4_state_owner *sp = NULL; + struct rb_node **p = &clp->cl_state_owners.rb_node, + *parent = NULL; + struct nfs4_state_owner *sp; - if (!list_empty(&clp->cl_unused)) { - sp = list_entry(clp->cl_unused.next, struct nfs4_state_owner, so_list); - atomic_inc(&sp->so_count); - sp->so_cred = cred; - list_move(&sp->so_list, &clp->cl_state_owners); - clp->cl_nunused--; + while (*p != NULL) { + parent = *p; + sp = rb_entry(parent, struct nfs4_state_owner, so_client_node); + + if (new->so_server < sp->so_server) { + p = &parent->rb_left; + continue; + } + if (new->so_server > sp->so_server) { + p = &parent->rb_right; + continue; + } + if (new->so_cred < sp->so_cred) + p = &parent->rb_left; + else if (new->so_cred > sp->so_cred) + p = &parent->rb_right; + else { + atomic_inc(&sp->so_count); + return sp; + } } - return sp; + nfs_alloc_unique_id(&clp->cl_openowner_id, &new->so_owner_id, 1, 64); + rb_link_node(&new->so_client_node, parent, p); + rb_insert_color(&new->so_client_node, &clp->cl_state_owners); + return new; } -static struct nfs4_state_owner * -nfs4_find_state_owner(struct nfs4_client *clp, struct rpc_cred *cred) +static void +nfs4_remove_state_owner(struct nfs_client *clp, struct nfs4_state_owner *sp) { - struct nfs4_state_owner *sp, *res = NULL; - - list_for_each_entry(sp, &clp->cl_state_owners, so_list) { - if (sp->so_cred != cred) - continue; - atomic_inc(&sp->so_count); - /* Move to the head of the list */ - list_move(&sp->so_list, &clp->cl_state_owners); - res = sp; - break; - } - return res; + if (!RB_EMPTY_NODE(&sp->so_client_node)) + rb_erase(&sp->so_client_node, &clp->cl_state_owners); + nfs_free_unique_id(&clp->cl_openowner_id, &sp->so_owner_id); } /* @@ -267,6 +281,7 @@ nfs4_alloc_state_owner(void) sp = kzalloc(sizeof(*sp),GFP_KERNEL); if (!sp) return NULL; + spin_lock_init(&sp->so_lock); INIT_LIST_HEAD(&sp->so_states); INIT_LIST_HEAD(&sp->so_delegations); rpc_init_wait_queue(&sp->so_sequence.wait, "Seqid_waitqueue"); @@ -277,13 +292,17 @@ nfs4_alloc_state_owner(void) return sp; } -void +static void nfs4_drop_state_owner(struct nfs4_state_owner *sp) { - struct nfs4_client *clp = sp->so_client; - spin_lock(&clp->cl_lock); - list_del_init(&sp->so_list); - spin_unlock(&clp->cl_lock); + if (!RB_EMPTY_NODE(&sp->so_client_node)) { + struct nfs_client *clp = sp->so_client; + + spin_lock(&clp->cl_lock); + rb_erase(&sp->so_client_node, &clp->cl_state_owners); + RB_CLEAR_NODE(&sp->so_client_node); + spin_unlock(&clp->cl_lock); + } } /* @@ -292,30 +311,30 @@ nfs4_drop_state_owner(struct nfs4_state_owner *sp) */ struct nfs4_state_owner *nfs4_get_state_owner(struct nfs_server *server, struct rpc_cred *cred) { - struct nfs4_client *clp = server->nfs4_state; + struct nfs_client *clp = server->nfs_client; struct nfs4_state_owner *sp, *new; - get_rpccred(cred); - new = nfs4_alloc_state_owner(); spin_lock(&clp->cl_lock); - sp = nfs4_find_state_owner(clp, cred); - if (sp == NULL) - sp = nfs4_client_grab_unused(clp, cred); - if (sp == NULL && new != NULL) { - list_add(&new->so_list, &clp->cl_state_owners); - new->so_client = clp; - new->so_id = nfs4_alloc_lockowner_id(clp); - new->so_cred = cred; - sp = new; - new = NULL; - } + sp = nfs4_find_state_owner(server, cred); spin_unlock(&clp->cl_lock); - if (new) - kfree(new); if (sp != NULL) return sp; - put_rpccred(cred); - return NULL; + new = nfs4_alloc_state_owner(); + if (new == NULL) + return NULL; + new->so_client = clp; + new->so_server = server; + new->so_cred = cred; + spin_lock(&clp->cl_lock); + sp = nfs4_insert_state_owner(clp, new); + spin_unlock(&clp->cl_lock); + if (sp == new) + get_rpccred(cred); + else { + rpc_destroy_wait_queue(&new->so_sequence.wait); + kfree(new); + } + return sp; } /* @@ -324,24 +343,14 @@ struct nfs4_state_owner *nfs4_get_state_owner(struct nfs_server *server, struct */ void nfs4_put_state_owner(struct nfs4_state_owner *sp) { - struct nfs4_client *clp = sp->so_client; + struct nfs_client *clp = sp->so_client; struct rpc_cred *cred = sp->so_cred; if (!atomic_dec_and_lock(&sp->so_count, &clp->cl_lock)) return; - if (clp->cl_nunused >= OPENOWNER_POOL_SIZE) - goto out_free; - if (list_empty(&sp->so_list)) - goto out_free; - list_move(&sp->so_list, &clp->cl_unused); - clp->cl_nunused++; - spin_unlock(&clp->cl_lock); - put_rpccred(cred); - cred = NULL; - return; -out_free: - list_del(&sp->so_list); + nfs4_remove_state_owner(clp, sp); spin_unlock(&clp->cl_lock); + rpc_destroy_wait_queue(&sp->so_sequence.wait); put_rpccred(cred); kfree(sp); } @@ -351,44 +360,29 @@ nfs4_alloc_open_state(void) { struct nfs4_state *state; - state = kmalloc(sizeof(*state), GFP_KERNEL); + state = kzalloc(sizeof(*state), GFP_KERNEL); if (!state) return NULL; - state->state = 0; - state->nreaders = 0; - state->nwriters = 0; - state->flags = 0; - memset(state->stateid.data, 0, sizeof(state->stateid.data)); atomic_set(&state->count, 1); INIT_LIST_HEAD(&state->lock_states); spin_lock_init(&state->state_lock); + seqlock_init(&state->seqlock); return state; } -static struct nfs4_state * -__nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode) +void +nfs4_state_set_mode_locked(struct nfs4_state *state, mode_t mode) { - struct nfs_inode *nfsi = NFS_I(inode); - struct nfs4_state *state; - - mode &= (FMODE_READ|FMODE_WRITE); - list_for_each_entry(state, &nfsi->open_states, inode_states) { - if (state->owner->so_cred != cred) - continue; - if ((mode & FMODE_READ) != 0 && state->nreaders == 0) - continue; - if ((mode & FMODE_WRITE) != 0 && state->nwriters == 0) - continue; - if ((state->state & mode) != mode) - continue; - atomic_inc(&state->count); - if (mode & FMODE_READ) - state->nreaders++; + if (state->state == mode) + return; + /* NB! List reordering - see the reclaim code for why. */ + if ((mode & FMODE_WRITE) != (state->state & FMODE_WRITE)) { if (mode & FMODE_WRITE) - state->nwriters++; - return state; + list_move(&state->open_states, &state->owner->so_states); + else + list_move_tail(&state->open_states, &state->owner->so_states); } - return NULL; + state->state = mode; } static struct nfs4_state * @@ -398,28 +392,14 @@ __nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner) struct nfs4_state *state; list_for_each_entry(state, &nfsi->open_states, inode_states) { - /* Is this in the process of being freed? */ - if (state->nreaders == 0 && state->nwriters == 0) + if (state->owner != owner) continue; - if (state->owner == owner) { - atomic_inc(&state->count); + if (atomic_inc_not_zero(&state->count)) return state; - } } return NULL; } -struct nfs4_state * -nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode) -{ - struct nfs4_state *state; - - spin_lock(&inode->i_lock); - state = __nfs4_find_state(inode, cred, mode); - spin_unlock(&inode->i_lock); - return state; -} - static void nfs4_free_open_state(struct nfs4_state *state) { @@ -438,20 +418,23 @@ nfs4_get_open_state(struct inode *inode, struct nfs4_state_owner *owner) if (state) goto out; new = nfs4_alloc_open_state(); + spin_lock(&owner->so_lock); spin_lock(&inode->i_lock); state = __nfs4_find_state_byowner(inode, owner); if (state == NULL && new != NULL) { state = new; - /* Note: The reclaim code dictates that we add stateless - * and read-only stateids to the end of the list */ - list_add_tail(&state->open_states, &owner->so_states); state->owner = owner; atomic_inc(&owner->so_count); list_add(&state->inode_states, &nfsi->open_states); state->inode = igrab(inode); spin_unlock(&inode->i_lock); + /* Note: The reclaim code dictates that we add stateless + * and read-only stateids to the end of the list */ + list_add_tail(&state->open_states, &owner->so_states); + spin_unlock(&owner->so_lock); } else { spin_unlock(&inode->i_lock); + spin_unlock(&owner->so_lock); if (new) nfs4_free_open_state(new); } @@ -468,14 +451,14 @@ void nfs4_put_open_state(struct nfs4_state *state) struct inode *inode = state->inode; struct nfs4_state_owner *owner = state->owner; - if (!atomic_dec_and_lock(&state->count, &inode->i_lock)) + if (!atomic_dec_and_lock(&state->count, &owner->so_lock)) return; - if (!list_empty(&state->inode_states)) - list_del(&state->inode_states); - spin_unlock(&inode->i_lock); + spin_lock(&inode->i_lock); + list_del(&state->inode_states); list_del(&state->open_states); + spin_unlock(&inode->i_lock); + spin_unlock(&owner->so_lock); iput(inode); - BUG_ON (state->state != 0); nfs4_free_open_state(state); nfs4_put_state_owner(owner); } @@ -483,44 +466,58 @@ void nfs4_put_open_state(struct nfs4_state *state) /* * Close the current file. */ -void nfs4_close_state(struct nfs4_state *state, mode_t mode) +static void __nfs4_close(struct path *path, struct nfs4_state *state, mode_t mode, int wait) { - struct inode *inode = state->inode; struct nfs4_state_owner *owner = state->owner; + int call_close = 0; int newstate; atomic_inc(&owner->so_count); /* Protect against nfs4_find_state() */ - spin_lock(&inode->i_lock); - if (mode & FMODE_READ) - state->nreaders--; - if (mode & FMODE_WRITE) - state->nwriters--; - if (state->nwriters == 0) { - if (state->nreaders == 0) - list_del_init(&state->inode_states); - /* See reclaim code */ - list_move_tail(&state->open_states, &owner->so_states); + spin_lock(&owner->so_lock); + switch (mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_READ: + state->n_rdonly--; + break; + case FMODE_WRITE: + state->n_wronly--; + break; + case FMODE_READ|FMODE_WRITE: + state->n_rdwr--; } - spin_unlock(&inode->i_lock); - newstate = 0; - if (state->state != 0) { - if (state->nreaders) - newstate |= FMODE_READ; - if (state->nwriters) - newstate |= FMODE_WRITE; - if (state->state == newstate) - goto out; - if (test_bit(NFS_DELEGATED_STATE, &state->flags)) { - state->state = newstate; - goto out; + newstate = FMODE_READ|FMODE_WRITE; + if (state->n_rdwr == 0) { + if (state->n_rdonly == 0) { + newstate &= ~FMODE_READ; + call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags); + call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags); } - if (nfs4_do_close(inode, state, newstate) == 0) - return; + if (state->n_wronly == 0) { + newstate &= ~FMODE_WRITE; + call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags); + call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags); + } + if (newstate == 0) + clear_bit(NFS_DELEGATED_STATE, &state->flags); } -out: - nfs4_put_open_state(state); - nfs4_put_state_owner(owner); + nfs4_state_set_mode_locked(state, newstate); + spin_unlock(&owner->so_lock); + + if (!call_close) { + nfs4_put_open_state(state); + nfs4_put_state_owner(owner); + } else + nfs4_do_close(path, state, wait); +} + +void nfs4_close_state(struct path *path, struct nfs4_state *state, mode_t mode) +{ + __nfs4_close(path, state, mode, 0); +} + +void nfs4_close_sync(struct path *path, struct nfs4_state *state, mode_t mode) +{ + __nfs4_close(path, state, mode, 1); } /* @@ -548,21 +545,35 @@ __nfs4_find_lock_state(struct nfs4_state *state, fl_owner_t fl_owner) static struct nfs4_lock_state *nfs4_alloc_lock_state(struct nfs4_state *state, fl_owner_t fl_owner) { struct nfs4_lock_state *lsp; - struct nfs4_client *clp = state->owner->so_client; + struct nfs_client *clp = state->owner->so_client; lsp = kzalloc(sizeof(*lsp), GFP_KERNEL); if (lsp == NULL) return NULL; - lsp->ls_seqid.sequence = &state->owner->so_sequence; + rpc_init_wait_queue(&lsp->ls_sequence.wait, "lock_seqid_waitqueue"); + spin_lock_init(&lsp->ls_sequence.lock); + INIT_LIST_HEAD(&lsp->ls_sequence.list); + lsp->ls_seqid.sequence = &lsp->ls_sequence; atomic_set(&lsp->ls_count, 1); lsp->ls_owner = fl_owner; spin_lock(&clp->cl_lock); - lsp->ls_id = nfs4_alloc_lockowner_id(clp); + nfs_alloc_unique_id(&clp->cl_lockowner_id, &lsp->ls_id, 1, 64); spin_unlock(&clp->cl_lock); INIT_LIST_HEAD(&lsp->ls_locks); return lsp; } +static void nfs4_free_lock_state(struct nfs4_lock_state *lsp) +{ + struct nfs_client *clp = lsp->ls_state->owner->so_client; + + spin_lock(&clp->cl_lock); + nfs_free_unique_id(&clp->cl_lockowner_id, &lsp->ls_id); + spin_unlock(&clp->cl_lock); + rpc_destroy_wait_queue(&lsp->ls_sequence.wait); + kfree(lsp); +} + /* * Return a compatible lock_state. If no initialized lock_state structure * exists, return an uninitialized one. @@ -592,7 +603,8 @@ static struct nfs4_lock_state *nfs4_get_lock_state(struct nfs4_state *state, fl_ return NULL; } spin_unlock(&state->state_lock); - kfree(new); + if (new != NULL) + nfs4_free_lock_state(new); return lsp; } @@ -613,7 +625,7 @@ void nfs4_put_lock_state(struct nfs4_lock_state *lsp) if (list_empty(&state->lock_states)) clear_bit(LK_STATE_IN_USE, &state->flags); spin_unlock(&state->state_lock); - kfree(lsp); + nfs4_free_lock_state(lsp); } static void nfs4_fl_copy_lock(struct file_lock *dst, struct file_lock *src) @@ -655,8 +667,12 @@ int nfs4_set_lock_state(struct nfs4_state *state, struct file_lock *fl) void nfs4_copy_stateid(nfs4_stateid *dst, struct nfs4_state *state, fl_owner_t fl_owner) { struct nfs4_lock_state *lsp; + int seq; - memcpy(dst, &state->stateid, sizeof(*dst)); + do { + seq = read_seqbegin(&state->seqlock); + memcpy(dst, &state->stateid, sizeof(*dst)); + } while (read_seqretry(&state->seqlock, seq)); if (test_bit(LK_STATE_IN_USE, &state->flags) == 0) return; @@ -670,33 +686,26 @@ void nfs4_copy_stateid(nfs4_stateid *dst, struct nfs4_state *state, fl_owner_t f struct nfs_seqid *nfs_alloc_seqid(struct nfs_seqid_counter *counter) { - struct rpc_sequence *sequence = counter->sequence; struct nfs_seqid *new; new = kmalloc(sizeof(*new), GFP_KERNEL); if (new != NULL) { new->sequence = counter; - new->task = NULL; - spin_lock(&sequence->lock); - list_add_tail(&new->list, &sequence->list); - spin_unlock(&sequence->lock); + INIT_LIST_HEAD(&new->list); } return new; } void nfs_free_seqid(struct nfs_seqid *seqid) { - struct rpc_sequence *sequence = seqid->sequence->sequence; - struct rpc_task *next = NULL; + if (!list_empty(&seqid->list)) { + struct rpc_sequence *sequence = seqid->sequence->sequence; - spin_lock(&sequence->lock); - list_del(&seqid->list); - if (!list_empty(&sequence->list)) { - next = list_entry(sequence->list.next, struct nfs_seqid, list)->task; - if (next) - rpc_wake_up_task(next); + spin_lock(&sequence->lock); + list_del(&seqid->list); + spin_unlock(&sequence->lock); + rpc_wake_up(&sequence->wait); } - spin_unlock(&sequence->lock); kfree(seqid); } @@ -705,12 +714,19 @@ void nfs_free_seqid(struct nfs_seqid *seqid) * failed with a seqid incrementing error - * see comments nfs_fs.h:seqid_mutating_error() */ -static inline void nfs_increment_seqid(int status, struct nfs_seqid *seqid) +static void nfs_increment_seqid(int status, struct nfs_seqid *seqid) { + BUG_ON(list_first_entry(&seqid->sequence->sequence->list, struct nfs_seqid, list) != seqid); switch (status) { case 0: break; case -NFS4ERR_BAD_SEQID: + if (seqid->sequence->flags & NFS_SEQID_CONFIRMED) + return; + printk(KERN_WARNING "NFS: v4 server returned a bad" + " sequence-id error on an" + " unconfirmed sequence %p!\n", + seqid->sequence); case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_STALE_STATEID: case -NFS4ERR_BAD_STATEID: @@ -734,7 +750,7 @@ void nfs_increment_open_seqid(int status, struct nfs_seqid *seqid) struct nfs4_state_owner, so_seqid); nfs4_drop_state_owner(sp); } - return nfs_increment_seqid(status, seqid); + nfs_increment_seqid(status, seqid); } /* @@ -744,7 +760,7 @@ void nfs_increment_open_seqid(int status, struct nfs_seqid *seqid) */ void nfs_increment_lock_seqid(int status, struct nfs_seqid *seqid) { - return nfs_increment_seqid(status, seqid); + nfs_increment_seqid(status, seqid); } int nfs_wait_on_sequence(struct nfs_seqid *seqid, struct rpc_task *task) @@ -753,67 +769,91 @@ int nfs_wait_on_sequence(struct nfs_seqid *seqid, struct rpc_task *task) int status = 0; spin_lock(&sequence->lock); - if (sequence->list.next != &seqid->list) { - seqid->task = task; - rpc_sleep_on(&sequence->wait, task, NULL, NULL); - status = -EAGAIN; - } + 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); + status = -EAGAIN; +unlock: spin_unlock(&sequence->lock); return status; } static int reclaimer(void *); -struct reclaimer_args { - struct nfs4_client *clp; - struct completion complete; -}; + +static inline void nfs4_clear_recover_bit(struct nfs_client *clp) +{ + smp_mb__before_clear_bit(); + clear_bit(NFS4CLNT_STATE_RECOVER, &clp->cl_state); + smp_mb__after_clear_bit(); + wake_up_bit(&clp->cl_state, NFS4CLNT_STATE_RECOVER); + rpc_wake_up(&clp->cl_rpcwaitq); +} /* * State recovery routine */ -void -nfs4_recover_state(void *data) +static void nfs4_recover_state(struct nfs_client *clp) { - struct nfs4_client *clp = (struct nfs4_client *)data; - struct reclaimer_args args = { - .clp = clp, - }; - might_sleep(); - - init_completion(&args.complete); + struct task_struct *task; - if (kernel_thread(reclaimer, &args, CLONE_KERNEL) < 0) - goto out_failed_clear; - wait_for_completion(&args.complete); - return; -out_failed_clear: - set_bit(NFS4CLNT_OK, &clp->cl_state); - wake_up_all(&clp->cl_waitq); - rpc_wake_up(&clp->cl_rpcwaitq); + __module_get(THIS_MODULE); + atomic_inc(&clp->cl_count); + task = kthread_run(reclaimer, clp, "%s-reclaim", + rpc_peeraddr2str(clp->cl_rpcclient, + RPC_DISPLAY_ADDR)); + if (!IS_ERR(task)) + return; + nfs4_clear_recover_bit(clp); + nfs_put_client(clp); + module_put(THIS_MODULE); } /* * Schedule a state recovery attempt */ -void -nfs4_schedule_state_recovery(struct nfs4_client *clp) +void nfs4_schedule_state_recovery(struct nfs_client *clp) { if (!clp) return; - if (test_and_clear_bit(NFS4CLNT_OK, &clp->cl_state)) - schedule_work(&clp->cl_recoverd); + if (!test_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state)) + set_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state); + if (test_and_set_bit(NFS4CLNT_STATE_RECOVER, &clp->cl_state) == 0) + nfs4_recover_state(clp); } -static int nfs4_reclaim_locks(struct nfs4_state_recovery_ops *ops, struct nfs4_state *state) +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(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(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state); + return 1; +} + +static int nfs4_reclaim_locks(struct nfs4_state *state, const struct nfs4_state_recovery_ops *ops) { struct inode *inode = state->inode; struct file_lock *fl; int status = 0; - for (fl = inode->i_flock; fl != 0; fl = fl->fl_next) { - if (!(fl->fl_flags & FL_POSIX)) + for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) { + if (!(fl->fl_flags & (FL_POSIX|FL_FLOCK))) continue; - if (((struct nfs_open_context *)fl->fl_file->private_data)->state != state) + if (nfs_file_open_context(fl->fl_file)->state != state) continue; status = ops->recover_lock(state, fl); if (status >= 0) @@ -821,12 +861,12 @@ static int nfs4_reclaim_locks(struct nfs4_state_recovery_ops *ops, struct nfs4_s switch (status) { default: printk(KERN_ERR "%s: unhandled error %d. Zeroing state\n", - __FUNCTION__, status); + __func__, status); case -NFS4ERR_EXPIRED: case -NFS4ERR_NO_GRACE: case -NFS4ERR_RECLAIM_BAD: case -NFS4ERR_RECLAIM_CONFLICT: - /* kill_proc(fl->fl_owner, SIGLOST, 1); */ + /* kill_proc(fl->fl_pid, SIGLOST, 1); */ break; case -NFS4ERR_STALE_CLIENTID: goto out_err; @@ -837,7 +877,7 @@ out_err: return status; } -static int nfs4_reclaim_open_state(struct nfs4_state_recovery_ops *ops, struct nfs4_state_owner *sp) +static int nfs4_reclaim_open_state(struct nfs4_state_owner *sp, const struct nfs4_state_recovery_ops *ops) { struct nfs4_state *state; struct nfs4_lock_state *lock; @@ -852,27 +892,28 @@ static int nfs4_reclaim_open_state(struct nfs4_state_recovery_ops *ops, struct n * server that doesn't support a grace period. */ list_for_each_entry(state, &sp->so_states, open_states) { + if (!test_and_clear_bit(ops->state_flag_bit, &state->flags)) + continue; if (state->state == 0) continue; status = ops->recover_open(sp, state); if (status >= 0) { - status = nfs4_reclaim_locks(ops, state); - if (status < 0) - goto out_err; - list_for_each_entry(lock, &state->lock_states, ls_locks) { - if (!(lock->ls_flags & NFS_LOCK_INITIALIZED)) - printk("%s: Lock reclaim failed!\n", - __FUNCTION__); + status = nfs4_reclaim_locks(state, ops); + if (status >= 0) { + list_for_each_entry(lock, &state->lock_states, ls_locks) { + if (!(lock->ls_flags & NFS_LOCK_INITIALIZED)) + printk("%s: Lock reclaim failed!\n", + __func__); + } + continue; } - continue; } switch (status) { default: printk(KERN_ERR "%s: unhandled error %d. Zeroing state\n", - __FUNCTION__, status); + __func__, status); case -ENOENT: - case -NFS4ERR_RECLAIM_BAD: - case -NFS4ERR_RECLAIM_CONFLICT: + case -ESTALE: /* * Open state on this file cannot be recovered * All we can do is revert to using the zero stateid. @@ -882,8 +923,13 @@ static int nfs4_reclaim_open_state(struct nfs4_state_recovery_ops *ops, struct n /* Mark the file as being 'closed' */ state->state = 0; break; + case -NFS4ERR_RECLAIM_BAD: + case -NFS4ERR_RECLAIM_CONFLICT: + nfs4_state_mark_reclaim_nograce(sp->so_client, state); + break; case -NFS4ERR_EXPIRED: case -NFS4ERR_NO_GRACE: + nfs4_state_mark_reclaim_nograce(sp->so_client, state); case -NFS4ERR_STALE_CLIENTID: goto out_err; } @@ -893,93 +939,228 @@ out_err: return status; } -static void nfs4_state_mark_reclaim(struct nfs4_client *clp) +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.counter = 0; + lock->ls_seqid.flags = 0; + lock->ls_flags &= ~NFS_LOCK_INITIALIZED; + } +} + +static void nfs4_state_mark_reclaim_helper(struct nfs_client *clp, int (*mark_reclaim)(struct nfs_client *clp, struct nfs4_state *state)) { struct nfs4_state_owner *sp; + struct rb_node *pos; struct nfs4_state *state; - struct nfs4_lock_state *lock; /* Reset all sequence ids to zero */ - list_for_each_entry(sp, &clp->cl_state_owners, so_list) { + for (pos = rb_first(&clp->cl_state_owners); pos != NULL; pos = rb_next(pos)) { + sp = rb_entry(pos, struct nfs4_state_owner, so_client_node); sp->so_seqid.counter = 0; sp->so_seqid.flags = 0; + spin_lock(&sp->so_lock); list_for_each_entry(state, &sp->so_states, open_states) { - list_for_each_entry(lock, &state->lock_states, ls_locks) { - lock->ls_seqid.counter = 0; - lock->ls_seqid.flags = 0; - lock->ls_flags &= ~NFS_LOCK_INITIALIZED; - } + if (mark_reclaim(clp, state)) + nfs4_clear_open_state(state); } + spin_unlock(&sp->so_lock); } } -static int reclaimer(void *ptr) +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_state_end_reclaim_reboot(struct nfs_client *clp) { - struct reclaimer_args *args = (struct reclaimer_args *)ptr; - struct nfs4_client *clp = args->clp; struct nfs4_state_owner *sp; - struct nfs4_state_recovery_ops *ops; - int status = 0; + struct rb_node *pos; + struct nfs4_state *state; - daemonize("%u.%u.%u.%u-reclaim", NIPQUAD(clp->cl_addr)); - allow_signal(SIGKILL); + if (!test_and_clear_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state)) + return; - atomic_inc(&clp->cl_count); - complete(&args->complete); + for (pos = rb_first(&clp->cl_state_owners); pos != NULL; pos = rb_next(pos)) { + sp = rb_entry(pos, struct nfs4_state_owner, so_client_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); + } - /* Ensure exclusive access to NFSv4 state */ - lock_kernel(); - down_write(&clp->cl_sem); - /* Are there any NFS mounts out there? */ - if (list_empty(&clp->cl_superblocks)) - goto out; -restart_loop: - status = nfs4_proc_renew(clp); - switch (status) { - case 0: + nfs_delegation_reap_unclaimed(clp); +} + +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_state_end_reclaim_nograce(struct nfs_client *clp) +{ + clear_bit(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state); +} + +static void nfs4_recovery_handle_error(struct nfs_client *clp, int error) +{ + switch (error) { case -NFS4ERR_CB_PATH_DOWN: - goto out; + set_bit(NFS4CLNT_CB_PATH_DOWN, &clp->cl_state); + break; case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_LEASE_MOVED: - ops = &nfs4_reboot_recovery_ops; + set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); + nfs4_state_start_reclaim_reboot(clp); break; - default: - ops = &nfs4_network_partition_recovery_ops; - }; - nfs4_state_mark_reclaim(clp); - status = __nfs4_init_client(clp); - if (status) - goto out_error; - /* Mark all delegations for reclaim */ - nfs_delegation_mark_reclaim(clp); + case -NFS4ERR_EXPIRED: + set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); + nfs4_state_start_reclaim_nograce(clp); + } +} + +static int nfs4_do_reclaim(struct nfs_client *clp, const struct nfs4_state_recovery_ops *ops) +{ + struct rb_node *pos; + int status = 0; + /* Note: list is protected by exclusive lock on cl->cl_sem */ - list_for_each_entry(sp, &clp->cl_state_owners, so_list) { - status = nfs4_reclaim_open_state(ops, sp); - if (status < 0) { - if (status == -NFS4ERR_NO_GRACE) { - ops = &nfs4_network_partition_recovery_ops; - status = nfs4_reclaim_open_state(ops, sp); + for (pos = rb_first(&clp->cl_state_owners); pos != NULL; pos = rb_next(pos)) { + struct nfs4_state_owner *sp = rb_entry(pos, struct nfs4_state_owner, so_client_node); + status = nfs4_reclaim_open_state(sp, ops); + if (status < 0) + break; + } + nfs4_recovery_handle_error(clp, status); + return status; +} + +static int nfs4_check_lease(struct nfs_client *clp) +{ + struct rpc_cred *cred; + int status = -NFS4ERR_EXPIRED; + + /* Are there any open files on this volume? */ + cred = nfs4_get_renew_cred(clp); + if (cred != NULL) { + /* Yes there are: try to renew the old lease */ + status = nfs4_proc_renew(clp, cred); + put_rpccred(cred); + nfs4_recovery_handle_error(clp, status); + return status; + } + + /* "reboot" to ensure we clear all state on the server */ + clp->cl_boot_time = CURRENT_TIME; + set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); + return status; +} + +static int nfs4_reclaim_lease(struct nfs_client *clp) +{ + struct rpc_cred *cred; + int status = -ENOENT; + + cred = nfs4_get_setclientid_cred(clp); + if (cred != NULL) { + status = nfs4_init_client(clp, cred); + put_rpccred(cred); + /* Handle case where the user hasn't set up machine creds */ + if (status == -EACCES && cred == clp->cl_machine_cred) { + nfs4_clear_machine_cred(clp); + status = -EAGAIN; + } + } + return status; +} + +static int reclaimer(void *ptr) +{ + struct nfs_client *clp = ptr; + int status = 0; + + allow_signal(SIGKILL); + + /* Ensure exclusive access to NFSv4 state */ + down_write(&clp->cl_sem); + while (!list_empty(&clp->cl_superblocks)) { + if (test_and_clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state)) { + /* We're going to have to re-establish a clientid */ + status = nfs4_reclaim_lease(clp); + if (status) { + set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); + if (status == -EAGAIN) + continue; + goto out_error; } + clear_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state); + } + + if (test_and_clear_bit(NFS4CLNT_CHECK_LEASE, &clp->cl_state)) { + status = nfs4_check_lease(clp); + if (status != 0) + continue; + } + + /* First recover reboot state... */ + if (test_and_clear_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state)) { + /* Note: list is protected by exclusive lock on cl->cl_sem */ + status = nfs4_do_reclaim(clp, &nfs4_reboot_recovery_ops); if (status == -NFS4ERR_STALE_CLIENTID) - goto restart_loop; - if (status == -NFS4ERR_EXPIRED) - goto restart_loop; + continue; + nfs4_state_end_reclaim_reboot(clp); + continue; + } + + /* Now recover expired state... */ + if (test_and_clear_bit(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state)) { + /* Note: list is protected by exclusive lock on cl->cl_sem */ + status = nfs4_do_reclaim(clp, &nfs4_nograce_recovery_ops); + if (status < 0) { + set_bit(NFS4CLNT_RECLAIM_NOGRACE, &clp->cl_state); + if (status == -NFS4ERR_STALE_CLIENTID) + continue; + if (status == -NFS4ERR_EXPIRED) + continue; + goto out_error; + } else + nfs4_state_end_reclaim_nograce(clp); + continue; } + break; } - nfs_delegation_reap_unclaimed(clp); out: - set_bit(NFS4CLNT_OK, &clp->cl_state); up_write(&clp->cl_sem); - unlock_kernel(); - wake_up_all(&clp->cl_waitq); - rpc_wake_up(&clp->cl_rpcwaitq); - if (status == -NFS4ERR_CB_PATH_DOWN) + if (test_and_clear_bit(NFS4CLNT_CB_PATH_DOWN, &clp->cl_state)) nfs_handle_cb_pathdown(clp); - nfs4_put_client(clp); + nfs4_clear_recover_bit(clp); + nfs_put_client(clp); + module_put_and_exit(0); return 0; out_error: - printk(KERN_WARNING "Error: state recovery failed on NFSv4 server %u.%u.%u.%u with error %d\n", - NIPQUAD(clp->cl_addr.s_addr), -status); + printk(KERN_WARNING "Error: state recovery failed on NFSv4 server %s" + " with error %d\n", clp->cl_hostname, -status); + if (test_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state)) + nfs4_state_end_reclaim_reboot(clp); goto out; }