/** * * @brief invalidates an entry in the cache * * This function invalidates the related cache entry correponding to a * FSAL handle. It is designed to be called when an FSAL upcall is * triggered. * * @param[in] handle FSAL handle for the entry to be invalidated * @param[out] status Returned status * * @retval CACHE_INODE_SUCCESS if operation is a success * @retval CACHE_INODE_INVALID_ARGUMENT bad parameter(s) as input * @retval CACHE_INODE_NOT_FOUND if entry is not cached * @retval CACHE_INODE_STATE_CONFLICT if invalidating this entry would * result is state conflict * @retval CACHE_INODE_INCONSISTENT_ENTRY if entry is not consistent * @retval Other errors shows a FSAL error. * */ cache_inode_status_t cache_inode_invalidate(cache_inode_fsal_data_t *fsal_data, cache_inode_status_t *status, uint32_t flags) { hash_buffer_t key, value; int rc = 0 ; cache_entry_t *entry; struct hash_latch latch; if (status == NULL || fsal_data == NULL) { *status = CACHE_INODE_INVALID_ARGUMENT; goto out; } /* Locate the entry in the cache */ FSAL_ExpandHandle(NULL, /* pcontext but not used... */ FSAL_DIGEST_SIZEOF, &fsal_data->fh_desc); /* Turn the input to a hash key */ key.pdata = fsal_data->fh_desc.start; key.len = fsal_data->fh_desc.len; if ((rc = HashTable_GetLatch(fh_to_cache_entry_ht, &key, &value, FALSE, &latch)) == HASHTABLE_ERROR_NO_SUCH_KEY) { /* Entry is not cached */ HashTable_ReleaseLatched(fh_to_cache_entry_ht, &latch); *status = CACHE_INODE_NOT_FOUND; return *status; } else if (rc != HASHTABLE_SUCCESS) { LogCrit(COMPONENT_CACHE_INODE, "Unexpected error %u while calling HashTable_GetLatch", rc) ; *status = CACHE_INODE_INVALID_ARGUMENT; goto out; } entry = value.pdata; if (cache_inode_lru_ref(entry, 0) != CACHE_INODE_SUCCESS) { HashTable_ReleaseLatched(fh_to_cache_entry_ht, &latch); *status = CACHE_INODE_NOT_FOUND; return *status; } HashTable_ReleaseLatched(fh_to_cache_entry_ht, &latch); PTHREAD_RWLOCK_WRLOCK(&entry->attr_lock); PTHREAD_RWLOCK_WRLOCK(&entry->content_lock); /* We can invalidate entries with state just fine. We force Cache_inode to contact the FSAL for any use of content or attributes, and if the FSAL indicates the entry is stale, it can be disposed of then. */ /* We should have a way to invalidate content and attributes separately. Or at least a way to invalidate attributes without invalidating content (since any change in content really ought to modify mtime, at least.) */ if ((flags & CACHE_INODE_INVALIDATE_CLEARBITS) != 0) atomic_clear_uint32_t_bits(&entry->flags, CACHE_INODE_TRUST_ATTRS | CACHE_INODE_DIR_POPULATED | CACHE_INODE_TRUST_CONTENT); /* The main reason for holding the lock at this point is so we don't clear the trust bits while someone is populating the directory or refreshing attributes. */ if (((flags & CACHE_INODE_INVALIDATE_CLOSE) != 0) && (entry->type == REGULAR_FILE)) { cache_inode_close(entry, NULL, (CACHE_INODE_FLAG_REALLYCLOSE | CACHE_INODE_FLAG_CONTENT_HAVE | CACHE_INODE_FLAG_CONTENT_HOLD), status); } PTHREAD_RWLOCK_UNLOCK(&entry->attr_lock); PTHREAD_RWLOCK_UNLOCK(&entry->content_lock); cache_inode_lru_unref(entry, 0); out: /* Memory copying attributes with every call is expensive. Let's not do it. */ return (*status); } /* cache_inode_invalidate */
cache_entry_t * cache_inode_create(cache_entry_t *parent, fsal_name_t *name, cache_inode_file_type_t type, fsal_accessmode_t mode, cache_inode_create_arg_t *create_arg, fsal_attrib_list_t *attr, fsal_op_context_t *context, cache_inode_status_t *status) { cache_entry_t *entry = NULL; fsal_status_t fsal_status = {0, 0}; fsal_handle_t object_handle; fsal_attrib_list_t object_attributes; cache_inode_fsal_data_t fsal_data; cache_inode_create_arg_t zero_create_arg; memset(&zero_create_arg, 0, sizeof(zero_create_arg)); memset(&fsal_data, 0, sizeof(fsal_data)); memset(&object_handle, 0, sizeof(object_handle)); if (create_arg == NULL) { create_arg = &zero_create_arg; } /* Set the return default to CACHE_INODE_SUCCESS */ *status = CACHE_INODE_SUCCESS; if ((type != REGULAR_FILE) && (type != DIRECTORY) && (type != SYMBOLIC_LINK) && (type != SOCKET_FILE) && (type != FIFO_FILE) && (type != CHARACTER_FILE) && (type != BLOCK_FILE)) { *status = CACHE_INODE_BAD_TYPE; entry = NULL; goto out; } /* Check if an entry of the same name exists */ entry = cache_inode_lookup(parent, name, attr, context, status); if (entry != NULL) { *status = CACHE_INODE_ENTRY_EXISTS; if (entry->type != type) { /* Incompatible types, returns NULL */ cache_inode_lru_unref(entry, LRU_FLAG_NONE); entry = NULL; goto out; } else { goto out; } } /* The entry doesn't exist, so we can create it. */ object_attributes.asked_attributes = cache_inode_params.attrmask; switch (type) { case REGULAR_FILE: fsal_status = FSAL_create(&parent->handle, name, context, mode, &object_handle, &object_attributes); break; case DIRECTORY: fsal_status = FSAL_mkdir(&parent->handle, name, context, mode, &object_handle, &object_attributes); break; case SYMBOLIC_LINK: fsal_status = FSAL_symlink(&parent->handle, name, &create_arg->link_content, context, mode, &object_handle, &object_attributes); break; case SOCKET_FILE: fsal_status = FSAL_mknode(&parent->handle, name, context, mode, FSAL_TYPE_SOCK, NULL, &object_handle, &object_attributes); break; case FIFO_FILE: fsal_status = FSAL_mknode(&parent->handle, name, context, mode, FSAL_TYPE_FIFO, NULL, &object_handle, &object_attributes); break; case BLOCK_FILE: fsal_status = FSAL_mknode(&parent->handle, name, context, mode, FSAL_TYPE_BLK, &create_arg->dev_spec, &object_handle, &object_attributes); break; case CHARACTER_FILE: fsal_status = FSAL_mknode(&parent->handle, name, context, mode, FSAL_TYPE_CHR, &create_arg->dev_spec, &object_handle, &object_attributes); break; default: /* we should never go there */ *status = CACHE_INODE_INCONSISTENT_ENTRY; entry = NULL; goto out; break; } /* Check for the result */ if (FSAL_IS_ERROR(fsal_status)) { if (fsal_status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_CACHE_INODE, "FSAL returned STALE on create type %d", type); cache_inode_kill_entry(parent); } *status = cache_inode_error_convert(fsal_status); entry = NULL; goto out; } fsal_data.fh_desc.start = (caddr_t) &object_handle; fsal_data.fh_desc.len = 0; FSAL_ExpandHandle(context->export_context, FSAL_DIGEST_SIZEOF, &fsal_data.fh_desc); entry = cache_inode_new_entry(&fsal_data, &object_attributes, type, create_arg, status); if (entry == NULL) { *status = CACHE_INODE_INSERT_ERROR; return NULL; } PTHREAD_RWLOCK_WRLOCK(&parent->content_lock); /* Add this entry to the directory (also takes an internal ref) */ cache_inode_add_cached_dirent(parent, name, entry, NULL, status); PTHREAD_RWLOCK_UNLOCK(&parent->content_lock); if (*status != CACHE_INODE_SUCCESS) { cache_inode_lru_unref(entry, LRU_FLAG_NONE); entry = NULL; goto out; } PTHREAD_RWLOCK_WRLOCK(&parent->attr_lock); /* Update the parent cached attributes */ cache_inode_set_time_current(&parent->attributes.mtime); parent->attributes.ctime = parent->attributes.mtime; /* if the created object is a directory, it contains a link to its parent : '..'. Thus the numlink attr must be increased. */ if (type == DIRECTORY) { ++(parent->attributes.numlinks); } PTHREAD_RWLOCK_UNLOCK(&parent->attr_lock); /* Copy up the child attributes */ *attr = object_attributes; *status = CACHE_INODE_SUCCESS; out: return entry; }
cache_inode_status_t cache_inode_operate_cached_dirent(cache_entry_t *directory, const char *name, const char *newname, cache_inode_dirent_op_t dirent_op) { cache_inode_dir_entry_t *dirent, *dirent2, *dirent3; cache_inode_status_t status = CACHE_INODE_SUCCESS; int code = 0; assert((dirent_op == CACHE_INODE_DIRENT_OP_LOOKUP) || (dirent_op == CACHE_INODE_DIRENT_OP_REMOVE) || (dirent_op == CACHE_INODE_DIRENT_OP_RENAME)); /* Sanity check */ if (directory->type != DIRECTORY) { status = CACHE_INODE_NOT_A_DIRECTORY; goto out; } LogFullDebug(COMPONENT_CACHE_INODE, "%s %p name=%s newname=%s", dirent_op == CACHE_INODE_DIRENT_OP_REMOVE ? "REMOVE" : "RENAME", directory, name, newname); /* If no active entry, do nothing */ if (directory->object.dir.nbactive == 0) { if (! ((directory->flags & CACHE_INODE_TRUST_CONTENT) && (directory->flags & CACHE_INODE_DIR_POPULATED))) { /* We cannot serve negative lookups. */ /* status == CACHE_INODE_SUCCESS; */ } else { status = CACHE_INODE_NOT_FOUND; } goto out; } dirent = cache_inode_avl_qp_lookup_s(directory, name, 1); if ((!dirent) || (dirent->flags & DIR_ENTRY_FLAG_DELETED)) { if (! ((directory->flags & CACHE_INODE_TRUST_CONTENT) && (directory->flags & CACHE_INODE_DIR_POPULATED)) || (dirent_op == CACHE_INODE_DIRENT_OP_REMOVE)) { /* We cannot serve negative lookups. */ /* status == CACHE_INODE_SUCCESS; */ } else { status = CACHE_INODE_NOT_FOUND; } LogFullDebug(COMPONENT_CACHE_INODE, "dirent=%p%s directory flags%s%s", dirent, dirent ? ((dirent->flags & DIR_ENTRY_FLAG_DELETED) ? " DELETED" : "") : "", (directory->flags & CACHE_INODE_TRUST_CONTENT) ? " TRUST" : "", (directory->flags & CACHE_INODE_DIR_POPULATED) ? " POPULATED" : ""); goto out; } /* We perform operations anyway even if CACHE_INODE_TRUST_CONTENT is clear. That way future upcalls can call in to this function to update the content to be correct. We just don't ever return a not found or exists error. */ switch (dirent_op) { case CACHE_INODE_DIRENT_OP_REMOVE: /* mark deleted */ avl_dirent_set_deleted(directory, dirent); directory->object.dir.nbactive--; break; case CACHE_INODE_DIRENT_OP_RENAME: dirent2 = cache_inode_avl_qp_lookup_s(directory, newname, 1); if (dirent2) { /* rename would cause a collision */ if (directory->flags & CACHE_INODE_TRUST_CONTENT) { /* overwrite, replace entry and expire the * old */ cache_entry_t *oldentry; avl_dirent_set_deleted(directory, dirent); cache_inode_key_dup(&dirent2->ckey, &dirent->ckey); oldentry = cache_inode_get_keyed( &dirent2->ckey, CIG_KEYED_FLAG_CACHED_ONLY, &status); if (oldentry) { /* if it is still around, mark it * gone/stale */ status = cache_inode_invalidate( oldentry, CIV_FLAGS); cache_inode_lru_unref(oldentry, LRU_FLAG_NONE); } } else status = CACHE_INODE_ENTRY_EXISTS; } else { /* Size (including terminating NUL) of the filename */ size_t newnamesize = strlen(newname) + 1; /* try to rename--no longer in-place */ dirent3 = gsh_malloc(sizeof(cache_inode_dir_entry_t) + newnamesize); memcpy(dirent3->name, newname, newnamesize); dirent3->flags = DIR_ENTRY_FLAG_NONE; cache_inode_key_dup(&dirent3->ckey, &dirent->ckey); avl_dirent_set_deleted(directory, dirent); code = cache_inode_avl_qp_insert(directory, dirent3); if (code < 0) { /* collision, tree state unchanged (unlikely) */ status = CACHE_INODE_ENTRY_EXISTS; /* dirent is on persist tree, undelete it */ avl_dirent_clear_deleted(directory, dirent); /* dirent3 was never inserted */ gsh_free(dirent3); } } /* !found */ break; case CACHE_INODE_DIRENT_OP_LOOKUP: /* Lookup was performed before switch statement */ break; } out: return status; } /* cache_inode_operate_cached_dirent */
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 */
/** * * cache_inode_put: release logical reference to a cache entry conferred by * a previous call to cache_inode_get (cache_inode_get_located). * * The result is typically to decrement the reference count on entry, but * additional side effects include LRU adjustment, movement to/from the * protected LRU partition, or recyling if the caller has raced an operation * which made entry unreachable (and this current caller has the last * reference). Caller MUST NOT make further accesses to the memory pointed * to by entry. * * @param[in] entry Cache entry being returned * * @return CACHE_INDOE_STATUS or error codes * */ void cache_inode_put(cache_entry_t *entry) { cache_inode_lru_unref(entry, LRU_FLAG_NONE); }