in turbonfs/src/rpc_readdir.cpp [603:763]
bool readdirectory_cache::remove(cookie3 cookie,
const char *filename_hint,
bool acquire_lock)
{
AZLogDebug("[{}] remove(cookie: {}, filename_hint: {})",
dir_inode->ino, cookie, filename_hint ? filename_hint : "");
// Either cookie or filename_hint (not both) must be passed.
assert((cookie == 0) == (filename_hint != nullptr));
struct nfs_inode *inode = nullptr;
/*
* dnlc_remove will be set for the case when we are deleting a file or
* directory. The cache is no longer in sync with the directory and we
* need to mark the cache as lookuponly.
*/
const bool is_dnlc_remove = (cookie == 0);
{
/*
* If acquire_lock is true, get exclusive lock on the map for removing
* the entry from the map. We use a dummy_lock for minimal code changes
* in the no-lock case.
* If you call it with acquire_lock=false make sure readdircache_lock_2
* is held in exclusive mode.
*/
std::shared_mutex dummy_lock;
std::unique_lock<std::shared_mutex> lock(
acquire_lock ? readdircache_lock_2 : dummy_lock);
if (filename_hint) {
cookie = filename_to_cookie(filename_hint);
if (cookie == 0) {
AZLogDebug("[{}] filename_hint: {}, not found",
dir_inode->ino, filename_hint);
return false;
}
AZLogDebug("[{}] filename_hint: {}, found with cookie: {}",
dir_inode->ino, filename_hint, cookie);
}
const auto it = dir_entries.find(cookie);
std::shared_ptr<struct directory_entry> dirent =
(it != dir_entries.end()) ? it->second : nullptr;
if (!dirent){
AZLogDebug("[{}] cookie: {}, not found",
dir_inode->ino, cookie);
} else {
AZLogDebug("[{}] cookie: {}, found, ino: {}",
dir_inode->ino, cookie,
dirent->nfs_inode ? dirent->nfs_inode->ino : -1);
}
/*
* Given cookie not found in the cache.
* It should not happen though since the caller would call remove()
* only after checking.
*/
if (!dirent) {
return false;
}
assert(dirent->cookie == cookie);
if (is_dnlc_remove) {
set_lookuponly();
}
/*
* This directory_entry is being removed from this readdirectory_cache,
* reduce cache_size. Note that the directory_entry may not be freed
* just yet as there could be references held to it.
*/
assert(cache_size >= dirent->get_cache_size());
cache_size -= dirent->get_cache_size();
/*
* Remove the DNLC entry.
*/
[[maybe_unused]] const int cnt = dnlc_map.erase(dirent->name);
assert(cnt == 1);
/*
* This just removes it from the cache, no destructor is called at
* this point as there is a ref held on this by the dirent shared_ptr.
* Also there could be other shared_ptr references to this
* directory_entry, but no one can take a fresh directory_entry ref
* after it's removed from dir_entries.
*/
dir_entries.erase(it);
inode = dirent->nfs_inode;
/*
* READDIR created cache entry, nothing more to do.
* directory_entry destructor will be called when dirent goes out of
* scope.
*/
if (!inode) {
AZLogDebug("[{}] Removing \"{}\", cookie {}, from readdir cache",
dir_inode->get_fuse_ino(),
dirent->name,
dirent->cookie);
return true;
}
assert(inode->magic == NFS_INODE_MAGIC);
/*
* Any inode referenced by a directory_entry added to a
* readdirectory_cache must have one reference held, by
* readdirectory_cache::add().
*/
assert(inode->dircachecnt > 0);
AZLogDebug("[{}] Removing {} \"{}\" fuse ino {}, cookie {}, from "
"readdir cache (lookupcnt={}, dircachecnt={}, "
"forget_expected={})",
dir_inode->get_fuse_ino(),
inode->is_dir() ? "directory" : "file",
dirent->name,
inode->get_fuse_ino(),
dirent->cookie,
inode->lookupcnt.load(),
inode->dircachecnt.load(),
inode->forget_expected.load());
/*
* If this is the last dircachecnt on this inode, it means
* there are no more readdirectory_cache,s referencing this
* inode. If there are no lookupcnt refs then we can free it.
* For safely freeing the inode against any races, we need to call
* decref() and for that we need to make sure we have at least one
* ref on the inode, so we call incref() before deleting the
* directory_entry. Later below we call decref() to drop the ref
* held and if that's the only ref, inode will be deleted.
*
* Once dirent goes out of scope ~directory_entry() will be caalled
* which will drop the inode's original dircachecnt.
*/
if (inode->dircachecnt == 1) {
inode->incref();
} else {
return true;
}
}
AZLogDebug("[D:{}] inode {} to be freed, after readdir cache remove",
dir_inode->get_fuse_ino(),
inode->get_fuse_ino());
/*
* Drop the extra ref held above. If it's the last ref the inode will be
* freed.
*/
assert(inode->lookupcnt > 0);
inode->decref();
return true;
}