[f2fs] Enable mount options and internal features

The configurable options are as below.
- discard
- # of active logs
- cold data separation based on file extension

TODO: noheap can be turned off after gc impl.

Test: fx test f2fs-unittest fs-tests large-fs-tests

Change-Id: I01ad30709c1fd3da24113f7630f5d1fa41a65ca9
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/543303
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 3a58c58..f2d0cf4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -29,6 +29,7 @@
     "file.cc",
     "gc.cc",
     "mkfs.cc",
+    "mount.cc",
     "namei.cc",
     "node.cc",
     "recovery.cc",
@@ -49,6 +50,7 @@
     "f2fs_types.h",
     "file.h",
     "mkfs.h",
+    "mount.h",
     "node.h",
     "node.h",
     "segment.h",
diff --git a/bcache.cc b/bcache.cc
index 853a5b0..348d9d8 100644
--- a/bcache.cc
+++ b/bcache.cc
@@ -26,6 +26,32 @@
 
 namespace f2fs {
 
+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 / kBlockSize;
+
+  // The maximum volume size of f2fs is 16TB
+  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), block_count, kBlockSize, out);
+}
+
 std::unique_ptr<block_client::BlockDevice> Bcache::Destroy(std::unique_ptr<Bcache> bcache) {
   {
     // Destroy the VmoBuffer before extracting the underlying device, as it needs
@@ -61,6 +87,22 @@
   return RunOperation(operation, &buffer_);
 }
 
+zx_status_t Bcache::Trim(block_t start, block_t num) {
+  if (!(info_.flags & fuchsia_hardware_block_FLAG_TRIM_SUPPORT)) {
+    return ZX_ERR_NOT_SUPPORTED;
+  }
+
+  block_fifo_request_t request = {
+      .opcode = BLOCKIO_TRIM,
+      .vmoid = BLOCK_VMOID_INVALID,
+      .length = static_cast<uint32_t>(BlockNumberToDevice(num)),
+      .vmo_offset = 0,
+      .dev_offset = BlockNumberToDevice(start),
+  };
+
+  return device()->FifoTransaction(&request, 1);
+}
+
 zx_status_t Bcache::BlockAttachVmo(const zx::vmo& vmo, storage::Vmoid* out) {
   return device()->BlockAttachVmo(vmo, out);
 }
@@ -69,12 +111,6 @@
   return device()->BlockDetachVmo(std::move(vmoid));
 }
 
-zx_status_t Bcache::Sync() {
-  block_fifo_request_t request = {};
-  request.opcode = BLOCKIO_FLUSH;
-  return device_->FifoTransaction(&request, 1);
-}
-
 zx_status_t FdToBlockDevice(fbl::unique_fd& fd, std::unique_ptr<block_client::BlockDevice>* out) {
   zx::channel channel, server;
   zx_status_t status = zx::channel::create(0, &channel, &server);
@@ -99,44 +135,17 @@
   return ZX_OK;
 }
 
-zx_status_t Bcache::Create(std::unique_ptr<block_client::BlockDevice> device, uint32_t max_blocks,
-                           std::unique_ptr<Bcache>* out) {
-  zx_status_t status = Create(device.get(), max_blocks, out);
+zx_status_t Bcache::Create(std::unique_ptr<block_client::BlockDevice> device, uint64_t max_blocks,
+                           uint64_t block_size, std::unique_ptr<Bcache>* out) {
+  zx_status_t status = Create(device.get(), max_blocks, block_size, out);
   if (status == ZX_OK) {
     (*out)->owned_device_ = std::move(device);
   }
   return status;
 }
 
-zx_status_t Bcache::Create(block_client::BlockDevice* device, uint32_t max_blocks,
-                           std::unique_ptr<Bcache>* out) {
-  std::unique_ptr<Bcache> bcache(new Bcache(device, max_blocks, kBlockSize));
-
-  zx_status_t status = bcache->buffer_.Initialize(bcache.get(), 1, kBlockSize, "scratch-block");
-  if (status != ZX_OK) {
-    return status;
-  }
-
-  status = bcache->VerifyDeviceInfo();
-  if (status != ZX_OK) {
-    return status;
-  }
-
-  *out = std::move(bcache);
-  return ZX_OK;
-}
-
-zx_status_t Bcache::Create(std::unique_ptr<block_client::BlockDevice> device, uint32_t max_blocks,
-                           std::unique_ptr<Bcache>* out, uint32_t block_size) {
-  zx_status_t status = Create(device.get(), max_blocks, out, block_size);
-  if (status == ZX_OK) {
-    (*out)->owned_device_ = std::move(device);
-  }
-  return status;
-}
-
-zx_status_t Bcache::Create(block_client::BlockDevice* device, uint32_t max_blocks,
-                           std::unique_ptr<Bcache>* out, uint32_t block_size) {
+zx_status_t Bcache::Create(block_client::BlockDevice* device, uint64_t max_blocks,
+                           uint64_t block_size, std::unique_ptr<Bcache>* out) {
   std::unique_ptr<Bcache> bcache(new Bcache(device, max_blocks, block_size));
 
   zx_status_t status = bcache->buffer_.Initialize(bcache.get(), 1, block_size, "scratch-block");
@@ -153,9 +162,9 @@
   return ZX_OK;
 }
 
-uint32_t Bcache::DeviceBlockSize() const { return info_.block_size; }
+uint64_t Bcache::DeviceBlockSize() const { return info_.block_size; }
 
-Bcache::Bcache(block_client::BlockDevice* device, uint32_t max_blocks, uint32_t block_size)
+Bcache::Bcache(block_client::BlockDevice* device, uint64_t max_blocks, uint64_t block_size)
     : max_blocks_(max_blocks), block_size_(block_size), device_(device) {}
 
 zx_status_t Bcache::VerifyDeviceInfo() {
diff --git a/bcache.h b/bcache.h
index 2fd3193..b5d3b7f 100644
--- a/bcache.h
+++ b/bcache.h
@@ -29,11 +29,6 @@
 
 namespace f2fs {
 
-#ifdef __Fuchsia__
-
-// A helper function for converting "fd" to "BlockDevice".
-zx_status_t FdToBlockDevice(fbl::unique_fd& fd, std::unique_ptr<block_client::BlockDevice>* out);
-
 class Bcache : public fs::DeviceTransactionHandler, public storage::VmoidRegistry {
  public:
   // Not copyable or movable
@@ -61,14 +56,18 @@
 
   block_client::BlockDevice* GetDevice() final { return device_; }
 
-  uint32_t DeviceBlockSize() const;
+  uint64_t DeviceBlockSize() const;
 
-  // Raw block read functions.
+  // Raw block read/write/trim functions
+  // |bno| is a target LBA in a 4KB block size
+  // TODO: create io functions with vmo and request for removing memory copy
   // These do not track blocks (or attempt to access the block cache)
   // NOTE: Not marked as final, since these are overridden methods on host,
   // but not on __Fuchsia__.
   zx_status_t Readblk(block_t bno, void* data);
   zx_status_t Writeblk(block_t bno, const void* data);
+  zx_status_t Trim(block_t start, block_t num);
+  zx_status_t Flush() override { return DeviceTransactionHandler::Flush(); }
 
   // TODO(rvargas): Move this to BlockDevice.
   // VmoidRegistry interface:
@@ -80,28 +79,18 @@
 
   // This factory allows building this object from a BlockDevice. Bcache can take ownership of the
   // device (the first Create method), or not (the second Create method).
-  static zx_status_t Create(std::unique_ptr<block_client::BlockDevice> device, uint32_t max_blocks,
-                            std::unique_ptr<Bcache>* out);
+  static zx_status_t Create(std::unique_ptr<block_client::BlockDevice> device, uint64_t max_blocks,
+                            uint64_t block_size, std::unique_ptr<Bcache>* out);
 
-  static zx_status_t Create(block_client::BlockDevice* device, uint32_t max_blocks,
-                            std::unique_ptr<Bcache>* out);
+  static zx_status_t Create(block_client::BlockDevice* device, uint64_t max_blocks,
+                            uint64_t block_size, std::unique_ptr<Bcache>* out);
 
-  static zx_status_t Create(std::unique_ptr<block_client::BlockDevice> device, uint32_t max_blocks,
-                            std::unique_ptr<Bcache>* out, uint32_t block_size);
-
-  static zx_status_t Create(block_client::BlockDevice* device, uint32_t max_blocks,
-                            std::unique_ptr<Bcache>* out, uint32_t block_size);
-
-  // Returns the maximum number of available blocks,
-  // assuming the filesystem is non-resizable.
-  uint32_t Maxblk() const { return max_blocks_; }
-  uint32_t BlockSize() const { return block_size_; }
+  uint64_t Maxblk() const { return max_blocks_; }
+  uint64_t BlockSize() const { return block_size_; }
 
   block_client::BlockDevice* device() { return device_; }
   const block_client::BlockDevice* device() const { return device_; }
 
-  zx_status_t Sync();
-
   // Blocks all I/O operations to the underlying device (that go via the RunRequests method). This
   // does *not* block operations that go directly to the device.
   void Pause();
@@ -112,13 +101,13 @@
  private:
   friend class BlockNode;
 
-  Bcache(block_client::BlockDevice* device, uint32_t max_blocks, uint32_t block_size);
+  Bcache(block_client::BlockDevice* device, uint64_t max_blocks, uint64_t block_size);
 
   // Used during initialization of this object.
   zx_status_t VerifyDeviceInfo();
 
-  uint32_t max_blocks_;
-  const uint32_t block_size_;
+  const uint64_t max_blocks_;
+  const uint64_t block_size_;
   fuchsia_hardware_block_BlockInfo info_ = {};
   std::unique_ptr<block_client::BlockDevice> owned_device_;  // The device, if owned.
   block_client::BlockDevice* device_;  // Pointer to the device, irrespective of ownership.
@@ -127,63 +116,10 @@
   std::shared_mutex mutex_;
 };
 
-#else  // __Fuchsia__
-
-class Bcache : public fs::TransactionHandler {
- public:
-  // Not copyable or movable
-  Bcache(const Bcache&) = delete;
-  Bcache& operator=(const Bcache&) = delete;
-  Bcache(Bcache&&) = delete;
-  Bcache& operator=(Bcache&&) = delete;
-
-  ~Bcache() {}
-
-  ////////////////
-  // fs::TransactionHandler interface.
-
-  uint64_t BlockNumberToDevice(uint64_t block_num) const final { return block_num; }
-
-  zx_status_t RunRequests(const std::vector<storage::BufferedOperation>& operations) final;
-
-  // Raw block read functions.
-  // These do not track blocks (or attempt to access the block cache)
-  // NOTE: Not marked as final, since these are overridden methods on host,
-  // but not on __Fuchsia__.
-  zx_status_t Readblk(block_t bno, void* data);
-  zx_status_t Writeblk(block_t bno, const void* data);
-
-  ////////////////
-  // Other methods.
-
-  static zx_status_t Create(fbl::unique_fd fd, uint32_t max_blocks, std::unique_ptr<Bcache>* out);
-
-  // Returns the maximum number of available blocks,
-  // assuming the filesystem is non-resizable.
-  uint32_t Maxblk() const { return max_blocks_; }
-
-  // Lengths of each extent (in bytes)
-  fbl::Array<size_t> extent_lengths_;
-  // Tell Bcache to look for Minfs partition starting at |offset| bytes
-  zx_status_t SetOffset(off_t offset);
-  // Tell the Bcache it is pointing at a sparse file
-  // |offset| indicates where the minfs partition begins within the file
-  // |extent_lengths| contains the length of each extent (in bytes)
-  zx_status_t SetSparse(off_t offset, const fbl::Vector<size_t>& extent_lengths);
-
-  int Sync();
-
- private:
-  friend class BlockNode;
-
-  Bcache(fbl::unique_fd fd, uint32_t max_blocks);
-
-  const fbl::unique_fd fd_;
-  uint32_t max_blocks_;
-  off_t offset_ = 0;
-};
-
-#endif
+// A helper function for converting "fd" to "BlockDevice".
+zx_status_t FdToBlockDevice(fbl::unique_fd& fd, std::unique_ptr<block_client::BlockDevice>* out);
+zx_status_t CreateBcache(std::unique_ptr<block_client::BlockDevice> device, bool* out_readonly,
+                         std::unique_ptr<Bcache>* out);
 
 }  // namespace f2fs
 
diff --git a/dir.h b/dir.h
index c9db068..7c69ae2 100644
--- a/dir.h
+++ b/dir.h
@@ -76,7 +76,7 @@
 
   zx_status_t NewInode(uint32_t mode, fbl::RefPtr<VnodeF2fs> *out);
   int IsMultimediaFile(const char *s, const char *sub);
-  void SetColdFile(const char *name);
+  void SetColdFile(const char *name, VnodeF2fs *vnode);
   zx_status_t DoCreate(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out);
 #if 0  // porting needed
 //   int F2fsLink(dentry *old_dentry, dentry *dentry);
diff --git a/f2fs.cc b/f2fs.cc
index 40f31df..a8cf2f8 100644
--- a/f2fs.cc
+++ b/f2fs.cc
@@ -29,33 +29,6 @@
 
 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 / kBlockSize;
-
-  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,
-                              kBlockSize);
-}
-
 zx_status_t Fsck(const MountOptions& options, std::unique_ptr<f2fs::Bcache> bc) { return ZX_OK; }
 
 F2fs::F2fs(std::unique_ptr<f2fs::Bcache> bc, SuperBlock* sb, const MountOptions& mount_options)
@@ -74,19 +47,6 @@
 
 F2fs::~F2fs() { vnode_table_.clear(); }
 
-zx_status_t LoadSuperblock(f2fs::Bcache* bc, SuperBlock* 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(SuperBlock));
-
-  return ZX_OK;
-}
-
 zx_status_t F2fs::Create(std::unique_ptr<f2fs::Bcache> bc, const MountOptions& options,
                          std::unique_ptr<F2fs>* out) {
   SuperBlock* info;
@@ -98,6 +58,11 @@
 
   *out = std::unique_ptr<F2fs>(new F2fs(std::move(bc), info, options));
 
+  if (zx_status_t status = (*out)->FillSuper(); status != ZX_OK) {
+    FX_LOGS(ERROR) << "failed to initialize fs." << status;
+    return status;
+  }
+
   return ZX_OK;
 }
 
@@ -132,36 +97,44 @@
   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) {
+zx_status_t LoadSuperblock(f2fs::Bcache* bc, SuperBlock* out_info) {
+  // TODO: define ino for superblock after cache impl.
+  Page* page = GrabCachePage(nullptr, 0, 0);
+  if (zx_status_t status = bc->Readblk(kSuperblockStart, PageAddress(page)); status != ZX_OK) {
+    if (status = bc->Readblk(kSuperblockStart + 1, PageAddress(page)); status != ZX_OK) {
+      FX_LOGS(ERROR) << "failed to read superblock." << status;
+      F2fsPutPage(page, 1);
+      return status;
+    }
+  }
+  memcpy(out_info, static_cast<uint8_t*>(PageAddress(page)) + kSuperOffset, sizeof(SuperBlock));
+  F2fsPutPage(page, 1);
+  return ZX_OK;
+}
+
+zx::status<std::unique_ptr<F2fs>> 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) {
+  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 status;
-  }
-
-  if (zx_status_t ret = fs->FillSuper(); ret != ZX_OK) {
-    std::cout << "FillSuper error " << ret << std::endl;
-    return ret;
+    return zx::error(status);
   }
 
   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;
+    return zx::error(status);
   }
 
-  __UNUSED auto r = fs.release();
-
-  F2fs* vfs = data_root->Vfs();
-
-  vfs->SetUnmountCallback(std::move(on_unmount));
-  vfs->SetDispatcher(dispatcher);
+  fs->SetUnmountCallback(std::move(on_unmount));
+  fs->SetDispatcher(dispatcher);
 
   fbl::RefPtr<fs::Vnode> export_root;
   switch (serve_layout) {
@@ -175,49 +148,13 @@
       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;
+  if (zx_status_t status = fs->ServeDirectory(std::move(export_root), std::move(mount_channel));
+      status != ZX_OK) {
+    FX_LOGS(ERROR) << "failed to establish mount_channel" << status;
+    return zx::error(status);
   }
 
-  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;
+  return zx::ok(std::move(fs));
 }
 
 void Sync(SyncCallback closure) {
diff --git a/f2fs.h b/f2fs.h
index fcdf6d2..4bc72e5 100644
--- a/f2fs.h
+++ b/f2fs.h
@@ -41,21 +41,11 @@
 #include "node.h"
 #include "segment.h"
 #include "mkfs.h"
+#include "mount.h"
 // clang-format on
 
 namespace f2fs {
 
-struct MountOptions {
-  bool background_gc = false;
-  bool disable_roll_forward = true;
-  bool discard = false;
-  bool noheap = true;
-  bool xattr_user = false;
-  bool posix_acl = false;
-  bool disable_ext_identify = true;
-  uint32_t active_logs = 6;
-};
-
 enum class ServeLayout {
   // The root of the filesystem is exposed directly.
   kDataRootOnly,
@@ -66,12 +56,15 @@
 };
 
 zx_status_t Fsck(const MountOptions &options, std::unique_ptr<f2fs::Bcache> bc);
-zx_status_t Mount(const MountOptions &options, std::unique_ptr<f2fs::Bcache> bc);
-
-zx_status_t CreateBcache(std::unique_ptr<block_client::BlockDevice> device, bool *out_readonly,
-                         std::unique_ptr<f2fs::Bcache> *out);
 zx_status_t LoadSuperblock(f2fs::Bcache *bc, SuperBlock *out_info);
 
+zx::status<std::unique_ptr<F2fs>> 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);
+
 using SyncCallback = fs::Vnode::SyncCallback;
 
 class F2fs : public fs::ManagedVfs {
@@ -125,13 +118,13 @@
   // VnodeF2fs *F2fsNfsGetInode(uint64_t ino, uint32_t generation);
   // dentry *F2fsFhToDentry(fid *fid, int fh_len, int fh_type);
   // dentry *F2fsFhToParent(fid *fid, int fh_len, int fh_type);
-  // int ParseOptions(char *options);
 #endif
   loff_t MaxFileSize(unsigned bits);
   int SanityCheckRawSuper();
   int SanityCheckCkpt();
   void InitSbInfo();
   zx_status_t FillSuper();
+  void ParseOptions();
 #if 0  // porting needed
   // dentry *F2fsMount(file_system_type *fs_type, int flags,
   //     const char *dev_name, void *data);
diff --git a/f2fs_internal.h b/f2fs_internal.h
index 468c856..4c9bc58 100644
--- a/f2fs_internal.h
+++ b/f2fs_internal.h
@@ -10,21 +10,6 @@
 class VnodeF2fs;
 
 /*
- * For mount options
- */
-constexpr uint64_t kMountBgGC = 0x00000001;
-constexpr uint64_t kMountDisableRollForward = 0x00000002;
-constexpr uint64_t kMountDiscard = 0x00000004;
-constexpr uint64_t kMountNoheap = 0x00000008;
-constexpr uint64_t kMountXattrUser = 0x00000010;
-constexpr uint64_t kMountPosixAcl = 0x00000020;
-constexpr uint64_t kMountDisableExtIdentify = 0x00000040;
-
-struct MountInfo {
-  uint64_t opt;
-};
-
-/*
  * For checkpoint manager
  */
 enum class MetaBitmap { kNatBitmap, kSitBitmap };
@@ -362,7 +347,7 @@
   uint32_t s_next_generation = 0;                               /* for NFS support */
   atomic_t nr_pages[static_cast<int>(CountType::kNrCountType)]; /* # of pages, see count_type */
 
-  MountInfo mount_opt; /* mount options */
+  uint64_t mount_opt = 0;  // set with kMountOptxxxx bits according to F2fs::mount_options_
 
   /* for cleaning operations */
   mtx_t gc_mutex;                             /* mutex for GC */
@@ -396,10 +381,6 @@
 //  return (SbInfo *)sb->s_fs_info;
 //}
 
-inline void ClearOpt(SbInfo *sbi, uint64_t option) { sbi->mount_opt.opt &= ~option; }
-inline void SetOpt(SbInfo *sbi, uint64_t option) { sbi->mount_opt.opt |= option; }
-inline uint64_t TestOpt(SbInfo *sbi, uint64_t option) { return sbi->mount_opt.opt & option; }
-
 static inline const SuperBlock *RawSuper(SbInfo *sbi) {
   return static_cast<const SuperBlock *>(sbi->raw_super);
 }
diff --git a/f2fs_layout.h b/f2fs_layout.h
index 356436b..4a79139 100644
--- a/f2fs_layout.h
+++ b/f2fs_layout.h
@@ -9,17 +9,15 @@
 
 namespace f2fs {
 
-constexpr uint64_t kSuperOffset = 1024;     /* byte-size offset */
-constexpr uint32_t kLogSectorSize = 9;      /* 9 bits for 512 byte */
-constexpr uint32_t kLogSectorsPerBlock = 3; /* 4KB: kBlkSize */
-constexpr size_t kBlkSize = 4096;           /* support only 4KB block */
-constexpr int kMaxExtension = 64;           /* # of extension entries */
+constexpr uint64_t kSuperOffset = 1024;      // byte-size offset
+constexpr uint32_t kLogSectorSize = 9;       // 9 bits for 512 byte
+constexpr uint32_t kLogSectorsPerBlock = 3;  // log2 # of sectors per block
+constexpr uint32_t kBlockSize = 4096;        // F2fs block size in byte
+constexpr int kMaxExtension = 64;            // # of extension entries
 
 constexpr block_t kNullAddr = 0x0U;
 constexpr block_t kNewAddr = -1U;
 
-constexpr uint32_t kBlockSize = 4096;
-
 // Superblock location.
 constexpr size_t kSuperblockStart = 0;
 
diff --git a/mkfs.cc b/mkfs.cc
index 8c99f7f..67c66a4 100644
--- a/mkfs.cc
+++ b/mkfs.cc
@@ -3,13 +3,13 @@
 // found in the LICENSE file.
 
 #include <getopt.h>
+#include <lib/syslog/cpp/macros.h>
 
 #include <cmath>
 #include <codecvt>
 
 #include "f2fs.h"
 #include "src/lib/uuid/uuid.h"
-#include <lib/syslog/cpp/macros.h>
 
 namespace f2fs {
 
@@ -111,9 +111,9 @@
 
   if (zx_status_t ret = FormatDevice(); ret != ZX_OK)
     return ret;
-
-  fprintf(stderr, "Info: format successful\n");
-
+#ifdef F2FS_BU_DEBUG
+  FX_LOGS(INFO) << "Formated successfully";
+#endif
   return ZX_OK;
 }
 
@@ -284,7 +284,7 @@
   super_block_.block_count = CpuToLe((params_.total_sectors * kDefaultSectorSize) / blk_size_bytes);
 
   uint64_t zone_align_start_offset =
-      (params_.start_sector * kDefaultSectorSize + 2 * kBlkSize + zone_size_bytes - 1) /
+      (params_.start_sector * kDefaultSectorSize + 2 * kBlockSize + zone_size_bytes - 1) /
           zone_size_bytes * zone_size_bytes -
       params_.start_sector * kDefaultSectorSize;
 
@@ -499,13 +499,13 @@
 }
 
 zx_status_t MkfsWorker::WriteCheckPointPack() {
-  Checkpoint *ckp = static_cast<Checkpoint *>(calloc(kBlkSize, 1));
+  Checkpoint *ckp = static_cast<Checkpoint *>(calloc(kBlockSize, 1));
   if (ckp == nullptr) {
     printf("\n\tError: Calloc Failed for Checkpoint!!!\n");
     return ZX_ERR_NO_MEMORY;
   }
 
-  SummaryBlock *sum = static_cast<SummaryBlock *>(calloc(kBlkSize, 1));
+  SummaryBlock *sum = static_cast<SummaryBlock *>(calloc(kBlockSize, 1));
   if (sum == nullptr) {
     printf("\n\tError: Calloc Failed for summay_node!!!\n");
     return ZX_ERR_NO_MEMORY;
@@ -565,7 +565,7 @@
   uint32_t blk_size_bytes = 1 << LeToCpu(super_block_.log_blocksize);
   uint64_t cp_seg_blk_offset = LeToCpu(super_block_.segment0_blkaddr) * blk_size_bytes;
 
-  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the ckp to disk!!!\n");
     return ret;
   }
@@ -578,7 +578,7 @@
   sum->entries[0].ofs_in_node = 0;
 
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
@@ -588,7 +588,7 @@
   SetSumType((&sum->footer), kSumTypeData);
 
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
@@ -623,7 +623,7 @@
       CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegColdData) << 10)});
 
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
@@ -636,7 +636,7 @@
   sum->entries[0].ofs_in_node = 0;
 
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
@@ -646,7 +646,7 @@
   SetSumType((&sum->footer), kSumTypeNode);
 
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
@@ -655,14 +655,14 @@
   memset(sum, 0, sizeof(SummaryBlock));
   SetSumType((&sum->footer), kSumTypeNode);
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(sum, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the sum_blk to disk!!!\n");
     return ret;
   }
 
   /* 8. cp page2 */
   cp_seg_blk_offset += blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the ckp to disk!!!\n");
     return ret;
   }
@@ -678,7 +678,7 @@
 
   cp_seg_blk_offset =
       (LeToCpu(super_block_.segment0_blkaddr) + params_.blks_per_seg) * blk_size_bytes;
-  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(ckp, cp_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the ckp to disk!!!\n");
     return ret;
   }
@@ -689,12 +689,12 @@
 }
 
 zx_status_t MkfsWorker::WriteSuperBlock() {
-  uint8_t *zero_buff = static_cast<uint8_t *>(calloc(kBlkSize, 1));
+  uint8_t *zero_buff = static_cast<uint8_t *>(calloc(kBlockSize, 1));
 
   memcpy(zero_buff + kSuperOffset, &super_block_, sizeof(super_block_));
 
   for (uint32_t index = 0; index < 2; index++) {
-    if (zx_status_t ret = WriteToDisk(zero_buff, index * kBlkSize, kBlkSize); ret != ZX_OK) {
+    if (zx_status_t ret = WriteToDisk(zero_buff, index * kBlockSize, kBlockSize); ret != ZX_OK) {
       printf("\n\tError: While while writing supe_blk	on disk!!! index : %d\n", index);
       return ret;
     }
@@ -705,7 +705,7 @@
 }
 
 zx_status_t MkfsWorker::WriteRootInode() {
-  Node *raw_node = static_cast<Node *>(calloc(kBlkSize, 1));
+  Node *raw_node = static_cast<Node *>(calloc(kBlockSize, 1));
   if (raw_node == nullptr) {
     printf("\n\tError: Calloc Failed for raw_node!!!\n");
     return ZX_ERR_NO_MEMORY;
@@ -754,7 +754,7 @@
       params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] * params_.blks_per_seg;
   main_area_node_seg_blk_offset *= blk_size_bytes;
 
-  if (zx_status_t ret = WriteToDisk(raw_node, main_area_node_seg_blk_offset, kBlkSize);
+  if (zx_status_t ret = WriteToDisk(raw_node, main_area_node_seg_blk_offset, kBlockSize);
       ret != ZX_OK) {
     printf("\n\tError: While writing the raw_node to disk!!!, size = %lu\n", sizeof(Node));
     return ret;
@@ -762,7 +762,7 @@
 
   memset(raw_node, 0xff, sizeof(Node));
 
-  if (zx_status_t ret = WriteToDisk(raw_node, main_area_node_seg_blk_offset + 4096, kBlkSize);
+  if (zx_status_t ret = WriteToDisk(raw_node, main_area_node_seg_blk_offset + 4096, kBlockSize);
       ret != ZX_OK) {
     printf("\n\tError: While writing the raw_node to disk!!!\n");
     return ret;
@@ -772,7 +772,7 @@
 }
 
 zx_status_t MkfsWorker::UpdateNatRoot() {
-  NatBlock *nat_blk = static_cast<NatBlock *>(calloc(kBlkSize, 1));
+  NatBlock *nat_blk = static_cast<NatBlock *>(calloc(kBlockSize, 1));
   if (nat_blk == nullptr) {
     printf("\n\tError: Calloc Failed for nat_blk!!!\n");
     return ZX_ERR_NO_MEMORY;
@@ -796,7 +796,7 @@
 
   uint64_t nat_seg_blk_offset = LeToCpu(super_block_.nat_blkaddr) * blk_size_bytes;
 
-  if (zx_status_t ret = WriteToDisk(nat_blk, nat_seg_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(nat_blk, nat_seg_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the nat_blk set0 to disk!!!\n");
     return ret;
   }
@@ -806,7 +806,7 @@
 }
 
 zx_status_t MkfsWorker::AddDefaultDentryRoot() {
-  DentryBlock *dent_blk = static_cast<DentryBlock *>(calloc(kBlkSize, 1));
+  DentryBlock *dent_blk = static_cast<DentryBlock *>(calloc(kBlockSize, 1));
   if (dent_blk == nullptr) {
     printf("\n\tError: Calloc Failed for dent_blk!!!\n");
     return ZX_ERR_NO_MEMORY;
@@ -832,7 +832,7 @@
        params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] * params_.blks_per_seg) *
       blk_size_bytes;
 
-  if (zx_status_t ret = WriteToDisk(dent_blk, data_blk_offset, kBlkSize); ret != ZX_OK) {
+  if (zx_status_t ret = WriteToDisk(dent_blk, data_blk_offset, kBlockSize); ret != ZX_OK) {
     printf("\n\tError: While writing the dentry_blk to disk!!!\n");
     return ret;
   }
@@ -859,30 +859,7 @@
   return err;
 }
 
-#if 0  // porting needed
-// int MkfsWorker::F2fsTrimDevice()
-// {
-//         uint64_t range[2];
-//         stat stat_buf;
-
-//         range[0] = 0;
-//         range[1] = params_.total_sectors * kDefaultSectorSizes;
-
-//         if (fstat(params_.fd, &stat_buf) < 0 ) {
-//                 printf("\n\tError: Failed to get the device stat!!!\n");
-//                 return -1;
-//         }
-
-//         if (S_ISREG(stat_buf.st_mode))
-//                 return 0;
-//         else if (S_ISBLK(stat_buf.st_mode)) {
-// //		if (ioctl(params_.fd, BLKDISCARD, &range) < 0)
-//                         printf("Info: This device doesn't support TRIM\n");
-//         } else
-//                 return -1;
-//         return 0;
-// }
-#endif
+zx_status_t MkfsWorker::TrimDevice() { return bc_->Trim(0, params_.total_sectors); }
 
 zx_status_t MkfsWorker::FormatDevice() {
   zx_status_t err = ZX_OK;
@@ -891,14 +868,17 @@
     FX_LOGS(ERROR) << err_msg << "Failed to prepare superblock information";
     return err;
   }
-#if 0  // porting needed
-    // TRIM is not supported
-    // err = f2fs_trim_device();
-    // if (err < 0) {
-    //   printf("\n\tError: Failed to trim whole device!!!\n");
-    //   break;
-    // }
-#endif
+
+  if (err = TrimDevice(); err != ZX_OK) {
+    if (err == ZX_ERR_NOT_SUPPORTED) {
+      FX_LOGS(INFO) << "This device doesn't support TRIM";
+    } else {
+      FX_LOGS(ERROR) << err_msg << "Failed to trim whole device" << err;
+      return err;
+      ;
+    }
+  }
+
   if (err = InitSitArea(); err != ZX_OK) {
     FX_LOGS(ERROR) << err_msg << "Failed to Initialise the SIT AREA" << err;
     return err;
@@ -924,12 +904,8 @@
     return err;
   }
 
-  /*
-   * We should call fsync() to flush out all the dirty pages
-   * in the block device page cache.
-   */
-  bc_->Sync();
-
+  // Ensure that all cached data is flushed in the underlying block device
+  bc_->Flush();
   return err;
 }
 
diff --git a/mkfs.h b/mkfs.h
index f5bc25c..0198a6d 100644
--- a/mkfs.h
+++ b/mkfs.h
@@ -61,10 +61,7 @@
   void PrintUsage();
   void PrintCurrentOption();
 
-#if 0  // porting needed
-  // TODO: Trim support
-  // int f2fs_trim_device()
-#endif
+  zx_status_t TrimDevice();
 };
 
 zx_status_t Mkfs(Bcache* bc, int argc, char** argv);
diff --git a/mount.cc b/mount.cc
new file mode 100644
index 0000000..f4c9866
--- /dev/null
+++ b/mount.cc
@@ -0,0 +1,134 @@
+// 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 <lib/async-loop/cpp/loop.h>
+#include <lib/async-loop/default.h>
+#include <lib/async/dispatcher.h>
+#include <lib/syslog/cpp/macros.h>
+#include <lib/trace-provider/provider.h>
+
+#include "f2fs.h"
+
+namespace f2fs {
+
+// TODO: change .configurable to true once the feature is supported
+const MountOpt default_option[] = {
+    {"background_gc_off", 1, false},
+#ifdef F2FS_ROLL_FORWARD
+    {"disable_roll_forward", 0, false},
+#else
+    {"disable_roll_forward", 1, false},
+#endif
+    {"discard", 1, true},
+    {"no_heap", 1, false},
+    {"nouser_xattr", 1, false},
+    {"noacl", 1, false},
+    {"disable_ext_identify", 0, true},
+    {"active_logs", 6, true},
+};
+
+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";
+  };
+
+  auto fs_or = CreateFsAndRoot(options, loop.dispatcher(), std::move(bc), std::move(export_root),
+                               std::move(on_unmount), serve_layout);
+  if (fs_or.is_error()) {
+    FX_LOGS(ERROR) << "failed to create filesystem object " << fs_or.status_string();
+    return fs_or.error_value();
+  }
+
+  // Relinquish ownership of F2fs object
+  std::unique_ptr<f2fs::F2fs> fs = std::move(fs_or).value();
+  __UNUSED auto relinquish = fs.release();
+
+  ZX_ASSERT(loop.Run() == ZX_ERR_CANCELED);
+
+  return ZX_OK;
+}
+
+MountOptions::MountOptions() {
+  for (uint32_t i = 0; i < kOptMaxNum; i++) {
+    opt_[i] = default_option[i];
+  }
+}
+
+zx_status_t MountOptions::GetValue(const uint32_t opt_id, uint32_t *out) {
+  if (opt_id >= kOptMaxNum)
+    return ZX_ERR_INVALID_ARGS;
+  *out = opt_[opt_id].value;
+  return ZX_OK;
+}
+
+uint32_t MountOptions::GetOptionID(const std::string_view &opt) {
+  uint32_t i;
+  for (i = 0; i < kOptMaxNum; i++) {
+    if (opt_[i].name.compare(opt) == 0) {
+      break;
+    }
+  }
+  return i;
+}
+
+zx_status_t MountOptions::SetValue(const std::string_view &opt, const uint32_t value) {
+  zx_status_t ret = ZX_ERR_INVALID_ARGS;
+  uint32_t id = GetOptionID(opt);
+  if (id < kOptMaxNum && !opt_[id].configurable) {
+    std::cout << opt << " is not configurable." << std::endl;
+  } else {
+    switch (id) {
+      case kOptActiveLogs:
+        if (value != 2 && value != 4 && value != 6) {
+          std::cout << opt << " can be set only to 2, 4, or 6." << std::endl;
+        } else {
+          opt_[id].value = value;
+          ret = ZX_OK;
+        }
+        break;
+      case kOptDiscard:
+      case kOptBgGcOff:
+      case kOptNoHeap:
+      case kOptDisableExtIdentify:
+      case kOptNoUserXAttr:
+      case kOptNoAcl:
+      case kOptDisableRollForward:
+        opt_[id].value = value;
+        ret = ZX_OK;
+        break;
+      default:
+        std::cout << opt << " is not supported." << std::endl;
+        break;
+    };
+  }
+  return ret;
+}
+
+}  // namespace f2fs
diff --git a/mount.h b/mount.h
new file mode 100644
index 0000000..73392b4
--- /dev/null
+++ b/mount.h
@@ -0,0 +1,58 @@
+// 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 THIRD_PARTY_F2FS_MOUNT_H_
+#define THIRD_PARTY_F2FS_MOUNT_H_
+
+namespace f2fs {
+
+constexpr uint32_t kOptMaxNum = 8;
+constexpr uint32_t kOptBgGcOff = 0;
+constexpr uint32_t kOptDisableRollForward = 1;
+constexpr uint32_t kOptDiscard = 2;
+constexpr uint32_t kOptNoHeap = 3;
+constexpr uint32_t kOptNoUserXAttr = 4;
+constexpr uint32_t kOptNoAcl = 5;
+constexpr uint32_t kOptDisableExtIdentify = 6;
+constexpr uint32_t kOptActiveLogs = (kOptMaxNum - 1);
+
+constexpr uint64_t kMountBgGcOff = (1 << kOptBgGcOff);
+constexpr uint64_t kMountDisableRollForward = (1 << kOptDisableRollForward);
+constexpr uint64_t kMountDiscard = (1 << kOptDiscard);
+constexpr uint64_t kMountNoheap = (1 << kOptNoHeap);
+constexpr uint64_t kMountNoXAttr = (1 << kOptNoUserXAttr);
+constexpr uint64_t kMountNoAcl = (1 << kOptNoAcl);
+constexpr uint64_t kMountDisableExtIdentify = (1 << kOptDisableExtIdentify);
+
+struct MountOpt {
+  std::string name;
+  uint32_t value;
+  bool configurable;
+};
+
+class MountOptions {
+ public:
+  MountOptions();
+  MountOptions(const MountOptions &) = default;
+
+  zx_status_t GetValue(const uint32_t opt_id, uint32_t *out);
+  uint32_t GetOptionID(const std::string_view &opt);
+  zx_status_t SetValue(const std::string_view &opt, const uint32_t value);
+  const std::string_view GetNameView(const uint32_t opt_id) {
+    ZX_ASSERT(opt_id < kOptMaxNum);
+    return opt_[opt_id].name;
+  }
+
+ private:
+  MountOpt opt_[kOptMaxNum];
+};
+
+inline void ClearOpt(SbInfo *sbi, uint64_t option) { sbi->mount_opt &= ~option; }
+inline void SetOpt(SbInfo *sbi, uint64_t option) { sbi->mount_opt |= option; }
+inline bool TestOpt(SbInfo *sbi, uint64_t option) { return ((sbi->mount_opt & option) != 0); }
+zx_status_t Mount(const MountOptions &options, std::unique_ptr<f2fs::Bcache> bc);
+
+}  // namespace f2fs
+
+#endif  // THIRD_PARTY_F2FS_MOUNT_H_
diff --git a/namei.cc b/namei.cc
index 5128ae5..bf17419 100644
--- a/namei.cc
+++ b/namei.cc
@@ -75,14 +75,14 @@
 /**
  * Set multimedia files as cold files for hot/cold data separation
  */
-inline void Dir::SetColdFile(const char *name) {
+inline void Dir::SetColdFile(const char *name, VnodeF2fs *vnode) {
   int i;
   uint8_t(*extlist)[8] = Vfs()->RawSb().extension_list;
 
   int count = LeToCpu(Vfs()->RawSb().extension_count);
   for (i = 0; i < count; i++) {
     if (!IsMultimediaFile(name, reinterpret_cast<const char *>(extlist[i]))) {
-      fi_.i_advise |= kFAdviseColdBit;
+      vnode->fi_.i_advise |= kFAdviseColdBit;
       break;
     }
   }
@@ -100,7 +100,7 @@
   vnode->i_name_sp_ = name;
 
   if (!TestOpt(&sbi, kMountDisableExtIdentify))
-    SetColdFile(name.data());
+    SetColdFile(name.data(), vnode);
 
   SetInodeFlag(&vnode->fi_, InodeInfoFlag::kIncLink);
   if (zx_status_t err = AddLink(name, vnode); err != ZX_OK) {
diff --git a/node.cc b/node.cc
index 1d416d3..aed5abd 100644
--- a/node.cc
+++ b/node.cc
@@ -242,7 +242,9 @@
  *  - Mark cold node blocks in their node footer
  *  - Mark cold data pages in page cache
  */
-int NodeMgr::IsColdFile(VnodeF2fs *vnode) { return vnode->fi_.i_advise & kFAdviseColdBit; }
+bool NodeMgr::IsColdFile(VnodeF2fs *vnode) {
+  return ((vnode->fi_.i_advise & kFAdviseColdBit) != 0);
+}
 
 int NodeMgr::IsColdData(Page *page) {
 #if 0  // porting needed
diff --git a/node.h b/node.h
index bb842f4..90a8958 100644
--- a/node.h
+++ b/node.h
@@ -103,7 +103,7 @@
   uint32_t OfsOfNode(Page *node_page);
 
   static int IsColdNode(Page *page);
-  static int IsColdFile(VnodeF2fs *vnode);
+  static bool IsColdFile(VnodeF2fs *vnode);
   static int IsColdData(Page *page);
 
   uint8_t IsDentDnode(Page *page);
@@ -143,6 +143,8 @@
   zx_status_t RecoverInodePage(Page *page);
   void RecoverNodePage(Page *page, Summary *sum, NodeInfo *ni, block_t new_blkaddr);
 
+  void SetColdNode(VnodeF2fs *vnode, Page *page);
+
  private:
   F2fs *fs_;
 
@@ -161,8 +163,6 @@
   void SetColdData(Page *page);
 #endif
 
-  void SetColdNode(VnodeF2fs *vnode, Page *page);
-
   // Functions
   void ClearNodePageDirty(Page *page);
   Page *GetCurrentNatPage(nid_t nid);
diff --git a/segment.cc b/segment.cc
index aab275b..c2fd2b0 100644
--- a/segment.cc
+++ b/segment.cc
@@ -532,16 +532,8 @@
     if (test_and_clear_bit(segno, dirty_i->dirty_segmap[static_cast<int>(DirtyType::kPre)]))
       dirty_i->nr_dirty[static_cast<int>(DirtyType::kPre)]--;
 
-#if 0  // porting needed (Trim)
-    /* Let's use trim */
-    // if (TestOpt(sbi, kMountDiscard))
-    //	blkdev_issue_discard(sbi->sb->s_bdev,
-    //			StartBlock(sbi, segno) <<
-    //			sbi->log_sectors_per_block,
-    //			1 << (sbi->log_sectors_per_block +
-    //				sbi->log_blocks_per_seg),
-    //			GFP_NOFS, 0);
-#endif
+    if (TestOpt(&sbi, kMountDiscard))
+      fs_->bc_->Trim(StartBlock(&sbi, segno), (1 << sbi.log_blocks_per_seg));
   }
   mtx_unlock(&dirty_i->seglist_lock);
 }
@@ -1085,7 +1077,7 @@
     std::cout << "SubmitWritePage error " << ret << std::endl;
   }
 #if 0  // porting needed (bio)
-  //	fs_->bc_->Sync();
+  //	fs_->bc_->Flush();
 
   // 	block_device *bdev = sbi->sb->s_bdev;
 
diff --git a/super.cc b/super.cc
index cf7ec0d..e028594 100644
--- a/super.cc
+++ b/super.cc
@@ -12,30 +12,6 @@
 #if 0  // porting needed
 // static kmem_cache *Inode_cachep;
 
-// enum {
-//   Opt_gc_background_off,
-//   Opt_disable_roll_forward,
-//   Opt_discard,
-//   Opt_noheap,
-//   Opt_nouser_xattr,
-//   Opt_noacl,
-//   Opt_active_logs,
-//   Opt_disable_ext_identify,
-//   Opt_err,
-// };
-
-// static match_table_t f2fs_tokens = {
-//   {Opt_gc_background_off, "background_gc_off"},
-//   {Opt_disable_roll_forward, "disable_roll_forward"},
-//   {Opt_discard, "discard"},
-//   {Opt_noheap, "no_heap"},
-//   {Opt_nouser_xattr, "nouser_xattr"},
-//   {Opt_noacl, "noacl"},
-//   {Opt_active_logs, "active_logs=%u"},
-//   {Opt_disable_ext_identify, "disable_ext_identify"},
-//   {Opt_err, NULL},
-// };
-
 // void F2fs::InitOnce(void *foo)
 // {
 //   InodeInfo *fi = (InodeInfo *) foo;
@@ -208,57 +184,52 @@
 //   return generic_fh_to_parent(sb, fid, fh_len, fh_type,
 //             f2fs_nfs_get_inode);
 // }
-
-// int F2fs::ParseOptions(char *options) {
-  // substring_t args[MAX_OPT_ARGS];
-  // char *p;
-  // int arg = 0;
-
-  // if (!options)
-  //   return 0;
-
-  // while ((p = strsep(&options, ",")) != NULL) {
-  //   int token;
-  //   if (!*p)
-  //     continue;
-  //   /*
-  //    * Initialize args struct so we know whether arg was
-  //    * found; some options take optional arguments.
-  //    */
-  //   args[0].to = args[0].from = NULL;
-  //   token = match_token(p, f2fs_tokens, args);
-
-  //   switch (token) {
-  //   case Opt_gc_background_off:
-  //     ClearOpt(&sbi_.get(), kMountBgGC);
-  //     break;
-  //   case Opt_disable_roll_forward:
-  //     SetOpt(sbi_.get(), kMountDisableRollForward);
-  //     break;
-  //   case Opt_discard:
-  //     SetOpt(sbi_.get(), kMountDiscard);
-  //     break;
-  //   case Opt_noheap:
-  //     SetOpt(sbi_.get(), kMountNoheap);
-  //     break;
-  //   case Opt_active_logs:
-  //     if (args->from && match_int(args, &arg))
-  //       return -EINVAL;
-  //     if (arg != 2 && arg != 4 && arg != 6)
-  //       return -EINVAL;
-  //     sbi_.get()->active_logs = arg;
-  //     break;
-  //   case Opt_disable_ext_identify:
-  //     SetOpt(sbi_.get(), kMountDisableExtIdentify);
-  //     break;
-  //   default:
-  //     return -EINVAL;
-  //   }
-  // }
-  // return 0;
-// }
 #endif
 
+void F2fs::ParseOptions() {
+  for (uint32_t i = 0; i < kOptMaxNum; i++) {
+    uint32_t value;
+    if (mount_options_.GetValue(i, &value) == ZX_OK) {
+      switch (i) {
+        case kOptActiveLogs:
+          sbi_->active_logs = value;
+          break;
+        case kOptDiscard:
+          if (value)
+            SetOpt(sbi_.get(), kMountDiscard);
+          break;
+        case kOptBgGcOff:
+          if (value)
+            SetOpt(sbi_.get(), kMountBgGcOff);
+          break;
+        case kOptNoHeap:
+          if (value)
+            SetOpt(sbi_.get(), kMountNoheap);
+          break;
+        case kOptDisableExtIdentify:
+          if (value)
+            SetOpt(sbi_.get(), kMountDisableExtIdentify);
+          break;
+        case kOptNoUserXAttr:
+          if (value)
+            SetOpt(sbi_.get(), kMountNoXAttr);
+          break;
+        case kOptNoAcl:
+          if (value)
+            SetOpt(sbi_.get(), kMountNoAcl);
+          break;
+        case kOptDisableRollForward:
+          if (value)
+            SetOpt(sbi_.get(), kMountDisableRollForward);
+          break;
+        default:
+          std::cout << mount_options_.GetNameView(i) << " is not supported." << std::endl;
+          break;
+      };
+    }
+  }
+}
+
 loff_t F2fs::MaxFileSize(unsigned bits) {
   loff_t result = kAddrsPerInode;
   loff_t leaf_count = kAddrsPerBlock;
@@ -349,29 +320,11 @@
   // super_block *sb = sbi_->sb;
 
   /* set a temporary block size */
-  // if (!SbSetBlocksize(sb, kBlkSize))
+  // if (!SbSetBlocksize(sb, kBlockSize))
   //  goto free_sbi;
 #endif
 
-  // TODO: remove them after supporting mount options
-  ClearOpt(sbi_.get(), kMountBgGC);
-  ClearOpt(sbi_.get(), kMountDiscard);
-  SetOpt(sbi_.get(), kMountNoheap);
-  ClearOpt(sbi_.get(), kMountXattrUser);
-  ClearOpt(sbi_.get(), kMountPosixAcl);
-  sbi_->active_logs = kNrCursegType;
-  SetOpt(sbi_.get(), kMountDisableExtIdentify);
-#ifdef F2FS_ROLL_FORWARD
-  ClearOpt(sbi_.get(), kMountDisableRollForward);
-#else
-  SetOpt(sbi_.get(), kMountDisableRollForward);
-#endif
-
-#if 0  // porting needed
-  /* parse mount options */
-  // if (ParseOptions((char *)data))
-  //   goto free_sb_buf;
-#endif
+  ParseOptions();
 
   /* sanity checking of raw super */
   if (SanityCheckRawSuper())
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 4c41c24..ae54da2 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -13,6 +13,7 @@
 
 test("f2fs_unittest") {
   sources = [
+    "unit/bcache.cc",
     "unit/mkfs.cc",
     "unit/mount.cc",
   ]
diff --git a/test/unit/bcache.cc b/test/unit/bcache.cc
new file mode 100644
index 0000000..d586a33
--- /dev/null
+++ b/test/unit/bcache.cc
@@ -0,0 +1,38 @@
+// 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 <block-client/cpp/fake-device.h>
+#include <gtest/gtest.h>
+
+#include "third_party/f2fs/f2fs.h"
+
+namespace f2fs {
+namespace {
+
+using block_client::FakeBlockDevice;
+constexpr uint32_t kNumBlocks = kMinVolumeSize / kBlockSize;
+
+TEST(BCacheTest, TrimTest) {
+  {
+    std::unique_ptr<f2fs::Bcache> bc;
+    bool readonly_device = false;
+    auto device = std::make_unique<FakeBlockDevice>(FakeBlockDevice::Config{
+        .block_count = kNumBlocks, .block_size = kDefaultSectorSize, .supports_trim = false});
+    ASSERT_TRUE(device);
+    ASSERT_EQ(f2fs::CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
+    ASSERT_EQ(bc->Trim(0, bc->Maxblk()), ZX_ERR_NOT_SUPPORTED);
+  }
+  {
+    std::unique_ptr<f2fs::Bcache> bc;
+    bool readonly_device = false;
+    auto device = std::make_unique<FakeBlockDevice>(FakeBlockDevice::Config{
+        .block_count = kNumBlocks, .block_size = kDefaultSectorSize, .supports_trim = true});
+    ASSERT_TRUE(device);
+    ASSERT_EQ(f2fs::CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
+    ASSERT_EQ(bc->Trim(0, bc->Maxblk()), ZX_OK);
+  }
+}
+
+}  // namespace
+}  // namespace f2fs
diff --git a/test/unit/mount.cc b/test/unit/mount.cc
index 08669ce..f53f167 100644
--- a/test/unit/mount.cc
+++ b/test/unit/mount.cc
@@ -1,3 +1,233 @@
 // 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 <lib/async-loop/cpp/loop.h>
+#include <lib/async-loop/default.h>
+
+#include <block-client/cpp/fake-device.h>
+#include <gtest/gtest.h>
+
+#include "third_party/f2fs/f2fs.h"
+
+namespace f2fs {
+namespace {
+
+using block_client::FakeBlockDevice;
+constexpr uint32_t kMountVerifyTest = 0;
+constexpr uint32_t kMountDisableExtTest = 1;
+constexpr uint32_t kMountActiveLogsTest = 2;
+
+void MountTestVerifyOptions(F2fs *fs, MountOptions &options) {
+  uint32_t value;
+  SbInfo &sbi = fs->GetSbInfo();
+  for (uint32_t i = 0; i < kOptMaxNum; i++) {
+    ASSERT_EQ(options.GetValue(i, &value), ZX_OK);
+    switch (i) {
+      case kOptActiveLogs:
+        ASSERT_EQ(static_cast<uint32_t>(sbi.active_logs), value);
+        break;
+      case kOptDiscard:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDiscard) == 0));
+        break;
+      case kOptBgGcOff:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountBgGcOff) == 0));
+        break;
+      case kOptNoHeap:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoheap) == 0));
+        break;
+      case kOptDisableExtIdentify:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDisableExtIdentify) == 0));
+        break;
+      case kOptNoUserXAttr:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoXAttr) == 0));
+        break;
+      case kOptNoAcl:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoAcl) == 0));
+        break;
+      case kOptDisableRollForward:
+        ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDisableRollForward) == 0));
+        break;
+    };
+  }
+  ASSERT_EQ(options.GetValue(kOptMaxNum, &value), ZX_ERR_INVALID_ARGS);
+}
+
+void MountTestDisableExt(F2fs *fs, uint32_t expectation) {
+  fbl::RefPtr<VnodeF2fs> data_root;
+  bool result = (expectation > 0 ? true : false);
+  ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK);
+  Dir *root_dir = static_cast<Dir *>(data_root.get());
+
+  for (const char *ext_item : kMediaExtList) {
+    std::string name = "test.";
+    name += ext_item;
+    fbl::RefPtr<fs::Vnode> vnode;
+    // Use S_IFDIR to create regular file
+    ASSERT_EQ(root_dir->Create(name, S_IFREG, &vnode), ZX_OK);
+    File *file = static_cast<File *>(vnode.get());
+    ASSERT_EQ(NodeMgr::IsColdFile(file), result);
+    vnode->Close();
+  }
+}
+
+void TestSegmentType(F2fs *fs, Dir *root_dir, const std::string_view name, bool is_dir,
+                     std::vector<CursegType> &out) {
+  fbl::RefPtr<fs::Vnode> vnode;
+  uint64_t flag = (is_dir ? S_IFDIR : S_IFREG);
+  nid_t nid = 100;
+  uint32_t inode_ofs = 0;
+  uint32_t indirect_node_ofs = 3;
+  ASSERT_EQ(root_dir->Create(name, flag, &vnode), ZX_OK);
+  VnodeF2fs *vn = static_cast<VnodeF2fs *>(vnode.get());
+
+  // data block test
+  Page *page = GrabCachePage(vn, vn->Ino(), 0);
+  CursegType type = fs->Segmgr().GetSegmentType(page, PageType::kData);
+  out.push_back(type);
+  F2fsPutPage(page, 1);
+
+  // Dnode block test
+  page = GrabCachePage(nullptr, NodeIno(&fs->GetSbInfo()), vn->Ino());
+  fs->Nodemgr().FillNodeFooter(page, page->index, vn->Ino(), inode_ofs, true);
+  fs->Nodemgr().SetColdNode(vn, page);
+  type = fs->Segmgr().GetSegmentType(page, PageType::kNode);
+  out.push_back(type);
+  F2fsPutPage(page, 1);
+
+  // indirect node block test
+  page = GrabCachePage(nullptr, NodeIno(&fs->GetSbInfo()), nid);
+  fs->Nodemgr().FillNodeFooter(page, page->index, vn->Ino(), indirect_node_ofs, true);
+  fs->Nodemgr().SetColdNode(vn, page);
+  type = fs->Segmgr().GetSegmentType(page, PageType::kNode);
+  out.push_back(type);
+  F2fsPutPage(page, 1);
+  vnode->Close();
+}
+
+void MountTestActiveLogs(F2fs *fs, MountOptions options) {
+  fbl::RefPtr<VnodeF2fs> data_root;
+  ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK);
+  Dir *root_dir = static_cast<Dir *>(data_root.get());
+  const char *filenames[] = {"dir", "warm.exe", "cold.mp4"};
+  std::vector<CursegType> results(3, CursegType::kNoCheckType);
+  uint32_t num_logs = 0;
+  ASSERT_EQ(options.GetValue(kOptActiveLogs, &num_logs), ZX_OK);
+
+  constexpr int dir_file = 0;
+  constexpr int warm_file = 1;
+  __UNUSED constexpr int cold_file = 2;
+
+  __UNUSED constexpr int data_block = 0;
+  constexpr int dnode_block = 1;
+  constexpr int indirect_node_block = 2;
+
+  for (int i = 0; i < 3; i++) {
+    results.clear();
+    TestSegmentType(fs, root_dir, filenames[i], (i == dir_file), results);
+    for (int j = 0; j < 3; j++) {
+      CursegType type = results[j];
+      if (j == indirect_node_block) {
+        if (num_logs > 2)
+          ASSERT_EQ(type, CursegType::kCursegColdNode);
+        else
+          ASSERT_EQ(type, CursegType::kCursegHotNode);
+      } else if (j == dnode_block) {
+        if (i == dir_file || num_logs == 2) {
+          ASSERT_EQ(type, CursegType::kCursegHotNode);
+        } else if (num_logs == 6) {
+          ASSERT_EQ(type, CursegType::kCursegWarmNode);
+        } else
+          ASSERT_EQ(type, CursegType::kCursegColdNode);
+
+      } else {  // data block case
+        if (i == dir_file || num_logs == 2) {
+          ASSERT_EQ(type, CursegType::kCursegHotData);
+        } else {
+          if (i == warm_file && num_logs == 6)
+            ASSERT_EQ(type, CursegType::kCursegWarmData);
+          else
+            ASSERT_EQ(type, CursegType::kCursegColdData);
+        }
+      }
+    }
+  }
+}
+
+void MountTestMain(MountOptions &options, uint32_t test, uint32_t priv) {
+  std::unique_ptr<f2fs::Bcache> bc;
+  bool readonly_device = false;
+  auto device = std::make_unique<FakeBlockDevice>(
+      FakeBlockDevice::Config{.block_count = kMinVolumeSize / kDefaultSectorSize,
+                              .block_size = kDefaultSectorSize,
+                              .supports_trim = false});
+
+  ASSERT_TRUE(device);
+  ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
+
+  MkfsWorker mkfs(bc.get());
+  ASSERT_EQ(mkfs.DoMkfs(), ZX_OK);
+
+  std::unique_ptr<F2fs> fs;
+  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+
+  // Enclose the channels in a scope to ensure they are closed before we shut down.
+  {
+    zx::channel mount_channel, remote_mount_channel;
+    ASSERT_EQ(zx::channel::create(0, &mount_channel, &remote_mount_channel), ZX_OK);
+    auto fs_or = f2fs::CreateFsAndRoot(
+        options, loop.dispatcher(), std::move(bc), std::move(mount_channel), [] {},
+        ServeLayout::kExportDirectory);
+
+    ASSERT_EQ(fs_or.is_ok(), true);
+    fs = std::move(fs_or).value();
+    switch (test) {
+      case kMountVerifyTest:
+        MountTestVerifyOptions(fs.get(), options);
+        break;
+      case kMountDisableExtTest:
+        MountTestDisableExt(fs.get(), priv);
+        break;
+      case kMountActiveLogsTest:
+        MountTestActiveLogs(fs.get(), options);
+        break;
+      default:
+        ASSERT_EQ(0, 1);
+        break;
+    };
+  }
+  fs->Shutdown([&loop](zx_status_t) { loop.Quit(); });
+  __UNUSED F2fs *relinquish = fs.release();
+
+  ASSERT_EQ(loop.Run(), ZX_ERR_CANCELED);
+}
+
+TEST(MountTest, Verify) {
+  MountOptions options;
+  MountTestMain(options, kMountVerifyTest, 0);
+}
+
+TEST(MountTest, DisableExtOptions) {
+  constexpr uint32_t ShouldNotBeCold = 0;
+  MountOptions options{};
+  ASSERT_EQ(options.SetValue(options.GetNameView(kOptDisableExtIdentify), 1), ZX_OK);
+  MountTestMain(options, kMountDisableExtTest, ShouldNotBeCold);
+}
+
+TEST(MountTest, EnableExtOptions) {
+  constexpr uint32_t ShouldBeCold = 1;
+  MountOptions options{};
+  ASSERT_EQ(options.SetValue(options.GetNameView(kOptDisableExtIdentify), 0), ZX_OK);
+  MountTestMain(options, kMountDisableExtTest, ShouldBeCold);
+}
+
+TEST(MountTest, ActiveLogsOptions) {
+  for (uint32_t i = 2; i <= 6; i += 2) {
+    MountOptions options{};
+    ASSERT_EQ(options.SetValue(options.GetNameView(kOptActiveLogs), i), ZX_OK);
+    MountTestMain(options, kMountActiveLogsTest, 0);
+  }
+}
+
+}  // namespace
+}  // namespace f2fs
diff --git a/tools/main.cc b/tools/main.cc
index b5f22f3..cdcf160 100644
--- a/tools/main.cc
+++ b/tools/main.cc
@@ -28,6 +28,7 @@
 int main(int argc, char** argv) {
   f2fs::MountOptions options;
 
+#ifdef F2FS_BU_DEBUG
   std::cout << "f2fs arg= ";
 
   for (int i = 0; i < argc; i++) {
@@ -35,13 +36,13 @@
   }
 
   std::cout << std::endl;
+#endif
 
   if (argc <= 1) {
-    std::cout << "Hello F2FS!!!" << std::endl;
-    fprintf(stderr, "usage: mkfs [ <options>*] devicepath f2fs\n");
-    fprintf(stderr, "usage: fsck [ <options>*] devicepath f2fs\n");
-    fprintf(stderr, "usage: mount [ <options>*] devicepath f2fs\n");
-    fprintf(stderr, "usage: fsync filename\n");
+    fprintf(stderr, "usage: f2fs mkfs [ <options>*] devicepath f2fs\n");
+    fprintf(stderr, "usage: f2fs fsck [ <options>*] devicepath f2fs\n");
+    fprintf(stderr, "usage: f2fs mount [ <options>*] devicepath f2fs\n");
+    fprintf(stderr, "usage: f2fs fsync filename\n");
     return 0;
   }
 
@@ -75,7 +76,7 @@
   bool readonly_device = false;
 
   if (f2fs::CreateBcache(std::move(device), &readonly_device, &bc) != ZX_OK) {
-    fprintf(stderr, "f2fs: error: cannot create block cache\n");
+    FX_LOGS(ERROR) << "f2fs: error: cannot create block cache";
     return -1;
   }
 
@@ -87,7 +88,7 @@
   } else if (!strcmp(argv[1], "mount")) {
     f2fs::Mount(options, std::move(bc));
   } else {
-    std::cout << "f2fs: unknown operation:" << argv[1] << std::endl;
+    FX_LOGS(ERROR) << "unknown operation:" << argv[1];
   }
 
   return 0;
diff --git a/vnode.cc b/vnode.cc
index 1af9a3a..6436765 100644
--- a/vnode.cc
+++ b/vnode.cc
@@ -63,8 +63,6 @@
   SetInodeFlag(&vnode->fi_, InodeInfoFlag::kNewInode);
 }
 
-// TODO(sukka): fill vfs->members in addtion to size
-// TODO(sukka): if dir/file vnode are defined as different class, check if the ino is for dir/file
 void VnodeF2fs::Create(F2fs *fs, ino_t ino, fbl::RefPtr<VnodeF2fs> *out) {
   Page *node_page = nullptr;
   Inode *ri;
@@ -85,7 +83,6 @@
   rn = static_cast<Node *>(PageAddress(node_page));
   ri = &(rn->i);
 
-  // [sukka] need to check result?
   if (S_ISDIR(ri->i_mode)) {
     *out = fbl::MakeRefCounted<Dir>(fs, ino);
   } else {