ms-nfs41-client/daemon/recovery.c
Casey Bodley 8e310c5711 recovery: handle delegation.recalled on reclaim
while the server is required to grant us delegations we reclaim through CLAIM_PREVIOUS, it may set the flag recalled=TRUE if it's not ready to grant the delegation.  the client is then responsible for flushing modified state to the server and returning the delegation

new function nfs41_client_delegation_recovery() cleans up after delegation recovery by a) returning any delegations flagged as recalled, and b) 'forgetting' any delegations that we failed to reclaim.  this function is called under the client's state recovery lock, directly after open and delegation state is recovered

added 'try_recovery' argument to delegation_return(), allowing it to be called during client state recovery.  split out the code that removes the delegation from the client and its opens into delegation_remove(), which is what nfs41_client_delegation_recovery() uses to 'forget' a delegation

Signed-off-by: Casey Bodley <cbodley@citi.umich.edu>
2011-08-05 14:32:20 -04:00

527 lines
19 KiB
C

/* Copyright (c) 2010
* The Regents of the University of Michigan
* All Rights Reserved
*
* Permission is granted to use, copy and redistribute this software
* for noncommercial education and research purposes, so long as no
* fee is charged, and so long as the name of the University of Michigan
* is not used in any advertising or publicity pertaining to the use
* or distribution of this software without specific, written prior
* authorization. Permission to modify or otherwise create derivative
* works of this software is not granted.
*
* This software is provided as is, without representation or warranty
* of any kind either express or implied, including without limitation
* the implied warranties of merchantability, fitness for a particular
* purpose, or noninfringement. The Regents of the University of
* Michigan shall not be liable for any damages, including special,
* indirect, incidental, or consequential damages, with respect to any
* claim arising out of or in connection with the use of the software,
* even if it has been or is hereafter advised of the possibility of
* such damages.
*/
#include <time.h>
#include "recovery.h"
#include "delegation.h"
#include "nfs41_callback.h"
#include "nfs41_compound.h"
#include "nfs41_ops.h"
#include "daemon_debug.h"
/* session/client recovery uses a lock and condition variable in nfs41_client
* to prevent multiple threads from attempting to recover at the same time */
bool_t nfs41_recovery_start_or_wait(
IN nfs41_client *client)
{
bool_t status = TRUE;
EnterCriticalSection(&client->recovery.lock);
if (!client->recovery.in_recovery) {
dprintf(1, "Entering recovery mode for client %llu\n", client->clnt_id);
client->recovery.in_recovery = TRUE;
} else {
status = FALSE;
dprintf(1, "Waiting for recovery of client %llu\n", client->clnt_id);
while (client->recovery.in_recovery)
SleepConditionVariableCS(&client->recovery.cond,
&client->recovery.lock, INFINITE);
dprintf(1, "Woke up after recovery of client %llu\n", client->clnt_id);
}
LeaveCriticalSection(&client->recovery.lock);
return status;
}
void nfs41_recovery_finish(
IN nfs41_client *client)
{
EnterCriticalSection(&client->recovery.lock);
dprintf(1, "Finished recovery for client %llu\n", client->clnt_id);
client->recovery.in_recovery = FALSE;
WakeAllConditionVariable(&client->recovery.cond);
LeaveCriticalSection(&client->recovery.lock);
}
/* client state recovery for server reboot or lease expiration */
static int recover_open_grace(
IN nfs41_session *session,
IN nfs41_path_fh *parent,
IN nfs41_path_fh *file,
IN state_owner4 *owner,
IN uint32_t access,
IN uint32_t deny,
IN enum open_delegation_type4 delegate_type,
OUT stateid4 *stateid,
OUT open_delegation4 *delegation)
{
/* reclaim the open stateid with CLAIM_PREVIOUS */
open_claim4 claim;
claim.claim = CLAIM_PREVIOUS;
claim.u.prev.delegate_type = delegate_type;
return nfs41_open(session, parent, file, owner,
&claim, access, deny, OPEN4_NOCREATE, 0, 0, FALSE,
stateid, delegation, NULL);
}
static int recover_open_no_grace(
IN nfs41_session *session,
IN nfs41_path_fh *parent,
IN nfs41_path_fh *file,
IN state_owner4 *owner,
IN uint32_t access,
IN uint32_t deny,
IN enum open_delegation_type4 delegate_type,
OUT stateid4 *stateid,
OUT open_delegation4 *delegation)
{
open_claim4 claim;
/* TODO: try CLAIM_DELEGATE_PREV / CLAIM_DELEG_PREV_FH first */
/* attempt out-of-grace recovery with CLAIM_NULL */
claim.claim = CLAIM_NULL;
claim.u.null.filename = &file->name;
/* ask nicely for the delegation we had */
if (delegate_type == OPEN_DELEGATE_READ)
access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG;
else if (delegate_type == OPEN_DELEGATE_WRITE)
access |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG;
return nfs41_open(session, parent, file, owner,
&claim, access, deny, OPEN4_NOCREATE, 0, 0, FALSE,
stateid, delegation, NULL);
}
static int recover_open(
IN nfs41_session *session,
IN nfs41_open_state *open,
IN OUT bool_t *grace)
{
open_delegation4 delegation = { 0 };
stateid4 stateid;
enum open_delegation_type4 delegate_type = OPEN_DELEGATE_NONE;
int status = NFS4ERR_BADHANDLE;
/* check for an associated delegation */
AcquireSRWLockExclusive(&open->lock);
if (open->delegation.state) {
nfs41_delegation_state *deleg = open->delegation.state;
if (deleg->revoked) {
/* reclaim the delegation along with the open */
AcquireSRWLockShared(&deleg->lock);
delegate_type = deleg->state.type;
ReleaseSRWLockShared(&deleg->lock);
} else if (deleg->state.recalled) {
/* we'll need an open stateid regardless */
} else if (list_empty(&open->locks.list)) {
/* if there are locks, we need an open stateid to
* reclaim them; otherwise, the open can be delegated */
open->do_close = FALSE;
status = NFS4_OK;
}
}
ReleaseSRWLockExclusive(&open->lock);
if (status == NFS4_OK) /* use existing delegation */
goto out;
if (*grace)
status = recover_open_grace(session, &open->parent, &open->file,
&open->owner, open->share_access, open->share_deny,
delegate_type, &stateid, &delegation);
else
status = NFS4ERR_NO_GRACE;
if (status == NFS4ERR_NO_GRACE) {
*grace = FALSE;
status = recover_open_no_grace(session, &open->parent, &open->file,
&open->owner, open->share_access, open->share_deny,
delegate_type, &stateid, &delegation);
}
if (status)
goto out;
AcquireSRWLockExclusive(&open->lock);
/* update the open stateid */
memcpy(&open->stateid, &stateid, sizeof(stateid4));
open->do_close = TRUE;
if (open->delegation.state) {
nfs41_delegation_state *deleg = open->delegation.state;
if (deleg->revoked) {
/* update delegation state */
AcquireSRWLockExclusive(&deleg->lock);
if (delegation.type != OPEN_DELEGATE_READ &&
delegation.type != OPEN_DELEGATE_WRITE) {
eprintf("recover_open() got delegation type %u, "
"expected %u\n", delegation.type, deleg->state.type);
} else {
memcpy(&deleg->state, &delegation, sizeof(open_delegation4));
deleg->revoked = FALSE;
}
ReleaseSRWLockExclusive(&deleg->lock);
}
} else /* granted a new delegation? */
nfs41_delegation_granted(session, &open->parent, &open->file,
&delegation, FALSE, &open->delegation.state);
ReleaseSRWLockExclusive(&open->lock);
out:
return status;
}
static int recover_locks(
IN nfs41_session *session,
IN nfs41_open_state *open,
IN OUT bool_t *grace)
{
stateid_arg stateid;
struct list_entry *entry;
nfs41_lock_state *lock;
int status = NFS4_OK;
AcquireSRWLockExclusive(&open->lock);
/* initialize the open stateid for the first lock request */
memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4));
stateid.type = STATEID_OPEN;
stateid.open = open;
stateid.delegation = NULL;
/* recover any locks for this open */
list_for_each(entry, &open->locks.list) {
lock = list_container(entry, nfs41_lock_state, open_entry);
if (*grace)
status = nfs41_lock(session, &open->file, &open->owner,
lock->type, lock->offset, lock->length, TRUE, FALSE, &stateid);
else
status = NFS4ERR_NO_GRACE;
if (status == NFS4ERR_NO_GRACE) {
*grace = FALSE;
/* attempt out-of-grace recovery with a normal LOCK */
status = nfs41_lock(session, &open->file, &open->owner,
lock->type, lock->offset, lock->length, FALSE, FALSE, &stateid);
}
if (status == NFS4ERR_BADSESSION)
break;
}
if (status != NFS4ERR_BADSESSION) {
/* if we got a lock stateid back, save the lock with the open */
if (stateid.type == STATEID_LOCK)
memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4));
else
open->locks.stateid.seqid = 0;
}
ReleaseSRWLockExclusive(&open->lock);
return status;
}
/* delegation recovery via OPEN (requires corresponding CLOSE) */
static int recover_delegation_open(
IN nfs41_session *session,
IN nfs41_delegation_state *deleg,
IN OUT bool_t *grace)
{
state_owner4 owner;
open_delegation4 delegation = { 0 };
stateid_arg stateid;
uint32_t access = OPEN4_SHARE_ACCESS_READ;
uint32_t deny = OPEN4_SHARE_DENY_NONE;
enum open_delegation_type4 delegate_type = OPEN_DELEGATE_NONE;
int status = NFS4_OK;
/* choose the desired access mode based on delegation type */
AcquireSRWLockShared(&deleg->lock);
delegate_type = deleg->state.type;
if (delegate_type == OPEN_DELEGATE_WRITE)
access |= OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG;
else
access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG;
ReleaseSRWLockShared(&deleg->lock);
/* construct a temporary open owner by concatenating the time
* in seconds with the delegation pointer */
time((time_t*)owner.owner);
memcpy(owner.owner + sizeof(time_t), deleg, sizeof(deleg));
owner.owner_len = sizeof(time_t) + sizeof(deleg);
if (*grace)
status = recover_open_grace(session, &deleg->parent, &deleg->file,
&owner, access, deny, delegate_type, &stateid.stateid, &delegation);
else
status = NFS4ERR_NO_GRACE;
if (status == NFS4ERR_NO_GRACE) {
*grace = FALSE;
status = recover_open_no_grace(session, &deleg->parent, &deleg->file,
&owner, access, deny, delegate_type, &stateid.stateid, &delegation);
}
if (status)
goto out;
/* update delegation state */
AcquireSRWLockExclusive(&deleg->lock);
if (delegation.type != OPEN_DELEGATE_READ &&
delegation.type != OPEN_DELEGATE_WRITE) {
eprintf("recover_delegation_open() got delegation type %u, "
"expected %u\n", delegation.type, deleg->state.type);
} else {
memcpy(&deleg->state, &delegation, sizeof(open_delegation4));
deleg->revoked = FALSE;
}
ReleaseSRWLockExclusive(&deleg->lock);
/* send CLOSE to free the open stateid */
stateid.open = NULL;
stateid.delegation = NULL;
stateid.type = STATEID_OPEN;
nfs41_close(session, &deleg->file, &stateid);
out:
return status;
}
int nfs41_recover_client_state(
IN nfs41_session *session,
IN nfs41_client *client)
{
const struct cb_layoutrecall_args recall = { PNFS_LAYOUTTYPE_FILE,
PNFS_IOMODE_ANY, TRUE, { PNFS_RETURN_ALL } };
struct client_state *state = &session->client->state;
struct list_entry *entry;
nfs41_open_state *open;
nfs41_delegation_state *deleg;
bool_t grace = TRUE;
int status = NFS4_OK;
EnterCriticalSection(&state->lock);
/* flag all delegations as revoked until successful recovery;
* recover_open() and recover_delegation_open() will only ask
* for delegations when revoked = TRUE */
list_for_each(entry, &state->delegations) {
deleg = list_container(entry, nfs41_delegation_state, client_entry);
deleg->revoked = TRUE;
}
/* recover each of the client's opens and associated delegations */
list_for_each(entry, &state->opens) {
open = list_container(entry, nfs41_open_state, client_entry);
status = recover_open(session, open, &grace);
if (status == NFS4_OK)
status = recover_locks(session, open, &grace);
if (status == NFS4ERR_BADSESSION)
goto unlock;
}
/* recover delegations that weren't associated with any opens */
list_for_each(entry, &state->delegations) {
deleg = list_container(entry, nfs41_delegation_state, client_entry);
if (deleg->revoked) {
/* TODO: try WANT_DELEGATION (server support is optional) */
status = recover_delegation_open(session, deleg, &grace);
if (status == NFS4ERR_BADSESSION)
goto unlock;
}
}
/* return any delegations that were reclaimed as 'recalled' */
status = nfs41_client_delegation_recovery(client);
unlock:
LeaveCriticalSection(&state->lock);
/* revoke all of the client's layouts */
pnfs_file_layout_recall(client, &recall);
if (grace && status != NFS4ERR_BADSESSION) {
/* send reclaim_complete, but don't fail on errors */
status = nfs41_reclaim_complete(session);
if (status && status == NFS4ERR_NOTSUPP)
eprintf("nfs41_reclaim_complete() failed with %s\n",
nfs_error_string(status));
}
return status;
}
static bool_t recover_stateid_open(
IN nfs_argop4 *argop,
IN stateid_arg *stateid)
{
bool_t retry = FALSE;
if (stateid->open) {
stateid4 *source = &stateid->open->stateid;
/* if the source stateid is different, update and retry */
AcquireSRWLockShared(&stateid->open->lock);
if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
memcpy(&stateid->stateid, source, sizeof(stateid4));
retry = TRUE;
}
ReleaseSRWLockShared(&stateid->open->lock);
}
return retry;
}
static bool_t recover_stateid_lock(
IN nfs_argop4 *argop,
IN stateid_arg *stateid)
{
bool_t retry = FALSE;
if (stateid->open) {
stateid4 *source = &stateid->open->locks.stateid;
/* if the source stateid is different, update and retry */
AcquireSRWLockShared(&stateid->open->lock);
if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
if (argop->op == OP_LOCK && source->seqid == 0) {
/* resend LOCK with an open stateid */
nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg;
lock->locker.new_lock_owner = 1;
lock->locker.u.open_owner.open_stateid = stateid;
lock->locker.u.open_owner.lock_owner = &stateid->open->owner;
source = &stateid->open->stateid;
}
memcpy(&stateid->stateid, source, sizeof(stateid4));
retry = TRUE;
}
ReleaseSRWLockShared(&stateid->open->lock);
}
return retry;
}
static bool_t recover_stateid_delegation(
IN nfs_argop4 *argop,
IN stateid_arg *stateid)
{
bool_t retry = FALSE;
if (stateid->open) {
/* if the source stateid is different, update and retry */
AcquireSRWLockShared(&stateid->open->lock);
if (argop->op == OP_OPEN && stateid->open->do_close) {
/* for nfs41_delegation_to_open(); if we've already reclaimed
* an open stateid, just fail this OPEN with BAD_STATEID */
} else if (stateid->open->delegation.state) {
nfs41_delegation_state *deleg = stateid->open->delegation.state;
stateid4 *source = &deleg->state.stateid;
AcquireSRWLockShared(&deleg->lock);
if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
memcpy(&stateid->stateid, source, sizeof(stateid4));
retry = TRUE;
}
ReleaseSRWLockShared(&deleg->lock);
}
ReleaseSRWLockShared(&stateid->open->lock);
} else if (stateid->delegation) {
nfs41_delegation_state *deleg = stateid->delegation;
stateid4 *source = &deleg->state.stateid;
AcquireSRWLockShared(&deleg->lock);
if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
memcpy(&stateid->stateid, source, sizeof(stateid4));
retry = TRUE;
}
ReleaseSRWLockShared(&deleg->lock);
}
return retry;
}
bool_t nfs41_recover_stateid(
IN nfs41_session *session,
IN nfs_argop4 *argop)
{
stateid_arg *stateid = NULL;
/* get the stateid_arg from the operation's arguments */
if (argop->op == OP_OPEN) {
nfs41_op_open_args *open = (nfs41_op_open_args*)argop->arg;
if (open->claim->claim == CLAIM_DELEGATE_CUR)
stateid = open->claim->u.deleg_cur.delegate_stateid;
else if (open->claim->claim == CLAIM_DELEG_CUR_FH)
stateid = open->claim->u.deleg_cur_fh.delegate_stateid;
} else if (argop->op == OP_CLOSE) {
nfs41_op_close_args *close = (nfs41_op_close_args*)argop->arg;
stateid = close->stateid;
} else if (argop->op == OP_READ) {
nfs41_read_args *read = (nfs41_read_args*)argop->arg;
stateid = read->stateid;
} else if (argop->op == OP_WRITE) {
nfs41_write_args *write = (nfs41_write_args*)argop->arg;
stateid = write->stateid;
} else if (argop->op == OP_LOCK) {
nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg;
if (lock->locker.new_lock_owner)
stateid = lock->locker.u.open_owner.open_stateid;
else
stateid = lock->locker.u.lock_owner.lock_stateid;
} else if (argop->op == OP_LOCKU) {
nfs41_locku_args *locku = (nfs41_locku_args*)argop->arg;
stateid = locku->lock_stateid;
} else if (argop->op == OP_SETATTR) {
nfs41_setattr_args *setattr = (nfs41_setattr_args*)argop->arg;
stateid = setattr->stateid;
} else if (argop->op == OP_LAYOUTGET) {
pnfs_layoutget_args *lget = (pnfs_layoutget_args*)argop->arg;
stateid = lget->stateid;
} else if (argop->op == OP_DELEGRETURN) {
nfs41_delegreturn_args *dr = (nfs41_delegreturn_args*)argop->arg;
stateid = dr->stateid;
}
if (stateid == NULL)
return FALSE;
/* if there's recovery in progress, wait for it to finish */
EnterCriticalSection(&session->client->recovery.lock);
while (session->client->recovery.in_recovery)
SleepConditionVariableCS(&session->client->recovery.cond,
&session->client->recovery.lock, INFINITE);
LeaveCriticalSection(&session->client->recovery.lock);
switch (stateid->type) {
case STATEID_OPEN:
return recover_stateid_open(argop, stateid);
case STATEID_LOCK:
return recover_stateid_lock(argop, stateid);
case STATEID_DELEG_FILE:
return recover_stateid_delegation(argop, stateid);
default:
eprintf("%s can't recover stateid type %u\n",
nfs_opnum_to_string(argop->op), stateid->type);
break;
}
return FALSE;
}