| // 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "src/lib/uuid/uuid.h" |
| |
| #include <memory> |
| |
| #include <lib/zx/event.h> |
| #include <zircon/assert.h> |
| |
| #include <lib/async/dispatcher.h> |
| |
| #include "zircon/errors.h" |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/trace-provider/provider.h> |
| #include "src/lib/storage/vfs/cpp/pseudo_dir.h" |
| #include "src/lib/storage/vfs/cpp/trace.h" |
| |
| #include "f2fs.h" |
| |
| namespace f2fs { |
| |
| // This is the component's main class. It holds all of the component's state. |
| zx_status_t CreateBcache(std::unique_ptr<block_client::BlockDevice> device, bool* out_readonly, |
| std::unique_ptr<f2fs::Bcache>* out) { |
| fuchsia_hardware_block_BlockInfo info; |
| zx_status_t status = device->BlockGetInfo(&info); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Coult not access device info: " << status; |
| return status; |
| } |
| |
| uint64_t device_size = info.block_size * info.block_count; |
| |
| if (device_size == 0) { |
| FX_LOGS(ERROR) << "Invalid device size"; |
| return status; |
| } |
| uint64_t block_count = device_size / kF2fsBlockSize; |
| |
| if (block_count >= std::numeric_limits<uint32_t>::max()) { |
| FX_LOGS(ERROR) << "Block count overflow"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| return f2fs::Bcache::Create(std::move(device), static_cast<uint32_t>(block_count), out, |
| kF2fsBlockSize); |
| } |
| |
| zx_status_t Mkfs(const MkfsOptions& options, std::unique_ptr<f2fs::Bcache> bc) { |
| F2fsMkfs mkfs(std::move(bc), options); |
| |
| return mkfs.Mkfs(); |
| } |
| |
| zx_status_t Fsck(const MountOptions& options, std::unique_ptr<f2fs::Bcache> bc) { return ZX_OK; } |
| |
| F2fs::F2fs(std::unique_ptr<f2fs::Bcache> bc, f2fs_super_block* sb, |
| const MountOptions& mount_options) |
| : bc_(std::move(bc)), mount_options_(mount_options) { |
| raw_sb_ = std::unique_ptr<f2fs_super_block>(sb); |
| |
| zx::event event; |
| if (zx_status_t status = zx::event::create(0, &event); status == ZX_OK) { |
| zx_info_handle_basic_t info; |
| if (status = event.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); status == ZX_OK) { |
| fs_id_ = info.koid; |
| } |
| } |
| } |
| |
| F2fs::~F2fs() { vnode_table_.clear(); } |
| |
| zx_status_t LoadSuperblock(f2fs::Bcache* bc, f2fs_super_block* out_info) { |
| char buf[8192]; |
| |
| if (zx_status_t status = bc->Readblk(kSuperblockStart, buf); status != ZX_OK) { |
| FX_LOGS(ERROR) << "could not read info block."; |
| return status; |
| } |
| |
| memcpy(out_info, buf + 1024, sizeof(f2fs_super_block)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t F2fs::Create(std::unique_ptr<f2fs::Bcache> bc, const MountOptions& options, |
| std::unique_ptr<F2fs>* out) { |
| f2fs_super_block* info; |
| |
| info = new f2fs_super_block(); |
| if (zx_status_t status = LoadSuperblock(bc.get(), info); status != ZX_OK) { |
| return status; |
| } |
| |
| *out = std::unique_ptr<F2fs>(new F2fs(std::move(bc), info, options)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t F2fs::FindVnode(fbl::RefPtr<VnodeF2fs>* out, ino_t ino) { |
| fbl::RefPtr<VnodeF2fs> vn; |
| fbl::AutoLock lock(&vnode_table_lock_); |
| auto rawVn = vnode_table_.find(ino); |
| |
| if (rawVn.IsValid()) { |
| vn = fbl::MakeRefPtrUpgradeFromRaw(rawVn.CopyPointer(), vnode_table_lock_); |
| if (vn == nullptr) { |
| vnode_table_.erase(ino); |
| } |
| |
| *out = std::move(vn); |
| |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| void F2fs::InsertVnode(VnodeF2fs* vn) { |
| fbl::AutoLock lock(&vnode_table_lock_); |
| fbl::AutoLock lock_vn(&vn->v_lock_); |
| vnode_table_.insert(vn); |
| } |
| |
| void F2fs::EraseVnodeFromTable(VnodeF2fs* vn) { |
| fbl::AutoLock lock(&vnode_table_lock_); |
| fbl::AutoLock lock_vn(&vn->v_lock_); |
| vnode_table_.erase(*vn); |
| } |
| |
| zx_status_t CreateFsAndRoot(const MountOptions& mount_options, async_dispatcher_t* dispatcher, |
| std::unique_ptr<f2fs::Bcache> bcache, zx::channel mount_channel, |
| fbl::Closure on_unmount, ServeLayout serve_layout) { |
| TRACE_DURATION("f2fs", "CreateFsAndRoot"); |
| f2fs::MountOptions options = mount_options; |
| |
| std::unique_ptr<F2fs> fs; |
| if (zx_status_t status = F2fs::Create(std::move(bcache), options, &fs); status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create filesystem object " << status; |
| return status; |
| } |
| |
| if (zx_status_t ret = fs->FillSuper(); ret != ZX_OK) { |
| std::cout << "FillSuper error " << ret << std::endl; |
| return ret; |
| } |
| |
| 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 status; |
| } |
| |
| __UNUSED auto r = fs.release(); |
| |
| F2fs* vfs = data_root->Vfs(); |
| |
| vfs->SetUnmountCallback(std::move(on_unmount)); |
| vfs->SetDispatcher(dispatcher); |
| |
| fbl::RefPtr<fs::Vnode> export_root; |
| switch (serve_layout) { |
| case ServeLayout::kDataRootOnly: |
| export_root = std::move(data_root); |
| break; |
| case ServeLayout::kExportDirectory: |
| auto outgoing = fbl::MakeRefCounted<fs::PseudoDir>(); |
| outgoing->AddEntry("root", std::move(data_root)); |
| export_root = std::move(outgoing); |
| break; |
| } |
| |
| return vfs->ServeDirectory(std::move(export_root), std::move(mount_channel)); |
| } |
| |
| zx_status_t Mount(const MountOptions& options, std::unique_ptr<f2fs::Bcache> bc) { |
| zx::channel outgoing_server = zx::channel(zx_take_startup_handle(PA_DIRECTORY_REQUEST)); |
| zx::channel root_server = zx::channel(zx_take_startup_handle(FS_HANDLE_ROOT_ID)); |
| |
| if (outgoing_server.is_valid() && root_server.is_valid()) { |
| FX_LOGS(ERROR) << "both PA_DIRECTORY_REQUEST and FS_HANDLE_ROOT_ID provided - need one or the " |
| "other."; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| zx::channel export_root; |
| f2fs::ServeLayout serve_layout; |
| if (outgoing_server.is_valid()) { |
| export_root = std::move(outgoing_server); |
| serve_layout = f2fs::ServeLayout::kExportDirectory; |
| } else if (root_server.is_valid()) { |
| export_root = std::move(root_server); |
| serve_layout = f2fs::ServeLayout::kDataRootOnly; |
| } else { |
| FX_LOGS(ERROR) << "could not get startup handle to serve on"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| trace::TraceProviderWithFdio trace_provider(loop.dispatcher()); |
| |
| auto on_unmount = [&loop]() { |
| loop.Quit(); |
| FX_LOGS(WARNING) << "Unmounted"; |
| }; |
| |
| zx_status_t status = CreateFsAndRoot(options, loop.dispatcher(), std::move(bc), |
| std::move(export_root), std::move(on_unmount), serve_layout); |
| if (status != ZX_OK) { |
| return -1; |
| } |
| |
| ZX_ASSERT(loop.Run() == ZX_ERR_CANCELED); |
| |
| return ZX_OK; |
| } |
| |
| void Sync(SyncCallback closure) { |
| if (closure) |
| closure(ZX_OK); |
| } |
| |
| void F2fs::Shutdown(fs::Vfs::ShutdownCallback cb) { |
| ManagedVfs::Shutdown([this, cb = std::move(cb)](zx_status_t status) mutable { |
| Sync([this, cb = std::move(cb)](zx_status_t) mutable { |
| async::PostTask(dispatcher(), [this, cb = std::move(cb)]() mutable { |
| auto on_unmount = std::move(on_unmount_); |
| |
| PutSuper(); |
| |
| // Explicitly delete this (rather than just letting the memory release when |
| // the process exits) to ensure that the block device's fifo has been |
| // closed. |
| delete this; |
| |
| // Identify to the unmounting channel that teardown is complete. |
| cb(ZX_OK); |
| |
| // Identify to the unmounting thread that teardown is complete. |
| if (on_unmount) { |
| on_unmount(); |
| } |
| }); |
| }); |
| }); |
| } |
| |
| zx_status_t FlushDirtyNodePage(F2fs* fs, Page* page) { |
| f2fs_sb_info& sbi = fs->SbInfo(); |
| |
| if (!page) |
| return ZX_OK; |
| |
| ZX_ASSERT(page->host == nullptr); |
| ZX_ASSERT(page->host_nid == F2FS_NODE_INO(&sbi)); |
| |
| if (zx_status_t ret = fs->Nodemgr().F2fsWriteNodePage(page, nullptr); ret != ZX_OK) { |
| std::cout << "Node page write error " << ret << std::endl; |
| return ret; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FlushDirtyMetaPage(F2fs* fs, Page* page) { |
| f2fs_sb_info& sbi = fs->SbInfo(); |
| |
| if (!page) |
| return ZX_OK; |
| |
| ZX_ASSERT(page->host == nullptr); |
| ZX_ASSERT(page->host_nid == F2FS_META_INO(&sbi)); |
| |
| if (zx_status_t ret = fs->F2fsWriteMetaPage(page, nullptr); ret != ZX_OK) { |
| std::cout << "Meta page write error " << ret << std::endl; |
| return ret; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FlushDirtyDataPage(F2fs* fs, Page* page) { |
| if (!page) |
| return ZX_OK; |
| |
| ZX_ASSERT(page->host != nullptr); |
| |
| VnodeF2fs* vnode = static_cast<VnodeF2fs*>(page->host); |
| |
| if (zx_status_t ret = vnode->WriteDataPageReq(page, nullptr); ret != ZX_OK) { |
| std::cout << "Data page write error " << ret << std::endl; |
| return ret; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace f2fs |