/** * @brief Check delegation claims while opening a file * * This function implements the CLAIM_DELEGATE_CUR claim. * * @param[in] arg OPEN4 arguments * @param[in,out] data Comopund's data */ static nfsstat4 open4_claim_deleg(OPEN4args *arg, compound_data_t *data) { open_claim_type4 claim = arg->claim.claim; stateid4 *rcurr_state; struct fsal_obj_handle *obj_lookup; const utf8string *utfname; char *filename; fsal_status_t fsal_status; nfsstat4 status; state_t *found_state = NULL; if (!(op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_delegations_w) || op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_delegations_r)) ) { LogDebug(COMPONENT_STATE, "NFS4 OPEN returning NFS4ERR_NOTSUPP for CLAIM_DELEGATE"); return NFS4ERR_NOTSUPP; } assert(claim == CLAIM_DELEGATE_CUR); utfname = &arg->claim.open_claim4_u.delegate_cur_info.file; rcurr_state = &arg->claim.open_claim4_u.delegate_cur_info.delegate_stateid; LogDebug(COMPONENT_NFS_V4, "file name: %.*s", utfname->utf8string_len, utfname->utf8string_val); /* Check if filename is correct */ status = nfs4_utf8string2dynamic(utfname, UTF8_SCAN_ALL, &filename); if (status != NFS4_OK) { LogDebug(COMPONENT_NFS_V4, "Invalid filename"); return status; } /* Does a file with this name already exist ? */ fsal_status = fsal_lookup(data->current_obj, filename, &obj_lookup, NULL); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_NFS_V4, "%s lookup failed.", filename); gsh_free(filename); return nfs4_Errno_status(fsal_status); } gsh_free(filename); status = open4_create_fh(data, obj_lookup, false); if (status != NFS4_OK) { LogDebug(COMPONENT_NFS_V4, "open4_create_fh failed"); return status; } found_state = nfs4_State_Get_Pointer(rcurr_state->other); if (found_state == NULL) { LogDebug(COMPONENT_NFS_V4, "state not found with CLAIM_DELEGATE_CUR"); return NFS4ERR_BAD_STATEID; } else { if (isFullDebug(COMPONENT_NFS_V4)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = {sizeof(str), str, str}; display_stateid(&dspbuf, found_state); LogFullDebug(COMPONENT_NFS_V4, "found matching %s", str); } dec_state_t_ref(found_state); } LogFullDebug(COMPONENT_NFS_V4, "done with CLAIM_DELEGATE_CUR"); return NFS4_OK; }
/** * @brief Check and look up the supplied stateid * * This function yields the state for the stateid if it is valid. * * @param[in] stateid Stateid to look up * @param[in] entry Associated file (if any) * @param[out] state Found state * @param[in] data Compound data * @param[in] flags Flags governing special stateids * @param[in] owner_seqid seqid on v4.0 owner * @param[in] check_seqid Whether to validate owner_seqid * @param[in] tag Arbitrary string for logging/debugging * * @return NFSv4 status codes */ nfsstat4 nfs4_Check_Stateid(stateid4 *stateid, cache_entry_t *entry, state_t **state, compound_data_t *data, int flags, seqid4 owner_seqid, bool check_seqid, const char *tag) { uint32_t epoch = 0; uint64_t epoch_low = ServerEpoch & 0xFFFFFFFF; state_t *state2 = NULL; /* string str has to accomodate stateid->other(OTHERSIZE * 2 ), * stateid->seqid(max 10 bytes), * a colon (:) and a terminating null character. */ char str[OTHERSIZE * 2 + 10 + 2]; int32_t diff; clientid4 clientid; nfs_client_id_t *pclientid; int rc; nfsstat4 status; if (isDebug(COMPONENT_STATE)) { sprint_mem(str, (char *)stateid->other, OTHERSIZE); sprintf(str + OTHERSIZE * 2, ":%u", (unsigned int)stateid->seqid); } LogFullDebug(COMPONENT_STATE, "Check %s stateid flags%s%s%s%s%s%s%s", tag, flags & STATEID_SPECIAL_ALL_0 ? " ALL_0" : "", flags & STATEID_SPECIAL_ALL_1 ? " ALL_1" : "", flags & STATEID_SPECIAL_CURRENT ? " CURRENT" : "", flags & STATEID_SPECIAL_CLOSE_40 ? " CLOSE_40" : "", flags & STATEID_SPECIAL_CLOSE_41 ? " CLOSE_41" : "", flags & STATEID_SPECIAL_FREE ? " FREE" : "", flags == 0 ? " NONE" : ""); /* Test for OTHER is all zeros */ if (memcmp(stateid->other, all_zero, OTHERSIZE) == 0) { if (stateid->seqid == 0 && (flags & STATEID_SPECIAL_ALL_0) != 0) { /* All 0 stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid found special all 0 stateid", tag); /** @todo FSF: eventually this may want to return an * actual state for use in temporary locks for I/O. */ data->current_stateid_valid = false; goto success; } if (stateid->seqid == 1 && (flags & STATEID_SPECIAL_CURRENT) != 0) { /* Special current stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid found special 'current' stateid", tag); if (!data->current_stateid_valid) { LogDebug(COMPONENT_STATE, "Check %s stateid STATEID_SPECIAL_CURRENT - current stateid is bad", tag); status = NFS4ERR_BAD_STATEID; goto failure; } /* Copy current stateid in and proceed to checks */ *stateid = data->current_stateid; goto check_it; } LogDebug(COMPONENT_STATE, "Check %s stateid with OTHER all zeros, seqid %u unexpected", tag, (unsigned int)stateid->seqid); status = NFS4ERR_BAD_STATEID; goto failure; } /* Test for OTHER is all ones */ if (memcmp(stateid->other, all_ones, OTHERSIZE) == 0) { /* Test for special all ones stateid */ if (stateid->seqid == seqid_all_one && (flags & STATEID_SPECIAL_ALL_1) != 0) { /* All 1 stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid found special all 1 stateid", tag); /** @todo FSF: eventually this may want to return an * actual state for use in temporary locks for I/O. */ data->current_stateid_valid = false; goto success; } LogDebug(COMPONENT_STATE, "Check %s stateid with OTHER all ones, seqid %u unexpected", tag, (unsigned int)stateid->seqid); status = NFS4ERR_BAD_STATEID; goto failure; } check_it: /* Extract the clientid from the stateid other field */ memcpy(&clientid, stateid->other, sizeof(clientid)); /* Extract the epoch from the clientid */ epoch = clientid >> (clientid4) 32; /* Check if stateid was made from this server instance */ if (epoch != epoch_low) { LogDebug(COMPONENT_STATE, "Check %s stateid found stale stateid %s", tag, str); status = NFS4ERR_STALE_STATEID; goto failure; } /* Try to get the related state */ if (!nfs4_State_Get_Pointer(stateid->other, &state2)) { /* We matched this server's epoch, but could not find the * stateid. Chances are, the client was expired and the state * has all been freed. * * We could use another check here for a BAD stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid could not find state %s", tag, str); /* Try and find the clientid */ rc = nfs_client_id_get_confirmed(clientid, &pclientid); if (rc != CLIENT_ID_SUCCESS) { /* Unknown client id (or other problem), * return that result. */ status = clientid_error_to_nfsstat(rc); goto failure; } if ((flags & (STATEID_SPECIAL_CLOSE_40 | STATEID_SPECIAL_CLOSE_41)) != 0) { /* This is a close with a valid clientid, but invalid * stateid counter. We will assume this is a replayed * close. */ if (data->preserved_clientid != NULL) { /* We don't expect this, but, just in case... * Update and release already reserved lease. */ pthread_mutex_lock(&data->preserved_clientid ->cid_mutex); update_lease(data->preserved_clientid); pthread_mutex_unlock(&data->preserved_clientid ->cid_mutex); data->preserved_clientid = NULL; } /* Check if lease is expired and reserve it */ pthread_mutex_lock(&pclientid->cid_mutex); if (!reserve_lease(pclientid)) { LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED"); pthread_mutex_unlock(&pclientid->cid_mutex); status = NFS4ERR_EXPIRED; goto failure; } if ((flags & STATEID_SPECIAL_CLOSE_40) != 0) { /* Just update the lease and leave the reserved * clientid NULL. */ update_lease(pclientid); } else { /* Remember the reserved clientid for the rest * of the compound. */ data->preserved_clientid = pclientid; } pthread_mutex_unlock(&pclientid->cid_mutex); /* Replayed close, it's ok, but stateid doesn't exist */ LogDebug(COMPONENT_STATE, "Check %s stateid is a replayed close", tag); data->current_stateid_valid = false; goto success; } /* Release the clientid reference we just acquired. */ dec_client_id_ref(pclientid); status = NFS4ERR_BAD_STATEID; goto failure; } /* Now, if this lease is not already reserved, reserve it */ if (data->preserved_clientid != state2->state_owner->so_owner.so_nfs4_owner.so_clientrec) { if (data->preserved_clientid != NULL) { /* We don't expect this to happen, but, just in case... * Update and release already reserved lease. */ pthread_mutex_lock(&data->preserved_clientid ->cid_mutex); update_lease(data->preserved_clientid); pthread_mutex_unlock(&data->preserved_clientid ->cid_mutex); data->preserved_clientid = NULL; } /* Check if lease is expired and reserve it */ pthread_mutex_lock(&state2->state_owner->so_owner .so_nfs4_owner.so_clientrec->cid_mutex); if (!reserve_lease (state2->state_owner->so_owner.so_nfs4_owner. so_clientrec)) { LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED"); pthread_mutex_unlock(&state2->state_owner->so_owner .so_nfs4_owner.so_clientrec ->cid_mutex); status = NFS4ERR_EXPIRED; goto failure; } data->preserved_clientid = state2->state_owner->so_owner.so_nfs4_owner.so_clientrec; pthread_mutex_unlock(&state2->state_owner->so_owner .so_nfs4_owner.so_clientrec->cid_mutex); } /* Sanity check : Is this the right file ? */ if ((entry != NULL) && (state2->state_entry != entry)) { LogDebug(COMPONENT_STATE, "Check %s stateid found stateid %s has wrong file", tag, str); status = NFS4ERR_BAD_STATEID; goto failure; } /* Whether stateid.seqid may be zero depends on the state type exclusively, See RFC 5661 pp. 161,287-288. */ if ((state2->state_type == STATE_TYPE_LAYOUT) || (stateid->seqid != 0)) { /* Check seqid in stateid */ /** * @todo fsf: maybe change to simple comparison: * stateid->seqid < state2->state_seqid * as good enough and maybe makes pynfs happy. */ diff = stateid->seqid - state2->state_seqid; if (diff < 0) { /* if this is NFSv4.0 and stateid's seqid is one less * than current AND if owner_seqid is current * pass state back to allow replay check */ if ((check_seqid) && ((diff == -1) || ((state2->state_seqid == 1) && (stateid->seqid == seqid_all_one))) && (owner_seqid == state2->state_owner->so_owner.so_nfs4_owner. so_seqid)) { LogDebug(COMPONENT_STATE, "possible replay?"); *state = state2; status = NFS4ERR_REPLAY; goto replay; } /* OLD_STATEID */ LogDebug(COMPONENT_STATE, "Check %s stateid found OLD stateid %s, expected seqid %u", tag, str, (unsigned int)state2->state_seqid); status = NFS4ERR_OLD_STATEID; goto failure; } /* stateid seqid is current and owner seqid is previous, * replay (should be an error condition that did not change * the stateid, no real need to check since the operation * must be the same) */ else if ((diff == 0) && (check_seqid) && (owner_seqid == state2->state_owner->so_owner.so_nfs4_owner. so_seqid)) { LogDebug(COMPONENT_STATE, "possible replay?"); *state = state2; status = NFS4ERR_REPLAY; goto replay; } else if (diff > 0) { /* BAD_STATEID */ LogDebug(COMPONENT_STATE, "Check %s stateid found BAD stateid %s, expected seqid %u", tag, str, (unsigned int)state2->state_seqid); status = NFS4ERR_BAD_STATEID; goto failure; } } if ((flags & STATEID_SPECIAL_FREE) != 0) { switch (state2->state_type) { case STATE_TYPE_LOCK: PTHREAD_RWLOCK_rdlock(&state2->state_entry->state_lock); if (glist_empty (&state2->state_data.lock.state_locklist)) { LogFullDebug(COMPONENT_STATE, "Check %s stateid %s has no locks, ok to free", tag, str); PTHREAD_RWLOCK_unlock(&state2->state_entry-> state_lock); break; } PTHREAD_RWLOCK_unlock(&state2->state_entry->state_lock); /* Fall through for failure */ case STATE_TYPE_NONE: case STATE_TYPE_SHARE: case STATE_TYPE_DELEG: case STATE_TYPE_LAYOUT: LogDebug(COMPONENT_STATE, "Check %s stateid found stateid %s with locks held", tag, str); status = NFS4ERR_LOCKS_HELD; goto failure; } } data->current_stateid_valid = true; LogFullDebug(COMPONENT_STATE, "Check %s stateid found valid stateid %s - %p", tag, str, state2); /* Copy stateid into current for later use */ data->current_stateid = *stateid; data->current_stateid.seqid = state2->state_seqid; success: *state = state2; return NFS4_OK; failure: *state = NULL; replay: data->current_stateid_valid = false; return status; }