blob: b183b84e45953e27776249331b59f0129e9f9f03 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/f2fs/f2fs.h"
namespace f2fs {
VnodeCache::VnodeCache() = default;
VnodeCache::~VnodeCache() {
Reset();
{
std::lock_guard list_lock(list_lock_);
std::lock_guard table_lock(table_lock_);
ZX_ASSERT(dirty_list_.is_empty());
ZX_ASSERT(vnode_table_.is_empty());
ZX_ASSERT(ndirty_ == 0);
ZX_ASSERT(ndirty_dir_ == 0);
}
}
void VnodeCache::Reset() {
{
std::lock_guard list_lock(list_lock_);
ZX_ASSERT(dirty_list_.is_empty());
}
ForAllVnodes([this](fbl::RefPtr<VnodeF2fs>& vnode) { return Evict(vnode.get()); });
}
zx_status_t VnodeCache::ForAllVnodes(VnodeCallback callback) {
fbl::RefPtr<VnodeF2fs> prev_vnode;
while (true) {
fbl::RefPtr<VnodeF2fs> vnode;
// Scope the lock to prevent letting fbl::RefPtr<VnodeF2fs> destructors from running while
// it is held.
{
std::lock_guard lock(table_lock_);
if (vnode_table_.is_empty()) {
return ZX_OK;
}
VnodeF2fs* raw_vnode = nullptr;
if (prev_vnode == nullptr) {
// Acquire the first node from the front of the cache...
raw_vnode = &vnode_table_.front();
} else {
// ... Acquire all subsequent nodes by iterating from the lower bound of the current node.
auto current = vnode_table_.lower_bound(prev_vnode->GetKey());
if (current == vnode_table_.end()) {
return ZX_OK;
} else if (current.CopyPointer() != prev_vnode.get()) {
raw_vnode = current.CopyPointer();
} else {
auto next = ++current;
if (next == vnode_table_.end()) {
return ZX_OK;
}
raw_vnode = next.CopyPointer();
}
}
if (raw_vnode->IsActive()) {
vnode = fbl::MakeRefPtrUpgradeFromRaw(raw_vnode, table_lock_);
if (vnode == nullptr) {
// When it is being recycled, we should wait for deactivation or eviction.
raw_vnode->WaitForDeactive(table_lock_);
continue;
}
} else {
// When it is inactive, it is safe to make Refptr.
vnode = fbl::ImportFromRawPtr(raw_vnode);
vnode->Activate();
}
}
zx_status_t status = callback(vnode);
prev_vnode = std::move(vnode);
if (status == ZX_ERR_STOP) {
break;
}
if (status != ZX_ERR_NEXT && status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t VnodeCache::ForDirtyVnodesIf(VnodeCallback cb, VnodeCallback cb_if) {
std::vector<fbl::RefPtr<VnodeF2fs>> dirty_vnodes;
{
std::lock_guard lock(list_lock_);
for (auto iter = dirty_list_.begin(); iter != dirty_list_.end(); ++iter) {
fbl::RefPtr<VnodeF2fs> vnode = iter.CopyPointer();
if (cb_if == nullptr || cb_if(vnode) == ZX_OK) {
dirty_vnodes.push_back(std::move(vnode));
}
}
}
if (dirty_vnodes.empty()) {
return ZX_OK;
}
for (auto& vnode : dirty_vnodes) {
if (zx_status_t status = cb(vnode); status == ZX_ERR_STOP) {
break;
} else if (status != ZX_ERR_NEXT && status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
void VnodeCache::Downgrade(VnodeF2fs* raw_vnode) {
std::lock_guard lock(table_lock_);
// We resurrect it, so it can be used without strong references in the inactive state
raw_vnode->ResurrectRef();
fbl::RefPtr<VnodeF2fs> vnode = fbl::ImportFromRawPtr(raw_vnode);
// If it has been evicted already, it should be freed.
if (!(*raw_vnode).fbl::WAVLTreeContainable<VnodeF2fs*>::InContainer()) {
ZX_ASSERT(!(*raw_vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer());
delete fbl::ExportToRawPtr(&vnode);
return;
}
// TODO(https://fxbug.dev/42070947): Need to adjust the size of vnode_table_ according to memory
// pressure
// It is leaked to keep alive in vnode_table
[[maybe_unused]] auto leak = fbl::ExportToRawPtr(&vnode);
raw_vnode->Deactivate();
}
zx_status_t VnodeCache::Lookup(const ino_t& ino, fbl::RefPtr<VnodeF2fs>* out) {
fbl::RefPtr<VnodeF2fs> vnode;
{
std::lock_guard lock(table_lock_);
if (zx_status_t status = LookupUnsafe(ino, &vnode); status != ZX_OK) {
return status;
}
}
*out = std::move(vnode);
return ZX_OK;
}
zx_status_t VnodeCache::LookupUnsafe(const ino_t& ino, fbl::RefPtr<VnodeF2fs>* out) {
while (true) {
auto raw_ptr = vnode_table_.find(ino).CopyPointer();
if (raw_ptr != nullptr) {
// When the vnode is active, we should check if it is being recycled.
if (raw_ptr->IsActive()) {
*out = fbl::MakeRefPtrUpgradeFromRaw(raw_ptr, table_lock_);
if (*out == nullptr) {
// When it is being recycled, we should wait for it to be deactivate.
raw_ptr->WaitForDeactive(table_lock_);
continue;
}
return ZX_OK;
}
// When it is inactive, it is safe to make Refptr.
*out = fbl::ImportFromRawPtr(raw_ptr);
(*out)->Activate();
return ZX_OK;
}
break;
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t VnodeCache::Evict(VnodeF2fs* vnode) {
ZX_ASSERT(!(*vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer());
std::lock_guard lock(table_lock_);
return EvictUnsafe(vnode);
}
zx_status_t VnodeCache::EvictUnsafe(VnodeF2fs* vnode) {
if (!(*vnode).fbl::WAVLTreeContainable<VnodeF2fs*>::InContainer()) {
FX_LOGS(INFO) << "EvictUnsafe: " << vnode->GetNameView() << "(" << vnode->GetKey()
<< ") cannot be found in vnode table";
return ZX_ERR_NOT_FOUND;
}
ZX_ASSERT_MSG(vnode_table_.erase(*vnode) != nullptr, "Cannot find vnode (%u)", vnode->GetKey());
return ZX_OK;
}
zx_status_t VnodeCache::Add(VnodeF2fs* vnode) {
{
std::lock_guard lock(table_lock_);
if ((*vnode).fbl::WAVLTreeContainable<VnodeF2fs*>::InContainer()) {
return ZX_ERR_ALREADY_EXISTS;
}
vnode_table_.insert(vnode);
}
return ZX_OK;
}
zx_status_t VnodeCache::AddDirty(VnodeF2fs& vnode) {
std::lock_guard lock(list_lock_);
if (vnode.fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer()) {
return ZX_ERR_ALREADY_EXISTS;
}
fbl::RefPtr<VnodeF2fs> vnode_refptr = fbl::MakeRefPtrUpgradeFromRaw(&vnode, list_lock_);
dirty_list_.push_back(std::move(vnode_refptr));
if (vnode.IsDir()) {
++ndirty_dir_;
}
++ndirty_;
return ZX_OK;
}
bool VnodeCache::IsDirty(VnodeF2fs& vnode) {
fs::SharedLock lock(list_lock_);
if (vnode.fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer()) {
return true;
}
return false;
}
zx_status_t VnodeCache::RemoveDirty(VnodeF2fs* vnode) {
std::lock_guard lock(list_lock_);
return RemoveDirtyUnsafe(vnode);
}
zx_status_t VnodeCache::RemoveDirtyUnsafe(VnodeF2fs* vnode) {
ZX_ASSERT(vnode != nullptr);
if (!(*vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer()) {
return ZX_ERR_NOT_FOUND;
}
auto vnode_refptr = dirty_list_.erase(*vnode);
if (vnode_refptr->IsDir()) {
--ndirty_dir_;
}
--ndirty_;
return ZX_OK;
}
} // namespace f2fs