/* 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 "delegation.h" #include "nfs41_ops.h" #include "util.h" #include "daemon_debug.h" #define DGLVL 2 /* dprintf level for delegation logging */ /* allocation and reference counting */ static int delegation_create( IN const nfs41_path_fh *parent, IN const nfs41_path_fh *file, IN const open_delegation4 *delegation, OUT nfs41_delegation_state **deleg_out) { nfs41_delegation_state *state; int status = NO_ERROR; state = calloc(1, sizeof(nfs41_delegation_state)); if (state == NULL) { status = GetLastError(); goto out; } memcpy(&state->state, delegation, sizeof(open_delegation4)); abs_path_copy(&state->path, file->path); path_fh_init(&state->file, &state->path); fh_copy(&state->file.fh, &file->fh); path_fh_init(&state->parent, &state->path); last_component(state->path.path, state->file.name.name, &state->parent.name); fh_copy(&state->parent.fh, &parent->fh); list_init(&state->client_entry); state->status = DELEGATION_GRANTED; InitializeSRWLock(&state->lock); InitializeConditionVariable(&state->cond); state->ref_count = 1; *deleg_out = state; out: return status; } void nfs41_delegation_ref( IN nfs41_delegation_state *state) { const LONG count = InterlockedIncrement(&state->ref_count); dprintf(DGLVL, "nfs41_delegation_ref(%s) count %d\n", state->path.path, count); } void nfs41_delegation_deref( IN nfs41_delegation_state *state) { const LONG count = InterlockedDecrement(&state->ref_count); dprintf(DGLVL, "nfs41_delegation_deref(%s) count %d\n", state->path.path, count); if (count == 0) free(state); } /* delegation return */ #define open_entry(pos) list_container(pos, nfs41_open_state, client_entry) static int open_deleg_cmp(const struct list_entry *entry, const void *value) { nfs41_open_state *open = open_entry(entry); int result = -1; /* open must match the delegation and have no open stateid */ AcquireSRWLockShared(&open->lock); if (open->delegation.state != value) goto out; if (open->do_close) goto out; result = 0; out: ReleaseSRWLockShared(&open->lock); return result; } /* find the first open that needs recovery */ static nfs41_open_state* deleg_open_find( IN struct client_state *state, IN const nfs41_delegation_state *deleg) { struct list_entry *entry; nfs41_open_state *open = NULL; EnterCriticalSection(&state->lock); entry = list_search(&state->opens, deleg, open_deleg_cmp); if (entry) { open = open_entry(entry); nfs41_open_state_ref(open); /* return a reference */ } LeaveCriticalSection(&state->lock); return open; } #pragma warning (disable : 4706) /* assignment within conditional expression */ static void delegation_return( IN nfs41_client *client, IN nfs41_delegation_state *deleg, IN bool_t truncate) { stateid_arg stateid; struct list_entry *entry; nfs41_open_state *open; /* recover opens associated with the delegation */ while (open = deleg_open_find(&client->state, deleg)) { nfs41_delegation_to_open(open, TRUE); nfs41_open_state_deref(open); } /* TODO: flush data and metadata before returning delegation */ /* return the delegation */ stateid.type = STATEID_DELEG_FILE; stateid.open = NULL; stateid.delegation = deleg; AcquireSRWLockShared(&deleg->lock); memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); ReleaseSRWLockShared(&deleg->lock); nfs41_delegreturn(client->session, &deleg->file, &stateid, TRUE); /* remove from the client's list */ EnterCriticalSection(&client->state.lock); list_remove(&deleg->client_entry); list_for_each(entry, &client->state.opens) { open = open_entry(entry); AcquireSRWLockExclusive(&open->lock); if (open->delegation.state == deleg) { /* drop the delegation reference */ nfs41_delegation_deref(open->delegation.state); open->delegation.state = NULL; } ReleaseSRWLockExclusive(&open->lock); } LeaveCriticalSection(&client->state.lock); /* signal threads waiting on delegreturn */ AcquireSRWLockExclusive(&deleg->lock); deleg->status = DELEGATION_RETURNED; WakeAllConditionVariable(&deleg->cond); ReleaseSRWLockExclusive(&deleg->lock); /* release the client's reference */ nfs41_delegation_deref(deleg); } /* open delegation */ int nfs41_delegation_granted( IN nfs41_session *session, IN nfs41_path_fh *parent, IN nfs41_path_fh *file, IN open_delegation4 *delegation, IN bool_t try_recovery, OUT nfs41_delegation_state **deleg_out) { stateid_arg stateid; nfs41_client *client = session->client; nfs41_delegation_state *state; int status = NO_ERROR; if (delegation->type != OPEN_DELEGATE_READ && delegation->type != OPEN_DELEGATE_WRITE) goto out; if (delegation->recalled) goto out_return; /* allocate the delegation state */ status = delegation_create(parent, file, delegation, &state); if (status) goto out_return; /* register the delegation with the client */ EnterCriticalSection(&client->state.lock); /* XXX: check for duplicates by fh and stateid? */ list_add_tail(&client->state.delegations, &state->client_entry); LeaveCriticalSection(&client->state.lock); nfs41_delegation_ref(state); /* return a reference */ *deleg_out = state; out: return status; out_return: /* return the delegation on failure */ memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4)); stateid.type = STATEID_DELEG_FILE; stateid.open = NULL; stateid.delegation = NULL; nfs41_delegreturn(session, file, &stateid, try_recovery); goto out; } #define deleg_entry(pos) list_container(pos, nfs41_delegation_state, client_entry) static int deleg_fh_cmp(const struct list_entry *entry, const void *value) { const nfs41_fh *lhs = &deleg_entry(entry)->file.fh; const nfs41_fh *rhs = (const nfs41_fh*)value; if (lhs->superblock != rhs->superblock) return -1; if (lhs->fileid != rhs->fileid) return -1; return 0; } static bool_t delegation_compatible( IN enum open_delegation_type4 type, IN uint32_t create, IN uint32_t access, IN uint32_t deny) { switch (type) { case OPEN_DELEGATE_WRITE: /* An OPEN_DELEGATE_WRITE delegation allows the client to handle, * on its own, all opens. */ return TRUE; case OPEN_DELEGATE_READ: /* An OPEN_DELEGATE_READ delegation allows a client to handle, * on its own, requests to open a file for reading that do not * deny OPEN4_SHARE_ACCESS_READ access to others. */ if (create == OPEN4_CREATE) return FALSE; if (access & OPEN4_SHARE_ACCESS_WRITE || deny & OPEN4_SHARE_DENY_READ) return FALSE; return TRUE; default: return FALSE; } } static int delegation_find( IN nfs41_client *client, IN const void *value, IN list_compare_fn cmp, OUT nfs41_delegation_state **deleg_out) { struct list_entry *entry; int status = NFS4ERR_BADHANDLE; EnterCriticalSection(&client->state.lock); entry = list_search(&client->state.delegations, value, cmp); if (entry) { *deleg_out = deleg_entry(entry); nfs41_delegation_ref(*deleg_out); status = NFS4_OK; } LeaveCriticalSection(&client->state.lock); return status; } static int delegation_truncate( IN nfs41_delegation_state *deleg, IN nfs41_client *client, IN stateid_arg *stateid, IN uint32_t mode, IN nfs41_file_info *info) { nfs41_superblock *superblock = deleg->file.fh.superblock; /* use SETATTR to truncate the file */ info->attrmask.arr[0] = FATTR4_WORD0_SIZE; info->attrmask.arr[1] = FATTR4_WORD1_MODE | FATTR4_WORD1_TIME_CREATE | FATTR4_WORD1_TIME_MODIFY_SET; info->attrmask.count = 2; info->size = 0; info->mode = mode; get_nfs_time(&info->time_create); get_nfs_time(&info->time_modify); info->time_delta = &superblock->time_delta; /* mask out unsupported attributes */ nfs41_superblock_supported_attrs(superblock, &info->attrmask); return nfs41_setattr(client->session, &deleg->file, stateid, info); } int nfs41_delegate_open( IN nfs41_client *client, IN nfs41_path_fh *file, IN uint32_t create, IN uint32_t mode, IN uint32_t access, IN uint32_t deny, OUT nfs41_delegation_state **deleg_out, OUT nfs41_file_info *info) { nfs41_delegation_state *deleg; stateid_arg stateid; int status; /* search for a delegation with this filehandle */ status = delegation_find(client, &file->fh, deleg_fh_cmp, &deleg); if (status) goto out; AcquireSRWLockExclusive(&deleg->lock); if (deleg->status != DELEGATION_GRANTED) { /* the delegation is being returned, wait for it to finish */ while (deleg->status != DELEGATION_RETURNED) SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); status = NFS4ERR_BADHANDLE; } else if (!delegation_compatible(deleg->state.type, create, access, deny)) { #ifdef DELEGATION_RETURN_ON_CONFLICT /* this open will conflict, start the delegation return */ deleg->status = DELEGATION_RETURNING; status = NFS4ERR_DELEG_REVOKED; #else status = NFS4ERR_BADHANDLE; #endif } else if (create == OPEN4_CREATE) { /* copy the stateid for SETATTR */ stateid.open = NULL; stateid.delegation = deleg; stateid.type = STATEID_DELEG_FILE; memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); } ReleaseSRWLockExclusive(&deleg->lock); if (status == NFS4ERR_DELEG_REVOKED) goto out_return; if (status) goto out_deleg; if (create == OPEN4_CREATE) { /* write delegations allow us to simulate OPEN4_CREATE with SETATTR */ status = delegation_truncate(deleg, client, &stateid, mode, info); if (status) goto out_deleg; } /* TODO: check access against deleg->state.permissions or send ACCESS */ *deleg_out = deleg; status = NFS4_OK; out: return status; out_return: delegation_return(client, deleg, create == OPEN4_CREATE); out_deleg: nfs41_delegation_deref(deleg); goto out; } int nfs41_delegation_to_open( IN nfs41_open_state *open, IN bool_t try_recovery) { open_delegation4 ignore; open_claim4 claim; stateid4 open_stateid = { 0 }; stateid_arg deleg_stateid; int status = NFS4_OK; AcquireSRWLockExclusive(&open->lock); if (open->delegation.state == NULL) /* no delegation to reclaim */ goto out_unlock; if (open->do_close) /* already have an open stateid */ goto out_unlock; /* if another thread is reclaiming the open stateid, * wait for it to finish before returning success */ if (open->delegation.reclaim) { do { SleepConditionVariableSRW(&open->delegation.cond, &open->lock, INFINITE, 0); } while (open->delegation.reclaim); if (open->do_close) goto out_unlock; } open->delegation.reclaim = 1; AcquireSRWLockShared(&open->delegation.state->lock); deleg_stateid.open = open; deleg_stateid.delegation = NULL; deleg_stateid.type = STATEID_DELEG_FILE; memcpy(&deleg_stateid.stateid, &open->delegation.state->state.stateid, sizeof(stateid4)); ReleaseSRWLockShared(&open->delegation.state->lock); ReleaseSRWLockExclusive(&open->lock); /* send OPEN with CLAIM_DELEGATE_CUR */ claim.claim = CLAIM_DELEGATE_CUR; claim.u.deleg_cur.delegate_stateid = &deleg_stateid; claim.u.deleg_cur.name = &open->file.name; status = nfs41_open(open->session, &open->parent, &open->file, &open->owner, &claim, open->share_access, open->share_deny, OPEN4_NOCREATE, 0, 0, try_recovery, &open_stateid, &ignore, NULL); AcquireSRWLockExclusive(&open->lock); if (status == NFS4_OK) { /* save the new open stateid */ memcpy(&open->stateid, &open_stateid, sizeof(stateid4)); open->do_close = 1; } else if (status == NFS4ERR_BAD_STATEID && open->do_close) { /* something triggered client state recovery, and the open stateid * has already been reclaimed; see recover_stateid_delegation() */ status = NFS4_OK; } open->delegation.reclaim = 0; /* signal anyone waiting on the open stateid */ WakeAllConditionVariable(&open->delegation.cond); out_unlock: ReleaseSRWLockExclusive(&open->lock); if (status) eprintf("nfs41_delegation_to_open(%p) failed with %s\n", open, nfs_error_string(status)); return status; } /* synchronous delegation return */ #ifdef DELEGATION_RETURN_ON_CONFLICT int nfs41_delegation_return( IN nfs41_session *session, IN nfs41_path_fh *file, IN enum open_delegation_type4 access, IN bool_t truncate) { nfs41_client *client = session->client; nfs41_delegation_state *deleg; int status; /* find a delegation for this file */ status = delegation_find(client, &file->fh, deleg_fh_cmp, &deleg); if (status) goto out; AcquireSRWLockExclusive(&deleg->lock); if (deleg->status == DELEGATION_GRANTED) { /* return unless delegation is write and access is read */ if (deleg->state.type != OPEN_DELEGATE_WRITE || access != OPEN_DELEGATE_READ) { deleg->status = DELEGATION_RETURNING; status = NFS4ERR_DELEG_REVOKED; } } else { /* the delegation is being returned, wait for it to finish */ while (deleg->status != DELEGATION_RETURNED) SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); status = NFS4ERR_BADHANDLE; } ReleaseSRWLockExclusive(&deleg->lock); if (status == NFS4ERR_DELEG_REVOKED) { delegation_return(client, deleg, truncate); status = NFS4_OK; } nfs41_delegation_deref(deleg); out: return status; } #endif /* asynchronous delegation recall */ struct recall_thread_args { nfs41_client *client; nfs41_delegation_state *delegation; bool_t truncate; }; static unsigned int WINAPI delegation_recall_thread(void *args) { struct recall_thread_args *recall = (struct recall_thread_args*)args; delegation_return(recall->client, recall->delegation, recall->truncate); /* clean up thread arguments */ nfs41_delegation_deref(recall->delegation); nfs41_root_deref(recall->client->root); free(recall); return 0; } static int deleg_stateid_cmp(const struct list_entry *entry, const void *value) { const stateid4 *lhs = &deleg_entry(entry)->state.stateid; const stateid4 *rhs = (const stateid4*)value; return memcmp(lhs->other, rhs->other, NFS4_STATEID_OTHER); } int nfs41_delegation_recall( IN nfs41_client *client, IN nfs41_fh *fh, IN const stateid4 *stateid, IN bool_t truncate) { nfs41_delegation_state *deleg; struct recall_thread_args *args; int status; dprintf(2, "--> nfs41_delegation_recall()\n"); /* search for the delegation by stateid instead of filehandle; * deleg_fh_cmp() relies on a proper superblock and fileid, * which we don't get with CB_RECALL */ status = delegation_find(client, stateid, deleg_stateid_cmp, &deleg); if (status) goto out; AcquireSRWLockExclusive(&deleg->lock); if (deleg->state.recalled) { /* return BADHANDLE if we've already responded to CB_RECALL */ status = NFS4ERR_BADHANDLE; } else { deleg->state.recalled = 1; if (deleg->status == DELEGATION_GRANTED) { /* start the delegation return */ deleg->status = DELEGATION_RETURNING; status = NFS4ERR_DELEG_REVOKED; } /* else return NFS4_OK */ } ReleaseSRWLockExclusive(&deleg->lock); if (status != NFS4ERR_DELEG_REVOKED) goto out_deleg; /* allocate thread arguments */ args = calloc(1, sizeof(struct recall_thread_args)); if (args == NULL) { status = NFS4ERR_SERVERFAULT; eprintf("nfs41_delegation_recall() failed to allocate arguments\n"); goto out_deleg; } /* hold a reference on the root */ nfs41_root_ref(client->root); args->client = client; args->delegation = deleg; args->truncate = truncate; /* the callback thread can't make rpc calls, so spawn a separate thread */ if (_beginthreadex(NULL, 0, delegation_recall_thread, args, 0, NULL) == 0) { status = NFS4ERR_SERVERFAULT; eprintf("nfs41_delegation_recall() failed to start thread\n"); goto out_args; } status = NFS4_OK; out: dprintf(DGLVL, "<-- nfs41_delegation_recall() returning %s\n", nfs_error_string(status)); return status; out_args: free(args); nfs41_root_deref(client->root); out_deleg: nfs41_delegation_deref(deleg); goto out; } void nfs41_client_delegation_free( IN nfs41_client *client) { struct list_entry *entry, *tmp; EnterCriticalSection(&client->state.lock); list_for_each_tmp (entry, tmp, &client->state.delegations) { list_remove(entry); nfs41_delegation_deref(deleg_entry(entry)); } LeaveCriticalSection(&client->state.lock); }