/** * @brief Return true if create verifier matches * * This function returns true if the create verifier matches * * @param[in] entry Entry to be managed. * @param[in] req_ctx Request context(user creds, client address etc) * @param[in] verf_hi High long of verifier * @param[in] verf_lo Low long of verifier * * @return Errors from cache_inode_lock_trust_attributes. * */ bool cache_inode_create_verify(cache_entry_t *entry, const struct req_op_context *req_ctx, uint32_t verf_hi, uint32_t verf_lo) { /* True if the verifier matches */ bool verified = false; /* Lock (and refresh if necessary) the attributes, copy them out, and unlock. */ if (cache_inode_lock_trust_attrs(entry, req_ctx, false) == CACHE_INODE_SUCCESS) { if (FSAL_TEST_MASK(entry->obj_handle->attributes.mask, ATTR_ATIME) && FSAL_TEST_MASK(entry->obj_handle->attributes.mask, ATTR_MTIME) && entry->obj_handle->attributes.atime.tv_sec == verf_hi && entry->obj_handle->attributes.mtime.tv_sec == verf_lo) { verified = true; } PTHREAD_RWLOCK_unlock(&entry->attr_lock); } return verified; }
/** * * @brief Checks the permissions on an object * * This function returns success if the supplied credentials possess * permission required to meet the specified access. * * @param[in] entry The object to be checked * @param[in] access_type The kind of access to be checked * @param[in] use_mutex Whether to acquire a read lock * * @return CACHE_INODE_SUCCESS if operation is a success * */ cache_inode_status_t cache_inode_access_sw(cache_entry_t *entry, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, bool use_mutex) { fsal_status_t fsal_status; struct fsal_obj_handle *pfsal_handle = entry->obj_handle; cache_inode_status_t status = CACHE_INODE_SUCCESS; LogDebugCIA(COMPONENT_CACHE_INODE, COMPONENT_NFS_V4_ACL, "access_type=0X%x", access_type); /* Set the return default to CACHE_INODE_SUCCESS */ status = CACHE_INODE_SUCCESS; /* We actually need the lock here since we're using the attribute cache, so get it if the caller didn't acquire it. */ if (use_mutex) { status = cache_inode_lock_trust_attrs(entry, false); if (status != CACHE_INODE_SUCCESS) goto out; } fsal_status = pfsal_handle->ops->test_access(pfsal_handle, access_type, allowed, denied); if (use_mutex) PTHREAD_RWLOCK_unlock(&entry->attr_lock); if (FSAL_IS_ERROR(fsal_status)) { status = cache_inode_error_convert(fsal_status); LogDebugCIA(COMPONENT_CACHE_INODE, COMPONENT_NFS_V4_ACL, "status=%s", cache_inode_err_str(status)); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "STALE returned by FSAL, calling kill_entry"); cache_inode_kill_entry(entry); } } else { status = CACHE_INODE_SUCCESS; } out: return status; }
/** * @brief Set the attributes for a file. * * This function sets the attributes of a file, both in the cache and * in the underlying filesystem. * * @param[in] entry Entry whose attributes are to be set * @param[in,out] attr Attributes to set/result of set * * @retval CACHE_INODE_SUCCESS if operation is a success */ cache_inode_status_t cache_inode_setattr(cache_entry_t *entry, struct attrlist *attr, bool is_open_write) { struct fsal_obj_handle *obj_handle = entry->obj_handle; fsal_status_t fsal_status = { 0, 0 }; fsal_acl_t *saved_acl = NULL; fsal_acl_status_t acl_status = 0; cache_inode_status_t status = CACHE_INODE_SUCCESS; uint64_t before; /* True if we have taken the content lock on 'entry' */ bool content_locked = false; if ((attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) && (entry->type != REGULAR_FILE)) { LogWarn(COMPONENT_CACHE_INODE, "Attempt to truncate non-regular file: type=%d", entry->type); status = CACHE_INODE_BAD_TYPE; } /* Is it allowed to change times ? */ if (!op_ctx->fsal_export->ops->fs_supports(op_ctx->fsal_export, fso_cansettime) && (FSAL_TEST_MASK (attr->mask, (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME)))) { status = CACHE_INODE_INVALID_ARGUMENT; goto out; } /* Get wrlock on attr_lock and verify attrs */ status = cache_inode_lock_trust_attrs(entry, true); if (status != CACHE_INODE_SUCCESS) return status; /* Do permission checks */ status = cache_inode_check_setattr_perms(entry, attr, is_open_write); if (status != CACHE_INODE_SUCCESS) goto unlock; if (attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); content_locked = true; } saved_acl = obj_handle->attributes.acl; before = obj_handle->attributes.change; fsal_status = obj_handle->ops->setattrs(obj_handle, attr); if (FSAL_IS_ERROR(fsal_status)) { status = cache_inode_error_convert(fsal_status); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE from truncate"); cache_inode_kill_entry(entry); } goto unlock; } fsal_status = obj_handle->ops->getattrs(obj_handle); *attr = obj_handle->attributes; if (FSAL_IS_ERROR(fsal_status)) { status = cache_inode_error_convert(fsal_status); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE from setattrs"); cache_inode_kill_entry(entry); } goto unlock; } if (before == obj_handle->attributes.change) obj_handle->attributes.change++; /* Decrement refcount on saved ACL */ nfs4_acl_release_entry(saved_acl, &acl_status); if (acl_status != NFS_V4_ACL_SUCCESS) LogCrit(COMPONENT_CACHE_INODE, "Failed to release old acl, status=%d", acl_status); cache_inode_fixup_md(entry); /* Copy the complete set of new attributes out. */ *attr = entry->obj_handle->attributes; status = CACHE_INODE_SUCCESS; unlock: if (content_locked) PTHREAD_RWLOCK_unlock(&entry->content_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); out: return status; }
/** * @brief Set the attributes for a file. * * This function sets the attributes of a file, both in the cache and * in the underlying filesystem. * * @param[in] entry Entry whose attributes are to be set * @param[in,out] attr Attributes to set/result of set * * @retval CACHE_INODE_SUCCESS if operation is a success */ cache_inode_status_t cache_inode_setattr(cache_entry_t *entry, struct attrlist *attr, bool is_open_write) { struct fsal_obj_handle *obj_handle = entry->obj_handle; fsal_status_t fsal_status = { 0, 0 }; fsal_acl_t *saved_acl = NULL; fsal_acl_status_t acl_status = 0; cache_inode_status_t status = CACHE_INODE_SUCCESS; uint64_t before; const struct user_cred *creds = op_ctx->creds; /* True if we have taken the content lock on 'entry' */ bool content_locked = false; if ((attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) && (entry->type != REGULAR_FILE)) { LogWarn(COMPONENT_CACHE_INODE, "Attempt to truncate non-regular file: type=%d", entry->type); status = CACHE_INODE_BAD_TYPE; goto out; } /* Is it allowed to change times ? */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_cansettime) && (FSAL_TEST_MASK (attr->mask, (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME)))) { status = CACHE_INODE_INVALID_ARGUMENT; goto out; } /* Get wrlock on attr_lock and verify attrs */ status = cache_inode_lock_trust_attrs(entry, true); if (status != CACHE_INODE_SUCCESS) return status; /* Do permission checks */ status = cache_inode_check_setattr_perms(entry, attr, is_open_write); if (status != CACHE_INODE_SUCCESS) goto unlock; if (attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); content_locked = true; } /* Test for the following condition from chown(2): * * When the owner or group of an executable file are changed by an * unprivileged user the S_ISUID and S_ISGID mode bits are cleared. * POSIX does not specify whether this also should happen when * root does the chown(); the Linux behavior depends on the kernel * version. In case of a non-group-executable file (i.e., one for * which the S_IXGRP bit is not set) the S_ISGID bit indicates * mandatory locking, and is not cleared by a chown(). * */ if (creds->caller_uid != 0 && (FSAL_TEST_MASK(attr->mask, ATTR_OWNER) || FSAL_TEST_MASK(attr->mask, ATTR_GROUP)) && ((entry->obj_handle->attrs->mode & (S_IXOTH | S_IXUSR | S_IXGRP)) != 0) && ((entry->obj_handle->attrs->mode & (S_ISUID | S_ISGID)) != 0)) { /* Non-priviledged user changing ownership on an executable * file with S_ISUID or S_ISGID bit set, need to be cleared. */ if (!FSAL_TEST_MASK(attr->mask, ATTR_MODE)) { /* Mode wasn't being set, so set it now, start with * the current attributes. */ attr->mode = entry->obj_handle->attrs->mode; FSAL_SET_MASK(attr->mask, ATTR_MODE); } /* Don't clear S_ISGID if the file isn't group executable. * In that case, S_ISGID indicates mandatory locking and * is not cleared by chown. */ if ((entry->obj_handle->attrs->mode & S_IXGRP) != 0) attr->mode &= ~S_ISGID; /* Clear S_ISUID. */ attr->mode &= ~S_ISUID; } /* Test for the following condition from chmod(2): * * If the calling process is not privileged (Linux: does not have * the CAP_FSETID capability), and the group of the file does not * match the effective group ID of the process or one of its * supplementary group IDs, the S_ISGID bit will be turned off, * but this will not cause an error to be returned. * * We test the actual mode being set before testing for group * membership since that is a bit more expensive. */ if (creds->caller_uid != 0 && FSAL_TEST_MASK(attr->mask, ATTR_MODE) && (attr->mode & S_ISGID) != 0 && not_in_group_list(entry->obj_handle->attrs->group)) { /* Clear S_ISGID */ attr->mode &= ~S_ISGID; } saved_acl = entry->obj_handle->attrs->acl; before = entry->obj_handle->attrs->change; fsal_status = obj_handle->obj_ops.setattrs(obj_handle, attr); if (FSAL_IS_ERROR(fsal_status)) { status = cache_inode_error_convert(fsal_status); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE from setattrs"); cache_inode_kill_entry(entry); } goto unlock; } fsal_status = obj_handle->obj_ops.getattrs(obj_handle); *attr = *entry->obj_handle->attrs; if (FSAL_IS_ERROR(fsal_status)) { status = cache_inode_error_convert(fsal_status); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE from getattrs"); cache_inode_kill_entry(entry); } goto unlock; } if (before == entry->obj_handle->attrs->change) entry->obj_handle->attrs->change++; /* Decrement refcount on saved ACL */ nfs4_acl_release_entry(saved_acl, &acl_status); if (acl_status != NFS_V4_ACL_SUCCESS) LogCrit(COMPONENT_CACHE_INODE, "Failed to release old acl, status=%d", acl_status); cache_inode_fixup_md(entry); /* Copy the complete set of new attributes out. */ *attr = *entry->obj_handle->attrs; status = CACHE_INODE_SUCCESS; unlock: if (content_locked) PTHREAD_RWLOCK_unlock(&entry->content_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); out: return status; }
cache_inode_status_t cache_inode_setattr(cache_entry_t *entry, fsal_attrib_list_t *attr, fsal_op_context_t *context, int is_open_write, cache_inode_status_t *status) { fsal_status_t fsal_status = {0, 0}; int got_content_lock = FALSE; #ifdef _USE_NFS4_ACL fsal_acl_t *saved_acl = NULL; fsal_acl_status_t acl_status = 0; #endif /* _USE_NFS4_ACL */ if ((entry->type == UNASSIGNED) || (entry->type == RECYCLED)) { LogWarn(COMPONENT_CACHE_INODE, "WARNING: unknown source entry type: type=%d, " "line %d in file %s", entry->type, __LINE__, __FILE__); *status = CACHE_INODE_BAD_TYPE; goto out; } if ((attr->asked_attributes & FSAL_ATTR_SIZE) && (entry->type != REGULAR_FILE)) { LogWarn(COMPONENT_CACHE_INODE, "Attempt to truncate non-regular file: type=%d", entry->type); *status = CACHE_INODE_BAD_TYPE; goto out; } /* Get wrlock on attr_lock and verify attrs */ *status = cache_inode_lock_trust_attrs(entry, context, TRUE); if(*status != CACHE_INODE_SUCCESS) return *status; /* Do permission checks */ if(cache_inode_check_setattr_perms(entry, attr, context, is_open_write, status) != CACHE_INODE_SUCCESS) { goto unlock; } if (attr->asked_attributes & FSAL_ATTR_SIZE) { PTHREAD_RWLOCK_WRLOCK(&entry->content_lock); got_content_lock = TRUE; } #ifdef _USE_NFS4_ACL saved_acl = entry->attributes.acl; #endif /* _USE_NFS4_ACL */ fsal_status = FSAL_setattrs(&entry->handle, context, attr, &entry->attributes); if (FSAL_IS_ERROR(fsal_status)) { *status = cache_inode_error_convert(fsal_status); if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE from setattrs"); cache_inode_kill_entry(entry); } goto unlock; } else { #ifdef _USE_NFS4_ACL /* Decrement refcount on saved ACL */ nfs4_acl_release_entry(saved_acl, &acl_status); if (acl_status != NFS_V4_ACL_SUCCESS) { LogCrit(COMPONENT_CACHE_INODE, "Failed to release old acl, status=%d", acl_status); } #endif /* _USE_NFS4_ACL */ } cache_inode_fixup_md(entry); /* Copy the complete set of new attributes out. */ *attr = entry->attributes; set_mounted_on_fileid(entry, attr, context->export_context->fe_export); *status = CACHE_INODE_SUCCESS; unlock: if(got_content_lock) { PTHREAD_RWLOCK_UNLOCK(&entry->content_lock); } PTHREAD_RWLOCK_UNLOCK(&entry->attr_lock); out: return *status; } /* cache_inode_setattr */
cache_inode_status_t cache_inode_readdir(cache_entry_t *directory, uint64_t cookie, unsigned int *nbfound, bool *eod_met, attrmask_t attrmask, cache_inode_getattr_cb_t cb, void *opaque) { /* The entry being examined */ cache_inode_dir_entry_t *dirent = NULL; /* The node in the tree being traversed */ struct avltree_node *dirent_node; /* The access mask corresponding to permission to list directory entries */ fsal_accessflags_t access_mask = (FSAL_MODE_MASK_SET(FSAL_R_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_LIST_DIR)); fsal_accessflags_t access_mask_attr = (FSAL_MODE_MASK_SET(FSAL_R_OK) | FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_LIST_DIR) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE)); cache_inode_status_t status = CACHE_INODE_SUCCESS; cache_inode_status_t attr_status; struct cache_inode_readdir_cb_parms cb_parms = { opaque, NULL, true, 0, true }; bool retry_stale = true; LogFullDebug(COMPONENT_NFS_READDIR, "Enter...."); /* readdir can be done only with a directory */ if (directory->type != DIRECTORY) { status = CACHE_INODE_NOT_A_DIRECTORY; /* no lock acquired so far, just return status */ LogFullDebug(COMPONENT_NFS_READDIR, "Not a directory"); return status; } /* cache_inode_lock_trust_attrs can return an error, and no lock will * be acquired */ status = cache_inode_lock_trust_attrs(directory, false); if (status != CACHE_INODE_SUCCESS) { LogDebug(COMPONENT_NFS_READDIR, "cache_inode_lock_trust_attrs status=%s", cache_inode_err_str(status)); return status; } /* Adjust access mask if ACL is asked for. * NOTE: We intentionally do NOT check ACE4_READ_ATTR. */ if ((attrmask & ATTR_ACL) != 0) { access_mask |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL); access_mask_attr |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL); } /* Check if user (as specified by the credentials) is authorized to read * the directory or not */ status = cache_inode_access_no_mutex(directory, access_mask); if (status != CACHE_INODE_SUCCESS) { LogFullDebug(COMPONENT_NFS_READDIR, "permission check for directory status=%s", cache_inode_err_str(status)); PTHREAD_RWLOCK_unlock(&directory->attr_lock); return status; } if (attrmask != 0) { /* Check for access permission to get attributes */ attr_status = cache_inode_access_no_mutex(directory, access_mask_attr); if (attr_status != CACHE_INODE_SUCCESS) { LogFullDebug(COMPONENT_NFS_READDIR, "permission check for attributes " "status=%s", cache_inode_err_str(attr_status)); } } else /* No attributes requested, we don't need permission */ attr_status = CACHE_INODE_SUCCESS; PTHREAD_RWLOCK_rdlock(&directory->content_lock); PTHREAD_RWLOCK_unlock(&directory->attr_lock); if (! ((directory->flags & CACHE_INODE_TRUST_CONTENT) && (directory->flags & CACHE_INODE_DIR_POPULATED))) { PTHREAD_RWLOCK_unlock(&directory->content_lock); PTHREAD_RWLOCK_wrlock(&directory->content_lock); status = cache_inode_readdir_populate(directory); if (status != CACHE_INODE_SUCCESS) { LogFullDebug(COMPONENT_NFS_READDIR, "cache_inode_readdir_populate status=%s", cache_inode_err_str(status)); goto unlock_dir; } } /* deal with initial cookie value: * 1. cookie is invalid (-should- be checked by caller) * 2. cookie is 0 (first cookie) -- ok * 3. cookie is > than highest dirent position (error) * 4. cookie <= highest dirent position but > highest cached cookie * (currently equivalent to #2, because we pre-populate the cookie * avl) * 5. cookie is in cached range -- ok */ if (cookie > 0) { /* N.B., cache_inode_avl_qp_insert_s ensures k > 2 */ if (cookie < 3) { status = CACHE_INODE_BAD_COOKIE; LogFullDebug(COMPONENT_NFS_READDIR, "Bad cookie"); goto unlock_dir; } /* we assert this can now succeed */ dirent = cache_inode_avl_lookup_k(directory, cookie, CACHE_INODE_FLAG_NEXT_ACTIVE); if (!dirent) { /* Linux (3.4, etc) has been observed to send readdir * at the offset of the last entry's cookie, and * returns no dirents to userland if that readdir * notfound or badcookie. */ if (cache_inode_avl_lookup_k (directory, cookie, CACHE_INODE_FLAG_NONE)) { /* yup, it was the last entry */ LogFullDebug(COMPONENT_NFS_READDIR, "EOD because empty result"); *eod_met = true; goto unlock_dir; } LogFullDebug(COMPONENT_NFS_READDIR, "seek to cookie=%" PRIu64 " fail", cookie); status = CACHE_INODE_BAD_COOKIE; goto unlock_dir; } /* dirent is the NEXT entry to return, since we sent * CACHE_INODE_FLAG_NEXT_ACTIVE */ dirent_node = &dirent->node_hk; } else { /* initial readdir */ dirent_node = avltree_first(&directory->object.dir.avl.t); } LogFullDebug(COMPONENT_NFS_READDIR, "About to readdir in cache_inode_readdir: directory=%p " "cookie=%" PRIu64 " collisions %d", directory, cookie, directory->object.dir.avl.collisions); /* Now satisfy the request from the cached readdir--stop when either * the requested sequence or dirent sequence is exhausted */ *nbfound = 0; *eod_met = false; for (; cb_parms.in_result && dirent_node; dirent_node = avltree_next(dirent_node)) { cache_entry_t *entry = NULL; cache_inode_status_t tmp_status = 0; dirent = avltree_container_of(dirent_node, cache_inode_dir_entry_t, node_hk); estale_retry: LogFullDebug(COMPONENT_NFS_READDIR, "Lookup direct %s", dirent->name); entry = cache_inode_get_keyed(&dirent->ckey, CIG_KEYED_FLAG_NONE, &tmp_status); if (!entry) { LogFullDebug(COMPONENT_NFS_READDIR, "Lookup returned %s", cache_inode_err_str(tmp_status)); if (retry_stale && tmp_status == CACHE_INODE_ESTALE) { LogDebug(COMPONENT_NFS_READDIR, "cache_inode_get_keyed returned %s " "for %s - retrying entry", cache_inode_err_str(tmp_status), dirent->name); retry_stale = false; /* only one retry per * dirent */ goto estale_retry; } if (tmp_status == CACHE_INODE_NOT_FOUND || tmp_status == CACHE_INODE_ESTALE) { /* Directory changed out from under us. Invalidate it, skip the name, and keep going. */ atomic_clear_uint32_t_bits( &directory->flags, CACHE_INODE_TRUST_CONTENT); LogDebug(COMPONENT_NFS_READDIR, "cache_inode_get_keyed returned %s " "for %s - skipping entry", cache_inode_err_str(tmp_status), dirent->name); continue; } else { /* Something is more seriously wrong, probably an inconsistency. */ status = tmp_status; LogCrit(COMPONENT_NFS_READDIR, "cache_inode_get_keyed returned %s " "for %s - bailing out", cache_inode_err_str(status), dirent->name); goto unlock_dir; } } LogFullDebug(COMPONENT_NFS_READDIR, "cache_inode_readdir: dirent=%p name=%s " "cookie=%" PRIu64 " (probes %d)", dirent, dirent->name, dirent->hk.k, dirent->hk.p); cb_parms.name = dirent->name; cb_parms.attr_allowed = attr_status == CACHE_INODE_SUCCESS; cb_parms.cookie = dirent->hk.k; tmp_status = cache_inode_getattr(entry, &cb_parms, cb, CB_ORIGINAL); if (tmp_status != CACHE_INODE_SUCCESS) { cache_inode_lru_unref(entry, LRU_FLAG_NONE); if (tmp_status == CACHE_INODE_ESTALE) { if (retry_stale) { LogDebug(COMPONENT_NFS_READDIR, "cache_inode_getattr returned " "%s for %s - retrying entry", cache_inode_err_str (tmp_status), dirent->name); retry_stale = false; /* only one retry * per dirent */ goto estale_retry; } /* Directory changed out from under us. Invalidate it, skip the name, and keep going. */ atomic_clear_uint32_t_bits( &directory->flags, CACHE_INODE_TRUST_CONTENT); LogDebug(COMPONENT_NFS_READDIR, "cache_inode_lock_trust_attrs " "returned %s for %s - skipping entry", cache_inode_err_str(tmp_status), dirent->name); continue; } status = tmp_status; LogCrit(COMPONENT_NFS_READDIR, "cache_inode_lock_trust_attrs returned %s for " "%s - bailing out", cache_inode_err_str(status), dirent->name); goto unlock_dir; } (*nbfound)++; cache_inode_lru_unref(entry, LRU_FLAG_NONE); if (!cb_parms.in_result) { LogDebug(COMPONENT_NFS_READDIR, "bailing out due to entry not in result"); break; } } /* We have reached the last node and every node traversed was added to the result */ LogDebug(COMPONENT_NFS_READDIR, "dirent_node = %p, nbfound = %u, in_result = %s", dirent_node, *nbfound, cb_parms.in_result ? "TRUE" : "FALSE"); if (!dirent_node && cb_parms.in_result) *eod_met = true; else *eod_met = false; unlock_dir: PTHREAD_RWLOCK_unlock(&directory->content_lock); return status; } /* cache_inode_readdir */
/** * * @brief Gets an entry by using its fsdata as a key and caches it if needed. * * Gets an entry by using its fsdata as a key and caches it if needed. * * If a cache entry is returned, its refcount is incremented by one. * * It turns out we do need cache_inode_get_located functionality for * cases like lookupp on an entry returning itself when it isn't a * root. Therefore, if the 'associated' parameter is equal to the got * cache entry, a reference count is incremented but the structure * pointed to by attr is NOT filled in. * * @param[in] fsdata File system data * @param[out] attr The attributes of the got entry * @param[in] context FSAL credentials * @param[in] associated Entry that may be equal to the got entry * @param[out] status Returned status * * @return If successful, the pointer to the entry; NULL otherwise * */ cache_entry_t * cache_inode_get(cache_inode_fsal_data_t *fsdata, fsal_attrib_list_t *attr, fsal_op_context_t *context, cache_entry_t *associated, cache_inode_status_t *status) { hash_buffer_t key, value; cache_entry_t *entry = NULL; fsal_status_t fsal_status = {0, 0}; cache_inode_create_arg_t create_arg = { .newly_created_dir = FALSE }; cache_inode_file_type_t type = UNASSIGNED; hash_error_t hrc = 0; fsal_attrib_list_t fsal_attributes; fsal_handle_t *file_handle; struct hash_latch latch; /* Set the return default to CACHE_INODE_SUCCESS */ *status = CACHE_INODE_SUCCESS; /* Turn the input to a hash key on our own. */ key.pdata = fsdata->fh_desc.start; key.len = fsdata->fh_desc.len; hrc = HashTable_GetLatch(fh_to_cache_entry_ht, &key, &value, FALSE, &latch); if ((hrc != HASHTABLE_SUCCESS) && (hrc != HASHTABLE_ERROR_NO_SUCH_KEY)) { /* This should not happened */ *status = CACHE_INODE_HASH_TABLE_ERROR; LogCrit(COMPONENT_CACHE_INODE, "Hash access failed with code %d" " - this should not have happened", hrc); return NULL; } if (hrc == HASHTABLE_SUCCESS) { /* Entry exists in the cache and was found */ entry = value.pdata; /* take an extra reference within the critical section */ if (cache_inode_lru_ref(entry, LRU_REQ_INITIAL) != CACHE_INODE_SUCCESS) { /* Dead entry. Treat like a lookup failure. */ entry = NULL; } else { if (entry == associated) { /* Take a quick exit so we don't invert lock ordering. */ HashTable_ReleaseLatched(fh_to_cache_entry_ht, &latch); return entry; } } } HashTable_ReleaseLatched(fh_to_cache_entry_ht, &latch); if (!context) { /* Upcalls have no access to fsal_op_context_t, so just return the entry without revalidating it or creating a new one. */ if (entry == NULL) { *status = CACHE_INODE_NOT_FOUND; } return entry; } if (!entry) { /* Cache miss, allocate a new entry */ file_handle = (fsal_handle_t *) fsdata->fh_desc.start; /* First, call FSAL to know what the object is */ fsal_attributes.asked_attributes = cache_inode_params.attrmask; fsal_status = FSAL_getattrs(file_handle, context, &fsal_attributes); if (FSAL_IS_ERROR(fsal_status)) { *status = cache_inode_error_convert(fsal_status); LogDebug(COMPONENT_CACHE_INODE, "cache_inode_get: cache_inode_status=%u " "fsal_status=%u,%u ", *status, fsal_status.major, fsal_status.minor); return NULL; } /* The type has to be set in the attributes */ if (!FSAL_TEST_MASK(fsal_attributes.supported_attributes, FSAL_ATTR_TYPE)) { *status = CACHE_INODE_FSAL_ERROR; return NULL; } /* Get the cache_inode file type */ type = cache_inode_fsal_type_convert(fsal_attributes.type); if (type == SYMBOLIC_LINK) { fsal_attributes.asked_attributes = cache_inode_params.attrmask; fsal_status = FSAL_readlink(file_handle, context, &create_arg.link_content, &fsal_attributes); if (FSAL_IS_ERROR(fsal_status)) { *status = cache_inode_error_convert(fsal_status); return NULL; } } if ((entry = cache_inode_new_entry(fsdata, &fsal_attributes, type, &create_arg, status)) == NULL) { return NULL; } } *status = CACHE_INODE_SUCCESS; /* This is the replacement for cache_inode_renew_entry. Rather than calling that function at the start of every cache_inode call with the inode locked, we call cache_inode_check trust to perform 'heavyweight' (timed expiration of cached attributes, getattr-based directory trust) checks the first time after getting an inode. It does all of the checks read-locked and only acquires a write lock if there's something requiring a change. There is a second light-weight check done before use of cached data that checks whether the bits saying that inode attributes or inode content are trustworthy have been cleared by, for example, FSAL_CB. To summarize, the current implementation is that policy-based trust of validity is checked once per logical series of operations at cache_inode_get, and asynchronous trust is checked with use (when the attributes are locked for reading, for example.) */ if ((*status = cache_inode_check_trust(entry, context)) != CACHE_INODE_SUCCESS) { goto out_put; } /* Set the returned attributes */ *status = cache_inode_lock_trust_attrs(entry, context); /* cache_inode_lock_trust_attrs may fail, in that case, the attributes are wrong and pthread_rwlock_unlock can't be called again */ if(*status != CACHE_INODE_SUCCESS) { goto out_put; } *attr = entry->attributes; pthread_rwlock_unlock(&entry->attr_lock); return entry; out_put: cache_inode_put(entry); entry = NULL; return entry; } /* cache_inode_get */