| // 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" |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #ifdef __Fuchsia__ |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/trace-provider/provider.h> |
| #include <lib/zx/event.h> |
| #endif // __Fuchsia__ |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <memory> |
| |
| #ifdef __Fuchsia__ |
| #include "src/lib/storage/vfs/cpp/pseudo_dir.h" |
| #endif // __Fuchsia__ |
| #include "src/lib/storage/vfs/cpp/trace.h" |
| |
| namespace f2fs { |
| |
| #ifdef __Fuchsia__ |
| F2fs::F2fs(async_dispatcher_t* dispatcher, std::unique_ptr<f2fs::Bcache> bc, |
| std::unique_ptr<Superblock> sb, const MountOptions& mount_options) |
| : fs::PagedVfs(dispatcher), |
| bc_(std::move(bc)), |
| mount_options_(mount_options), |
| raw_sb_(std::move(sb)), |
| inspect_tree_(this) { |
| zx::event::create(0, &fs_id_); |
| } |
| |
| F2fs::~F2fs() { |
| // Inform PagedVfs so that it can stop threads that might call out to f2fs. |
| TearDown(); |
| } |
| #else // __Fuchsia__ |
| F2fs::F2fs(std::unique_ptr<f2fs::Bcache> bc, std::unique_ptr<Superblock> sb, |
| const MountOptions& mount_options) |
| : bc_(std::move(bc)), mount_options_(mount_options), raw_sb_(std::move(sb)) {} |
| |
| F2fs::~F2fs() {} |
| #endif // __Fuchsia__ |
| |
| #ifdef __Fuchsia__ |
| zx_status_t F2fs::Create(async_dispatcher_t* dispatcher, std::unique_ptr<f2fs::Bcache> bc, |
| const MountOptions& options, std::unique_ptr<F2fs>* out) { |
| #else // __Fuchsia__ |
| zx_status_t F2fs::Create(std::unique_ptr<f2fs::Bcache> bc, const MountOptions& options, |
| std::unique_ptr<F2fs>* out) { |
| #endif // __Fuchsia__ |
| auto info = std::make_unique<Superblock>(); |
| if (zx_status_t status = LoadSuperblock(bc.get(), info.get()); status != ZX_OK) { |
| return status; |
| } |
| |
| #ifdef __Fuchsia__ |
| *out = std::make_unique<F2fs>(dispatcher, std::move(bc), std::move(info), options); |
| // Create Pager and PagerPool |
| if (auto status = (*out)->Init(); status.is_error()) |
| return status.status_value(); |
| #else // __Fuchsia__ |
| *out = std::unique_ptr<F2fs>(new F2fs(std::move(bc), std::move(info), options)); |
| #endif // __Fuchsia__ |
| |
| if (zx_status_t status = (*out)->FillSuper(); status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to initialize fs." << status; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t LoadSuperblock(f2fs::Bcache* bc, Superblock* out_info, block_t bno) { |
| ZX_DEBUG_ASSERT(bno <= 1); |
| FsBlock super_block; |
| #ifdef __Fuchsia__ |
| auto buffer = super_block.GetData().data(); |
| #else // __Fuchsia__ |
| auto buffer = super_block.GetData(); |
| #endif // __Fuchsia__ |
| if (zx_status_t status = bc->Readblk(bno, buffer); status != ZX_OK) { |
| return status; |
| } |
| std::memcpy(out_info, buffer + kSuperOffset, sizeof(Superblock)); |
| return ZX_OK; |
| } |
| |
| zx_status_t LoadSuperblock(f2fs::Bcache* bc, Superblock* out_info) { |
| if (zx_status_t status = LoadSuperblock(bc, out_info, kSuperblockStart); status != ZX_OK) { |
| if (zx_status_t status = LoadSuperblock(bc, out_info, kSuperblockStart + 1); status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to read superblock." << status; |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| #ifdef __Fuchsia__ |
| zx::status<std::unique_ptr<F2fs>> CreateFsAndRoot(const MountOptions& mount_options, |
| async_dispatcher_t* dispatcher, |
| std::unique_ptr<f2fs::Bcache> bcache, |
| fidl::ServerEnd<fuchsia_io::Directory> root, |
| fit::closure on_unmount) { |
| #else // __Fuchsia__ |
| zx::status<std::unique_ptr<F2fs>> CreateFsAndRoot(const MountOptions& mount_options, |
| std::unique_ptr<f2fs::Bcache> bcache) { |
| #endif // __Fuchsia__ |
| TRACE_DURATION("f2fs", "CreateFsAndRoot"); |
| |
| std::unique_ptr<F2fs> fs; |
| #ifdef __Fuchsia__ |
| if (zx_status_t status = F2fs::Create(dispatcher, std::move(bcache), mount_options, &fs); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create filesystem object " << status; |
| return zx::error(status); |
| } |
| #else // __Fuchsia__ |
| if (zx_status_t status = F2fs::Create(std::move(bcache), mount_options, &fs); status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create filesystem object " << status; |
| return zx::error(status); |
| } |
| #endif // __Fuchsia__ |
| |
| fbl::RefPtr<VnodeF2fs> data_root; |
| if (zx_status_t status = VnodeF2fs::Vget(fs.get(), fs->RawSb().root_ino, &data_root); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "cannot find root inode: " << status; |
| return zx::error(status); |
| } |
| |
| #ifdef __Fuchsia__ |
| fs->SetUnmountCallback(std::move(on_unmount)); |
| |
| auto outgoing = fbl::MakeRefCounted<fs::PseudoDir>(fs.get()); |
| outgoing->AddEntry("root", std::move(data_root)); |
| |
| auto admin_svc = fbl::MakeRefCounted<AdminService>(fs->dispatcher(), fs.get()); |
| outgoing->AddEntry(fidl::DiscoverableProtocolName<fuchsia_fs::Admin>, admin_svc); |
| fs->SetAdminService(std::move(admin_svc)); |
| |
| fs->GetInspectTree().Initialize(); |
| |
| inspect::TreeHandlerSettings settings{.snapshot_behavior = |
| inspect::TreeServerSendPreference::Frozen( |
| inspect::TreeServerSendPreference::Type::DeepCopy)}; |
| |
| auto inspect_tree = fbl::MakeRefCounted<fs::Service>( |
| [connector = inspect::MakeTreeHandler(&fs->GetInspectTree().GetInspector(), dispatcher, |
| settings)](zx::channel chan) mutable { |
| connector(fidl::InterfaceRequest<fuchsia::inspect::Tree>(std::move(chan))); |
| return ZX_OK; |
| }); |
| |
| auto diagnostics_dir = fbl::MakeRefCounted<fs::PseudoDir>(fs.get()); |
| outgoing->AddEntry("diagnostics", diagnostics_dir); |
| diagnostics_dir->AddEntry(fuchsia::inspect::Tree::Name_, inspect_tree); |
| |
| if (zx_status_t status = fs->ServeDirectory(std::move(outgoing), std::move(root)); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to establish mount_channel" << status; |
| return zx::error(status); |
| } |
| #endif // __Fuchsia__ |
| |
| return zx::ok(std::move(fs)); |
| } |
| |
| #ifdef __Fuchsia__ |
| void F2fs::Sync(SyncCallback closure) { |
| SyncFs(true); |
| if (closure) { |
| closure(ZX_OK); |
| } |
| } |
| |
| void F2fs::Shutdown(fs::FuchsiaVfs::ShutdownCallback cb) { |
| PagedVfs::Shutdown([this, cb = std::move(cb)](zx_status_t status) mutable { |
| Sync([this, status, cb = std::move(cb)](zx_status_t) mutable { |
| async::PostTask(dispatcher(), [this, status, cb = std::move(cb)]() mutable { |
| PutSuper(); |
| bc_.reset(); |
| // Identify to the unmounting thread that teardown is complete. |
| if (on_unmount_) { |
| on_unmount_(); |
| } |
| // Identify to the unmounting channel that teardown is complete. |
| cb(status); |
| }); |
| }); |
| }); |
| } |
| |
| void F2fs::OnNoConnections() { |
| if (IsTerminating()) { |
| return; |
| } |
| Shutdown([](zx_status_t status) mutable { |
| ZX_ASSERT_MSG(status == ZX_OK, "Filesystem shutdown failed on OnNoConnections(): %s", |
| zx_status_get_string(status)); |
| }); |
| } |
| #endif // __Fuchsia__ |
| |
| void F2fs::DecValidBlockCount(VnodeF2fs* vnode, block_t count) { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| ZX_ASSERT(superblock_info_->GetTotalValidBlockCount() >= count); |
| vnode->DecBlocks(count); |
| superblock_info_->SetTotalValidBlockCount(superblock_info_->GetTotalValidBlockCount() - count); |
| } |
| |
| zx_status_t F2fs::IncValidBlockCount(VnodeF2fs* vnode, block_t count) { |
| block_t valid_block_count; |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| valid_block_count = superblock_info_->GetTotalValidBlockCount() + count; |
| if (valid_block_count > superblock_info_->GetUserBlockCount()) { |
| #ifdef __Fuchsia__ |
| inspect_tree_.OnOutOfSpace(); |
| #endif // __Fuchsia__ |
| return ZX_ERR_NO_SPACE; |
| } |
| vnode->IncBlocks(count); |
| superblock_info_->SetTotalValidBlockCount(valid_block_count); |
| superblock_info_->SetAllocValidBlockCount(superblock_info_->GetAllocValidBlockCount() + count); |
| return ZX_OK; |
| } |
| |
| block_t F2fs::ValidUserBlocks() { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| return superblock_info_->GetTotalValidBlockCount(); |
| } |
| |
| uint32_t F2fs::ValidNodeCount() { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| return superblock_info_->GetTotalValidNodeCount(); |
| } |
| |
| void F2fs::IncValidInodeCount() { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| ZX_ASSERT(superblock_info_->GetTotalValidInodeCount() != superblock_info_->GetTotalNodeCount()); |
| superblock_info_->SetTotalValidInodeCount(superblock_info_->GetTotalValidInodeCount() + 1); |
| } |
| |
| void F2fs::DecValidInodeCount() { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| ZX_ASSERT(superblock_info_->GetTotalValidInodeCount()); |
| superblock_info_->SetTotalValidInodeCount(superblock_info_->GetTotalValidInodeCount() - 1); |
| } |
| |
| uint32_t F2fs::ValidInodeCount() { |
| std::lock_guard lock(superblock_info_->GetStatLock()); |
| return superblock_info_->GetTotalValidInodeCount(); |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| zx::status<fs::FilesystemInfo> F2fs::GetFilesystemInfo() { |
| fs::FilesystemInfo info; |
| |
| info.block_size = kBlockSize; |
| info.max_filename_size = kMaxNameLen; |
| info.fs_type = VFS_TYPE_F2FS; |
| info.total_bytes = |
| safemath::CheckMul<uint64_t>(superblock_info_->GetUserBlockCount(), kBlockSize).ValueOrDie(); |
| info.used_bytes = safemath::CheckMul<uint64_t>(ValidUserBlocks(), kBlockSize).ValueOrDie(); |
| info.total_nodes = superblock_info_->GetTotalNodeCount(); |
| info.used_nodes = superblock_info_->GetTotalValidInodeCount(); |
| info.SetFsId(fs_id_); |
| info.name = "f2fs"; |
| |
| // TODO(unknown): Fill free_shared_pool_bytes using fvm info |
| |
| return zx::ok(info); |
| } |
| |
| #endif // __Fuchsia__ |
| |
| bool F2fs::IsValid() const { |
| if (bc_ == nullptr) { |
| return false; |
| } |
| if (root_vnode_ == nullptr) { |
| return false; |
| } |
| if (superblock_info_ == nullptr) { |
| return false; |
| } |
| if (segment_manager_ == nullptr) { |
| return false; |
| } |
| if (node_manager_ == nullptr) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Fill the locked page with data located in the block address. |
| zx_status_t F2fs::MakeReadOperation(LockedPage& page, block_t blk_addr, bool is_sync) { |
| if (page->IsUptodate()) { |
| return ZX_OK; |
| } |
| |
| if (blk_addr >= GetBc().Maxblk()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // TODO: Request multiple Pages. |
| zx_status_t ret = GetBc().Readblk(blk_addr, page->GetAddress()); |
| if (ret == ZX_OK && is_sync) { |
| page->SetUptodate(); |
| } |
| return ret; |
| } |
| |
| zx_status_t F2fs::MakeWriteOperation(LockedPage& page, block_t blk_addr, PageType type) { |
| if (blk_addr >= GetBc().Maxblk()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| storage::Operation op = { |
| .type = storage::OperationType::kWrite, |
| .dev_offset = blk_addr, |
| .length = 1, |
| }; |
| writer_->EnqueuePage(op, page, type); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t F2fs::MakeOperation(storage::OperationType op, LockedPage& page, block_t blk_addr, |
| PageType type, block_t nblocks) { |
| // TODO: verify_block_addr(superblock_info, blk_addr); |
| switch (op) { |
| case storage::OperationType::kWrite: |
| return MakeWriteOperation(page, blk_addr, type); |
| case storage::OperationType::kRead: |
| return MakeReadOperation(page, blk_addr, true); |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| }; |
| } |
| |
| zx_status_t F2fs::MakeOperation(storage::OperationType op, block_t blk_addr, block_t nblocks) { |
| if (op != storage::OperationType::kTrim) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return GetBc().Trim(blk_addr, nblocks); |
| } |
| } // namespace f2fs |