blob: b70e9029cf1de8b5c4501da73b597dcf8ad3042f [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.
#ifndef SRC_STORAGE_F2FS_F2FS_H_
#define SRC_STORAGE_F2FS_F2FS_H_
// clang-format off
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/trace/event.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <fidl/fuchsia.fs/cpp/wire.h>
#include <fidl/fuchsia.process.lifecycle/cpp/wire.h>
#include <fbl/auto_lock.h>
#include <fbl/condition_variable.h>
#include <fbl/mutex.h>
#include <fidl/fuchsia.fs.startup/cpp/wire.h>
#include <fcntl.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/zx/result.h>
#include <fbl/algorithm.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/macros.h>
#include <fbl/ref_ptr.h>
#include <fbl/string_buffer.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <semaphore>
#include "src/storage/lib/vfs/cpp/vfs.h"
#include "src/storage/lib/vfs/cpp/vnode.h"
#include "src/storage/lib/vfs/cpp/transaction/buffered_operations_builder.h"
#include "src/storage/lib/vfs/cpp/paged_vfs.h"
#include "src/storage/lib/vfs/cpp/paged_vnode.h"
#include "src/storage/lib/vfs/cpp/watcher.h"
#include "src/storage/lib/vfs/cpp/shared_mutex.h"
#include "src/storage/lib/vfs/cpp/service.h"
#include "lib/inspect/cpp/inspect.h"
#include "src/storage/lib/vfs/cpp/fuchsia_vfs.h"
#include "src/storage/lib/vfs/cpp/inspect/inspect_tree.h"
#include "src/storage/f2fs/common.h"
#include "src/storage/f2fs/f2fs_layout.h"
#include "src/storage/f2fs/bcache.h"
#include "src/storage/f2fs/mount.h"
#include "src/storage/f2fs/f2fs_internal.h"
#include "src/storage/f2fs/storage_buffer.h"
#include "src/storage/f2fs/writeback.h"
#include "src/storage/f2fs/reader.h"
#include "src/storage/f2fs/extent_cache.h"
#include "src/storage/f2fs/vnode.h"
#include "src/storage/f2fs/vnode_cache.h"
#include "src/storage/f2fs/dir.h"
#include "src/storage/f2fs/file.h"
#include "src/storage/f2fs/node.h"
#include "src/storage/f2fs/segment.h"
#include "src/storage/f2fs/gc.h"
#include "src/storage/f2fs/mkfs.h"
#include "src/storage/f2fs/fsck.h"
#include "src/storage/f2fs/service/admin.h"
#include "src/storage/f2fs/service/startup.h"
#include "src/storage/f2fs/service/lifecycle.h"
#include "src/storage/f2fs/component_runner.h"
#include "src/storage/f2fs/dir_entry_cache.h"
#include "src/storage/f2fs/inspect.h"
#include "src/storage/f2fs/memory_watcher.h"
// clang-format on
namespace f2fs {
zx::result<std::unique_ptr<Superblock>> LoadSuperblock(BcacheMapper &bc);
class F2fs final {
public:
// Not copyable or moveable
F2fs(const F2fs &) = delete;
F2fs &operator=(const F2fs &) = delete;
F2fs(F2fs &&) = delete;
F2fs &operator=(F2fs &&) = delete;
explicit F2fs(FuchsiaDispatcher dispatcher, std::unique_ptr<f2fs::BcacheMapper> bc,
const MountOptions &mount_options, PlatformVfs *vfs);
static zx::result<std::unique_ptr<F2fs>> Create(FuchsiaDispatcher dispatcher,
std::unique_ptr<f2fs::BcacheMapper> bc,
const MountOptions &options, PlatformVfs *vfs);
zx::result<fs::FilesystemInfo> GetFilesystemInfo();
DirEntryCache &GetDirEntryCache() { return dir_entry_cache_; }
InspectTree &GetInspectTree() { return *inspect_tree_; }
VnodeCache &GetVCache() { return vnode_cache_; }
zx_status_t InsertVnode(VnodeF2fs *vn) { return vnode_cache_.Add(vn); }
zx_status_t EvictVnode(VnodeF2fs *vn) { return vnode_cache_.Evict(vn); }
zx_status_t LookupVnode(ino_t ino, fbl::RefPtr<VnodeF2fs> *out) {
return vnode_cache_.Lookup(ino, out);
}
zx::result<std::unique_ptr<f2fs::BcacheMapper>> TakeBc() {
if (!bc_) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
return zx::ok(std::move(bc_));
}
BcacheMapper &GetBc() const {
ZX_DEBUG_ASSERT(bc_ != nullptr);
return *bc_;
}
SuperblockInfo &GetSuperblockInfo() const {
ZX_DEBUG_ASSERT(superblock_info_ != nullptr);
return *superblock_info_;
}
SegmentManager &GetSegmentManager() const {
ZX_DEBUG_ASSERT(segment_manager_ != nullptr);
return *segment_manager_;
}
NodeManager &GetNodeManager() const {
ZX_DEBUG_ASSERT(node_manager_ != nullptr);
return *node_manager_;
}
GcManager &GetGcManager() const {
ZX_DEBUG_ASSERT(gc_manager_ != nullptr);
return *gc_manager_;
}
PlatformVfs *vfs() const { return vfs_; }
// For testing Reset() and TakeBc()
bool IsValid() const;
zx_status_t LoadSuper(std::unique_ptr<Superblock> sb);
void Reset();
zx_status_t GrabMetaPage(pgoff_t index, LockedPage *out);
zx_status_t GetMetaPage(pgoff_t index, LockedPage *out);
bool CanReclaim() const;
bool IsTearDown() const;
void SetTearDown();
zx_status_t CheckOrphanSpace();
void PurgeOrphanInode(nid_t ino);
int PurgeOrphanInodes();
void WriteOrphanInodes(block_t start_blk);
zx_status_t GetValidCheckpoint();
zx_status_t ValidateCheckpoint(block_t cp_addr, uint64_t *version, LockedPage *out);
uint32_t GetFreeSectionsForDirtyPages();
void PutSuper();
void Sync(SyncCallback closure = nullptr) __TA_EXCLUDES(f2fs::GetGlobalLock());
zx_status_t SyncFs(bool bShutdown = false) __TA_EXCLUDES(f2fs::GetGlobalLock());
zx_status_t DoCheckpoint(bool is_umount) __TA_REQUIRES(f2fs::GetGlobalLock());
zx_status_t WriteCheckpoint(bool blocked, bool is_umount) __TA_REQUIRES(f2fs::GetGlobalLock());
// recovery.cc
// For the list of fsync inodes, used only during recovery
class FsyncInodeEntry : public fbl::DoublyLinkedListable<std::unique_ptr<FsyncInodeEntry>> {
public:
explicit FsyncInodeEntry(fbl::RefPtr<VnodeF2fs> vnode_refptr)
: vnode_(std::move(vnode_refptr)) {}
FsyncInodeEntry() = delete;
FsyncInodeEntry(const FsyncInodeEntry &) = delete;
FsyncInodeEntry &operator=(const FsyncInodeEntry &) = delete;
FsyncInodeEntry(FsyncInodeEntry &&) = delete;
FsyncInodeEntry &operator=(FsyncInodeEntry &&) = delete;
block_t GetLastDnodeBlkaddr() const { return last_dnode_blkaddr_; }
void SetLastDnodeBlkaddr(block_t blkaddr) { last_dnode_blkaddr_ = blkaddr; }
VnodeF2fs &GetVnode() const { return *vnode_; }
private:
fbl::RefPtr<VnodeF2fs> vnode_ = nullptr; // vfs inode pointer
block_t last_dnode_blkaddr_ = 0; // block address locating the last dnode
};
using FsyncInodeList = fbl::DoublyLinkedList<std::unique_ptr<FsyncInodeEntry>>;
FsyncInodeEntry *GetFsyncInode(FsyncInodeList &inode_list, nid_t ino);
zx_status_t RecoverDentry(NodePage &ipage, VnodeF2fs &vnode);
zx_status_t RecoverInode(VnodeF2fs &vnode, NodePage &node_page);
zx_status_t FindFsyncDnodes(FsyncInodeList &inode_list);
void DestroyFsyncDnodes(FsyncInodeList &inode_list);
void CheckIndexInPrevNodes(block_t blkaddr);
void DoRecoverData(VnodeF2fs &vnode, NodePage &page);
void RecoverData(FsyncInodeList &inode_list, CursegType type);
void RecoverFsyncData();
VnodeF2fs &GetNodeVnode() { return *node_vnode_; }
VnodeF2fs &GetMetaVnode() { return *meta_vnode_; }
zx::result<fbl::RefPtr<VnodeF2fs>> GetRootVnode() {
if (root_vnode_) {
return zx::ok(root_vnode_);
}
return zx::error(ZX_ERR_BAD_STATE);
}
// For testing
void SetVfsForTests(std::unique_ptr<PlatformVfs> vfs) { vfs_for_tests_ = std::move(vfs); }
zx::result<std::unique_ptr<PlatformVfs>> TakeVfsForTests() {
if (vfs_for_tests_) {
return zx::ok(std::move(vfs_for_tests_));
}
return zx::error(ZX_ERR_UNAVAILABLE);
}
zx::result<> MakeReadOperations(std::vector<LockedPage> &pages, std::vector<block_t> &addrs,
PageType type, bool is_sync = true);
zx::result<> MakeReadOperation(LockedPage &page, block_t blk_addr, PageType type,
bool is_sync = true);
zx::result<> MakeReadOperations(zx::vmo &vmo, std::vector<block_t> &addrs, PageType type,
bool is_sync = true);
zx_status_t MakeTrimOperation(block_t blk_addr, block_t nblocks) const;
void ScheduleWriter(sync_completion_t *completion = nullptr, PageList pages = {},
bool flush = true) {
writer_->ScheduleWriteBlocks(completion, std::move(pages), flush);
}
void ScheduleWriter(fpromise::promise<> task) { writer_->ScheduleTask(std::move(task)); }
void ScheduleWriteback(size_t num_pages = kDefaultBlocksPerSegment);
zx::result<> WaitForWriteback() {
if (!writeback_flag_.try_acquire_for(std::chrono::seconds(kWriteTimeOut))) {
return zx::error(ZX_ERR_TIMED_OUT);
}
writeback_flag_.release();
return zx::ok();
}
std::atomic_flag &GetStopReclaimFlag() { return stop_reclaim_flag_; }
bool StopWriteback() {
// release-acquire ordering with MemoryPressureWatcher::OnLevelChanged().
auto level = current_memory_pressure_level_.load(std::memory_order_acquire);
return level == MemoryPressure::kLow ||
!superblock_info_->GetPageCount(CountType::kDirtyData) ||
(level == MemoryPressure::kUnknown &&
superblock_info_->GetPageCount(CountType::kDirtyData) < kMaxDirtyDataPages / 4);
}
bool HasNotEnoughMemory() {
// release-acquire ordering with MemoryPressureWatcher::OnLevelChanged().
auto level = current_memory_pressure_level_.load(std::memory_order_acquire);
return (level > MemoryPressure::kLow &&
superblock_info_->GetPageCount(CountType::kDirtyData)) ||
(level == MemoryPressure::kUnknown &&
superblock_info_->GetPageCount(CountType::kDirtyData) >= kMaxDirtyDataPages);
}
void WaitForAvailableMemory() {
while (HasNotEnoughMemory()) {
ScheduleWriteback();
ZX_ASSERT(WaitForWriteback().is_ok());
}
}
bool IsOnRecovery() const { return on_recovery_; }
void SetOnRecovery() { on_recovery_ = true; }
void ClearOnRecovery() { on_recovery_ = false; }
void AddToVnodeSet(VnodeSet type, nid_t ino) __TA_EXCLUDES(vnode_set_mutex_);
void RemoveFromVnodeSet(VnodeSet type, nid_t ino) __TA_EXCLUDES(vnode_set_mutex_);
bool FindVnodeSet(VnodeSet type, nid_t ino) __TA_EXCLUDES(vnode_set_mutex_);
size_t GetVnodeSetSize(VnodeSet type) __TA_EXCLUDES(vnode_set_mutex_);
void ForAllVnodeSet(VnodeSet type, fit::function<void(nid_t)> callback)
__TA_EXCLUDES(vnode_set_mutex_);
void ClearVnodeSet() __TA_EXCLUDES(vnode_set_mutex_);
private:
void StartMemoryPressureWatcher();
void FlushDirsAndNodes() __TA_REQUIRES(f2fs::GetGlobalLock());
pgoff_t FlushDirtyMetaPages(bool is_commit) __TA_REQUIRES(f2fs::GetGlobalLock());
pgoff_t FlushDirtyDataPages(WritebackOperation &operation, bool wait_writer = false)
__TA_REQUIRES_SHARED(f2fs::GetGlobalLock());
zx::result<pgoff_t> FlushDirtyNodePages(WritebackOperation &operation)
__TA_REQUIRES(f2fs::GetGlobalLock());
std::atomic_flag teardown_flag_ = ATOMIC_FLAG_INIT;
std::atomic_flag stop_reclaim_flag_ = ATOMIC_FLAG_INIT;
std::binary_semaphore writeback_flag_{1};
FuchsiaDispatcher dispatcher_;
PlatformVfs *const vfs_ = nullptr;
std::unique_ptr<f2fs::BcacheMapper> bc_;
// for unittest
std::unique_ptr<PlatformVfs> vfs_for_tests_;
MountOptions mount_options_;
std::unique_ptr<SuperblockInfo> superblock_info_;
std::unique_ptr<SegmentManager> segment_manager_;
std::unique_ptr<NodeManager> node_manager_;
std::unique_ptr<GcManager> gc_manager_;
std::unique_ptr<Reader> reader_;
std::unique_ptr<Writer> writer_;
std::unique_ptr<VnodeF2fs> meta_vnode_;
std::unique_ptr<VnodeF2fs> node_vnode_;
fbl::RefPtr<VnodeF2fs> root_vnode_;
VnodeCache vnode_cache_;
DirEntryCache dir_entry_cache_;
bool on_recovery_ = false; // recovery is doing or not
// for inode number management
fs::SharedMutex vnode_set_mutex_;
std::map<ino_t, uint32_t> vnode_set_ __TA_GUARDED(vnode_set_mutex_);
size_t vnode_set_size_[static_cast<size_t>(VnodeSet::kMax)] __TA_GUARDED(vnode_set_mutex_) = {
0,
};
zx::event fs_id_;
std::unique_ptr<InspectTree> inspect_tree_;
std::atomic<MemoryPressure> current_memory_pressure_level_ = MemoryPressure::kUnknown;
std::unique_ptr<MemoryPressureWatcher> memory_pressure_watcher_;
};
} // namespace f2fs
#endif // SRC_STORAGE_F2FS_F2FS_H_