blob: ef68a0b240b3cf91ee78bffb6a3eaadbc8d1cfa8 [file] [log] [blame]
/*
* vim:noexpandtab:shiftwidth=8:tabstop=8:
*
* Copyright CEA/DAM/DIF (2008)
* contributeur : Philippe DENIEL philippe.deniel@cea.fr
* Thomas LEIBOVICI thomas.leibovici@cea.fr
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* ---------------------------------------
*/
/**
* @addtogroup cache_inode
* @{
*/
/**
* @file cache_inode_readdir.c
* @brief Reads the content of a directory
*
* Reads the content of a directory, also includes support functions
* for cached directories.
*
*
*/
#include "config.h"
#include "abstract_atomic.h"
#include "log.h"
#include "hashtable.h"
#include "fsal.h"
#include "cache_inode.h"
#include "cache_inode_lru.h"
#include "cache_inode_avl.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <time.h>
#include <pthread.h>
#include <assert.h>
/**
* @brief Invalidates all cached entries for a directory
*
* Invalidates all the entries for a cached directory. The content
* lock must be held when this function is called.
*
* @param[in,out] entry The directory to be managed
*
* @return CACHE_INODE_SUCCESS or errors.
*
*/
cache_inode_status_t
cache_inode_invalidate_all_cached_dirent(cache_entry_t *entry)
{
cache_inode_status_t status = CACHE_INODE_SUCCESS;
/* Only DIRECTORY entries are concerned */
if (entry->type != DIRECTORY) {
status = CACHE_INODE_NOT_A_DIRECTORY;
return status;
}
/* Get rid of entries cached in the DIRECTORY */
cache_inode_release_dirents(entry, CACHE_INODE_AVL_BOTH);
/* Now we can trust the content */
atomic_set_uint32_t_bits(&entry->flags, CACHE_INODE_TRUST_CONTENT);
status = CACHE_INODE_SUCCESS;
return status;
}
/**
* @brief Perform an operation on it on a cached entry
*
* This function looks up an entry in the drectory cache and performs
* the indicated operation. If the directory has not been populated,
* it will not return not found errors.
*
* The caller must hold the content lock on the directory.
*
* @param[in] directory The directory to be operated upon
* @param[in] name The name of the relevant entry
* @param[in] newname The new name for renames
* @param[in] dirent_op The operation (LOOKUP, REMOVE, or RENAME) to
* perform
*
* @retval CACHE_INODE_SUCCESS on success or failure in an unpopulated
* directory.
* @retval CACHE_INODE_BAD_TYPE if the supplied cache entry is not a
* directory.
* @retval CACHE_INODE_NOT_FOUND on lookup failure in a populated
* directory.
* @retval CACHE_INODE_ENTRY_EXISTS on rename collission in a
* populated directory.
*/
#define CIV_FLAGS (CACHE_INODE_INVALIDATE_ATTRS| \
CACHE_INODE_INVALIDATE_CONTENT)
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 */
/**
*
* @brief Adds a directory entry to a cached directory.
*
* This function adds a new directory entry to a directory. Directory
* entries have only weak references, so they do not prevent recycling
* or freeing the entry they locate. This function may be called
* either once (for handling creation) or iteratively in directory
* population.
*
* @param[in,out] parent Cache entry of the directory being updated
* @param[in] name The name to add to the entry
* @param[in] entry The cache entry associated with name
* @param[out] dir_entry The directory entry newly added (optional)
*
* @return CACHE_INODE_SUCCESS or errors on failure.
*/
cache_inode_status_t
cache_inode_add_cached_dirent(cache_entry_t *parent,
const char *name,
cache_entry_t *entry,
cache_inode_dir_entry_t **dir_entry)
{
cache_inode_dir_entry_t *new_dir_entry = NULL;
size_t namesize = strlen(name) + 1;
int code = 0;
cache_inode_status_t status = CACHE_INODE_SUCCESS;
/* Sanity check */
if (parent->type != DIRECTORY) {
status = CACHE_INODE_NOT_A_DIRECTORY;
return status;
}
/* in cache inode avl, we always insert on pentry_parent */
new_dir_entry = gsh_malloc(sizeof(cache_inode_dir_entry_t) + namesize);
if (new_dir_entry == NULL) {
status = CACHE_INODE_MALLOC_ERROR;
return status;
}
new_dir_entry->flags = DIR_ENTRY_FLAG_NONE;
memcpy(&new_dir_entry->name, name, namesize);
cache_inode_key_dup(&new_dir_entry->ckey, &entry->fh_hk.key);
/* add to avl */
code = cache_inode_avl_qp_insert(parent, new_dir_entry);
if (code < 0) {
/* collision, tree not updated--release both pool objects and
* return err */
gsh_free(new_dir_entry->ckey.kv.addr);
gsh_free(new_dir_entry);
status = CACHE_INODE_ENTRY_EXISTS;
return status;
}
if (dir_entry)
*dir_entry = new_dir_entry;
/* we're going to succeed */
parent->object.dir.nbactive++;
return status;
}
/**
* @brief Removes an entry from a cached directory.
*
* This function removes the named entry from a cached directory. The
* caller must hold the content lock.
*
* @param[in,out] directory The cache entry representing the directory
* @param[in] name The name indicating the entry to remove
*
* @retval CACHE_INODE_SUCCESS on success.
* @retval CACHE_INODE_BAD_TYPE if directory is not a directory.
* @retval The result of cache_inode_operate_cached_dirent
*
*/
cache_inode_status_t
cache_inode_remove_cached_dirent(cache_entry_t *directory,
const char *name)
{
cache_inode_status_t status = CACHE_INODE_SUCCESS;
/* Sanity check */
if (directory->type != DIRECTORY) {
status = CACHE_INODE_NOT_A_DIRECTORY;
return status;
}
status =
cache_inode_operate_cached_dirent(directory, name, NULL,
CACHE_INODE_DIRENT_OP_REMOVE);
return status;
}
/**
* @brief State to be passed to FSAL readdir callbacks
*/
struct cache_inode_populate_cb_state {
cache_entry_t *directory;
cache_inode_status_t *status;
uint64_t offset_cookie;
};
/**
* @brief Populate a single dir entry
*
* This callback serves to populate a single dir entry from the
* readdir.
*
* @param[in] name Name of the directory entry
* @param[in,out] dir_state Callback state
* @param[in] cookie Directory cookie
*
* @retval true if more entries are requested
* @retval false if no more should be sent and the last was not processed
*/
static bool
populate_dirent(const char *name, void *dir_state,
fsal_cookie_t cookie)
{
struct cache_inode_populate_cb_state *state =
(struct cache_inode_populate_cb_state *)dir_state;
struct fsal_obj_handle *entry_hdl;
cache_inode_dir_entry_t *new_dir_entry = NULL;
cache_entry_t *cache_entry = NULL;
fsal_status_t fsal_status = { 0, 0 };
struct fsal_obj_handle *dir_hdl = state->directory->obj_handle;
fsal_status = dir_hdl->obj_ops.lookup(dir_hdl, name, &entry_hdl);
if (FSAL_IS_ERROR(fsal_status)) {
*state->status = cache_inode_error_convert(fsal_status);
if (*state->status == CACHE_INODE_FSAL_XDEV) {
LogInfo(COMPONENT_NFS_READDIR,
"Ignoring XDEV entry %s",
name);
*state->status = CACHE_INODE_SUCCESS;
return true;
}
LogInfo(COMPONENT_CACHE_INODE,
"Lookup failed on %s in dir %p with %s",
name, dir_hdl, cache_inode_err_str(*state->status));
return !cache_param.retry_readdir;
}
LogFullDebug(COMPONENT_NFS_READDIR, "Creating entry for %s", name);
*state->status =
cache_inode_new_entry(entry_hdl, CACHE_INODE_FLAG_NONE,
&cache_entry);
if (cache_entry == NULL) {
*state->status = CACHE_INODE_NOT_FOUND;
/* we do not free entry_hdl because it is consumed by
cache_inode_new_entry */
LogEvent(COMPONENT_NFS_READDIR,
"cache_inode_new_entry failed with %s",
cache_inode_err_str(*state->status));
return false;
}
if (cache_entry->type == DIRECTORY) {
/* Insert Parent's key */
cache_inode_key_dup(&cache_entry->object.dir.parent,
&state->directory->fh_hk.key);
}
*state->status =
cache_inode_add_cached_dirent(state->directory, name, cache_entry,
&new_dir_entry);
/* return initial ref */
cache_inode_put(cache_entry);
if ((*state->status != CACHE_INODE_SUCCESS) &&
(*state->status != CACHE_INODE_ENTRY_EXISTS)) {
LogEvent(COMPONENT_NFS_READDIR,
"cache_inode_add_cached_dirent failed with %s",
cache_inode_err_str(*state->status));
return false;
}
return true;
}
/**
*
* @brief Cache complete directory contents
*
* This function reads a complete directory from the FSAL and caches
* both the names and filess. The content lock must be held on the
* directory being read.
*
* @param[in] directory Entry for the parent directory to be read
*
* @return CACHE_INODE_SUCCESS or errors.
*/
static cache_inode_status_t
cache_inode_readdir_populate(cache_entry_t *directory)
{
fsal_status_t fsal_status;
bool eod = false;
cache_inode_status_t status = CACHE_INODE_SUCCESS;
struct cache_inode_populate_cb_state state;
/* Only DIRECTORY entries are concerned */
if (directory->type != DIRECTORY) {
status = CACHE_INODE_NOT_A_DIRECTORY;
LogDebug(COMPONENT_NFS_READDIR,
"CACHE_INODE_NOT_A_DIRECTORY");
return status;
}
if ((directory->flags & CACHE_INODE_DIR_POPULATED)
&& (directory->flags & CACHE_INODE_TRUST_CONTENT)) {
LogFullDebug(COMPONENT_NFS_READDIR,
"CACHE_INODE_DIR_POPULATED and CACHE_INODE_TRUST_CONTENT");
status = CACHE_INODE_SUCCESS;
return status;
}
/* Invalidate all the dirents */
status = cache_inode_invalidate_all_cached_dirent(directory);
if (status != CACHE_INODE_SUCCESS) {
LogDebug(COMPONENT_NFS_READDIR,
"cache_inode_invalidate_all_cached_dirent status=%s",
cache_inode_err_str(status));
return status;
}
state.directory = directory;
state.status = &status;
state.offset_cookie = 0;
fsal_status =
directory->obj_handle->obj_ops.readdir(directory->obj_handle,
NULL,
(void *)&state,
populate_dirent,
&eod);
if (FSAL_IS_ERROR(fsal_status)) {
if (fsal_status.major == ERR_FSAL_STALE) {
LogEvent(COMPONENT_NFS_READDIR,
"FSAL returned STALE from readdir.");
cache_inode_kill_entry(directory);
}
status = cache_inode_error_convert(fsal_status);
LogDebug(COMPONENT_NFS_READDIR,
"FSAL readdir status=%s",
cache_inode_err_str(status));
return status;
}
/* we were supposed to read to the end.... */
if (!eod && cache_param.retry_readdir) {
LogInfo(COMPONENT_NFS_READDIR,
"Readdir didn't reach eod on dir %p (status %s)",
directory->obj_handle, cache_inode_err_str(status));
status = CACHE_INODE_DELAY;
} else if (eod) {
/* End of work */
atomic_set_uint32_t_bits(&directory->flags,
CACHE_INODE_DIR_POPULATED);
/* override status in case it was set to a
* minor error in the callback */
status = CACHE_INODE_SUCCESS;
}
/* If !eod (and fsal_status isn't an error), then the only error path
* is through a callback failure and status has been set the
* populate_dirent callback */
return status;
} /* cache_inode_readdir_populate */
/**
* @brief Reads a directory
*
* This function iterates over the cached directory entries (possibly
* after populating the cache) and invokes a supplied callback
* function for each one.
*
* The caller must not hold the attribute or content locks on
* directory.
*
* @param[in] directory The directory to be read
* @param[in] cookie Starting cookie for the readdir operation
* @param[out] nbfound Number of entries returned.
* @param[out] eod_met Whether the end of directory was met
* @param[in] attrmask Attributes requested, used for permission checking
* really all that matters is ATTR_ACL and any attrs
* at all, specifics never actually matter.
* @param[in] cb The callback function to receive entries
* @param[in] opaque A pointer passed to be passed in
* cache_inode_readdir_cb_parms
*
* @retval CACHE_INODE_SUCCESS if operation is a success
* @retval CACHE_INODE_BAD_TYPE if entry is not related to a directory
*/
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 */
/** @} */