diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c index f723c45..2f8eb5e 100644 --- a/daemon/nfs41_ops.c +++ b/daemon/nfs41_ops.c @@ -1527,6 +1527,50 @@ out: return status; } +enum nfsstat4 nfs41_want_delegation( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN deleg_claim4 *claim, + IN uint32_t want, + IN bool_t try_recovery, + OUT open_delegation4 *delegation) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_want_delegation_args wd_args; + nfs41_want_delegation_res wd_res; + + compound_init(&compound, argops, resops, "want_delegation"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + status = nfs41_session_sequence(&sequence_args, session, 0); + if (status) + goto out; + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_WANT_DELEGATION, &wd_args, &wd_res); + wd_args.claim = claim; + wd_args.want = want; + wd_res.delegation = delegation; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + int nfs41_delegpurge( IN nfs41_session *session) { diff --git a/daemon/nfs41_ops.h b/daemon/nfs41_ops.h index caf4f1d..2bc2c89 100644 --- a/daemon/nfs41_ops.h +++ b/daemon/nfs41_ops.h @@ -667,8 +667,6 @@ typedef struct __nfs41_op_open_res_ok { uint32_t rflags; bitmap4 attrset; open_delegation4 *delegation; - uint32_t why_no_deleg; - uint32_t why_none_flag; } nfs41_op_open_res_ok; typedef struct __nfs41_op_open_res { @@ -798,6 +796,25 @@ typedef struct __nfs41_setattr_res { } nfs41_setattr_res; +/* OP_WANT_DELEGATION */ +typedef struct __deleg_claim4 { + uint32_t claim; + /* case CLAIM_PREVIOUS: */ + uint32_t prev_delegate_type; +} deleg_claim4; + +typedef struct __nfs41_want_delegation_args { + uint32_t want; + deleg_claim4 *claim; +} nfs41_want_delegation_args; + +typedef struct __nfs41_want_delegation_res { + uint32_t status; + /* case NFS4_OK: */ + open_delegation4 *delegation; +} nfs41_want_delegation_res; + + /* OP_WRITE */ enum stable_how4 { UNSTABLE4 = 0, @@ -1158,6 +1175,14 @@ int nfs41_access( OUT uint32_t *supported OPTIONAL, OUT uint32_t *access OPTIONAL); +enum nfsstat4 nfs41_want_delegation( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN deleg_claim4 *claim, + IN uint32_t want, + IN bool_t try_recovery, + OUT open_delegation4 *delegation); + int nfs41_delegpurge( IN nfs41_session *session); diff --git a/daemon/nfs41_xdr.c b/daemon/nfs41_xdr.c index 63934c2..1940ec4 100644 --- a/daemon/nfs41_xdr.c +++ b/daemon/nfs41_xdr.c @@ -2005,22 +2005,22 @@ static bool_t encode_op_open( static bool_t decode_open_none_delegation4( XDR *xdr, - nfs41_op_open_res_ok *res) + open_delegation4 *delegation) { - bool_t result = TRUE; + enum_t why_no_deleg; + bool_t will_signal; - if (!xdr_u_int32_t(xdr, &res->why_no_deleg)) + if (!xdr_enum(xdr, (enum_t*)&why_no_deleg)) return FALSE; - switch (res->why_no_deleg) + + switch (why_no_deleg) { case WND4_CONTENTION: case WND4_RESOURCE: - result = xdr_u_int32_t(xdr, &res->why_none_flag); - break; + return xdr_bool(xdr, &will_signal); default: - break; + return TRUE; } - return result; } static bool_t decode_open_read_delegation4( @@ -2037,14 +2037,19 @@ static bool_t decode_open_read_delegation4( } static bool_t decode_modified_limit4( - XDR *xdr) + XDR *xdr, + uint64_t *filesize) { - uint32_t tmp; + uint32_t blocks, bytes_per_block; - if (!xdr_u_int32_t(xdr, &tmp)) + if (!xdr_u_int32_t(xdr, &blocks)) return FALSE; - return xdr_u_int32_t(xdr, &tmp); + if (!xdr_u_int32_t(xdr, &bytes_per_block)) + return FALSE; + + *filesize = blocks * bytes_per_block; + return TRUE; } enum limit_by4 { @@ -2053,27 +2058,23 @@ enum limit_by4 { }; static bool_t decode_space_limit4( - XDR *xdr) + XDR *xdr, + uint64_t *filesize) { - uint32_t tmp_limitby; - uint64_t tmp_filesize; + uint32_t limitby; - if (!xdr_u_int32_t(xdr, &tmp_limitby)) + if (!xdr_u_int32_t(xdr, &limitby)) return FALSE; - switch (tmp_limitby) + switch (limitby) { case NFS_LIMIT_SIZE: - return xdr_u_hyper(xdr, &tmp_filesize); - break; + return xdr_u_hyper(xdr, filesize); case NFS_LIMIT_BLOCKS: - return decode_modified_limit4(xdr); - break; + return decode_modified_limit4(xdr, filesize); default: - eprintf("decode_space_limit4: limitby %d invalid\n", - tmp_limitby); + eprintf("decode_space_limit4: limitby %d invalid\n", limitby); return FALSE; - break; } } @@ -2081,13 +2082,15 @@ static bool_t decode_open_write_delegation4( XDR *xdr, open_delegation4 *delegation) { + uint64_t size_limit; + if (!xdr_stateid4(xdr, &delegation->stateid)) return FALSE; if (!xdr_bool(xdr, &delegation->recalled)) return FALSE; - if (!decode_space_limit4(xdr)) + if (!decode_space_limit4(xdr, &size_limit)) return FALSE; return xdr_nfsace4(xdr, &delegation->permissions); @@ -2097,8 +2100,6 @@ static bool_t decode_open_res_ok( XDR *xdr, nfs41_op_open_res_ok *res) { - bool_t result = TRUE; - if (!xdr_stateid4(xdr, res->stateid)) return FALSE; @@ -2117,23 +2118,18 @@ static bool_t decode_open_res_ok( switch (res->delegation->type) { case OPEN_DELEGATE_NONE: - break; + return TRUE; case OPEN_DELEGATE_NONE_EXT: - result = decode_open_none_delegation4(xdr, res); - break; + return decode_open_none_delegation4(xdr, res->delegation); case OPEN_DELEGATE_READ: - result = decode_open_read_delegation4(xdr, res->delegation); - break; + return decode_open_read_delegation4(xdr, res->delegation); case OPEN_DELEGATE_WRITE: - result = decode_open_write_delegation4(xdr, res->delegation); - break; + return decode_open_write_delegation4(xdr, res->delegation); default: eprintf("decode_open_res_ok: delegation type %d not " "supported.\n", res->delegation->type); - result = FALSE; - break; + return FALSE; } - return result; } static bool_t decode_op_open( @@ -2681,6 +2677,64 @@ static bool_t decode_op_setattr( } +/* + * OP_WANT_DELEGATION + */ +static bool_t encode_op_want_delegation( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_want_delegation_args *args = (nfs41_want_delegation_args*)argop->arg; + + if (unexpected_op(argop->op, OP_WANT_DELEGATION)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->want)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->claim->claim)) + return FALSE; + + return args->claim->claim != CLAIM_PREVIOUS || + xdr_u_int32_t(xdr, &args->claim->prev_delegate_type); +} + +static bool_t decode_op_want_delegation( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_want_delegation_res *res = (nfs41_want_delegation_res*)resop->res; + + if (unexpected_op(resop->op, OP_WANT_DELEGATION)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status) + return TRUE; + + if (!xdr_enum(xdr, (enum_t*)&res->delegation->type)) + return FALSE; + + switch (res->delegation->type) + { + case OPEN_DELEGATE_NONE: + return TRUE; + case OPEN_DELEGATE_NONE_EXT: + return decode_open_none_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_READ: + return decode_open_read_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_WRITE: + return decode_open_write_delegation4(xdr, res->delegation); + default: + eprintf("decode_open_res_ok: delegation type %d not " + "supported.\n", res->delegation->type); + return FALSE; + } +} + + /* * OP_WRITE */ @@ -3426,7 +3480,7 @@ static const op_table_entry g_op_table[] = { { encode_op_sequence, decode_op_sequence }, /* OP_SEQUENCE = 53 */ { NULL, NULL }, /* OP_SET_SSV = 54 */ { NULL, NULL }, /* OP_TEST_STATEID = 55 */ - { NULL, NULL }, /* OP_WANT_DELEGATION = 56 */ + { encode_op_want_delegation, decode_op_want_delegation }, /* OP_WANT_DELEGATION = 56 */ { encode_op_destroy_clientid, decode_op_destroy_clientid }, /* OP_DESTROY_CLIENTID = 57 */ { encode_op_reclaim_complete, decode_op_reclaim_complete }, /* OP_RECLAIM_COMPLETE = 58 */ }; diff --git a/daemon/recovery.c b/daemon/recovery.c index 3033849..ee4ede2 100644 --- a/daemon/recovery.c +++ b/daemon/recovery.c @@ -262,6 +262,63 @@ static int recover_locks( return status; } +/* delegation recovery via WANT_DELEGATION */ +static int recover_delegation_want( + IN nfs41_session *session, + IN nfs41_delegation_state *deleg, + IN OUT bool_t *grace) +{ + deleg_claim4 claim; + open_delegation4 delegation = { 0 }; + enum open_delegation_type4 delegate_type; + uint32_t want_flags = 0; + int status = NFS4_OK; + + AcquireSRWLockShared(&deleg->lock); + delegate_type = deleg->state.type; + ReleaseSRWLockShared(&deleg->lock); + + if (delegate_type == OPEN_DELEGATE_READ) + want_flags |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG; + else + want_flags |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG; + + if (*grace) { + /* recover the delegation with WANT_DELEGATION/CLAIM_PREVIOUS */ + claim.claim = CLAIM_PREVIOUS; + claim.prev_delegate_type = delegate_type; + + status = nfs41_want_delegation(session, &deleg->file, + &claim, want_flags, FALSE, &delegation); + } else + status = NFS4ERR_NO_GRACE; + + if (status == NFS4ERR_NO_GRACE) { + *grace = FALSE; + /* attempt out-of-grace recovery with with CLAIM_DELEG_PREV_FH */ + claim.claim = CLAIM_DELEG_PREV_FH; + + status = nfs41_want_delegation(session, &deleg->file, + &claim, want_flags, FALSE, &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_want() 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); +out: + return status; +} + /* delegation recovery via OPEN (requires corresponding CLOSE) */ static int recover_delegation_open( IN nfs41_session *session, @@ -337,6 +394,7 @@ int nfs41_recover_client_state( nfs41_open_state *open; nfs41_delegation_state *deleg; bool_t grace = TRUE; + bool_t want_supported = TRUE; int status = NFS4_OK; EnterCriticalSection(&state->lock); @@ -362,9 +420,24 @@ int nfs41_recover_client_state( /* recover delegations that weren't associated with any opens */ list_for_each(entry, &state->delegations) { deleg = list_container(entry, nfs41_delegation_state, client_entry); + + /* 10.2.1. Delegation Recovery + * When a client needs to reclaim a delegation and there is no + * associated open, the client may use the CLAIM_PREVIOUS variant + * of the WANT_DELEGATION operation. However, since the server is + * not required to support this operation, an alternative is to + * reclaim via a dummy OPEN together with the delegation using an + * OPEN of type CLAIM_PREVIOUS. */ if (deleg->revoked) { - /* TODO: try WANT_DELEGATION (server support is optional) */ - status = recover_delegation_open(session, deleg, &grace); + if (want_supported) + status = recover_delegation_want(session, deleg, &grace); + else + status = NFS4ERR_NOTSUPP; + + if (status == NFS4ERR_NOTSUPP) { + want_supported = FALSE; + status = recover_delegation_open(session, deleg, &grace); + } if (status == NFS4ERR_BADSESSION) goto unlock; }