[f2fs] Add  unit tests for RecoverOrphanInodes

Test: f2fs-unittest f2fs-fs-tests f2fs-slow-fs-tests

Change-Id: Iac26b1481353784880a9464f37d28606380382cb
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/568221
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/checkpoint.cc b/checkpoint.cc
index 3290833..7c4cbc9 100644
--- a/checkpoint.cc
+++ b/checkpoint.cc
@@ -175,6 +175,7 @@
     ZX_ASSERT(GetVCache().RemoveDirty(vnode) == ZX_OK);
   }
 }
+
 void F2fs::AddOrphanInode(nid_t ino) {
   SbInfo &sbi = GetSbInfo();
   OrphanInodeEntry *new_entry = nullptr, *orphan = nullptr;
@@ -244,17 +245,16 @@
   vnode->ClearNlink();
 
   // truncate all the data and nodes in VnodeF2fs::Recycle()
-  Iput(vnode.get());
+  // Iput(vnode.get());
   vnode.reset();
 }
 
-int F2fs::RecoverOrphanInodes() {
+zx_status_t F2fs::RecoverOrphanInodes() {
   SbInfo &sbi = GetSbInfo();
   block_t start_blk, orphan_blkaddr, i, j;
 
   if (!(GetCheckpoint(&sbi)->ckpt_flags & kCpOrphanPresentFlag))
-    return 0;
-
+    return ZX_OK;
   sbi.por_doing = 1;
   start_blk = StartCpAddr(&sbi) + 1;
   orphan_blkaddr = StartSumAddr(&sbi) - 1;
@@ -273,7 +273,7 @@
   /* clear Orphan Flag */
   GetCheckpoint(&sbi)->ckpt_flags &= (~kCpOrphanPresentFlag);
   sbi.por_doing = 0;
-  return 0;
+  return ZX_OK;
 }
 
 void F2fs::WriteOrphanInodes(block_t start_blk) {
diff --git a/super.cc b/super.cc
index 448237c..0725c75 100644
--- a/super.cc
+++ b/super.cc
@@ -362,7 +362,7 @@
 
   /* if there are nt orphan nodes free them */
   err = ZX_ERR_INVALID_ARGS;
-  if (!(sbi_->ckpt->ckpt_flags & kCpUmountFlag) && RecoverOrphanInodes())
+  if (RecoverOrphanInodes() != ZX_OK)
     goto free_node_inode;
 
   /* read root inode and dentry */
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 77bb5ad..940db47 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -27,6 +27,7 @@
     "unit/mkfs.cc",
     "unit/mount.cc",
     "unit/node.cc",
+    "unit/orphan.cc",
     "unit/segment.cc",
     "unit/unit_lib.cc",
     "unit/vnode_cache.cc",
diff --git a/test/unit/orphan.cc b/test/unit/orphan.cc
new file mode 100644
index 0000000..aa3ad69
--- /dev/null
+++ b/test/unit/orphan.cc
@@ -0,0 +1,102 @@
+// 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/syslog/cpp/macros.h>
+
+#include <cstddef>
+#include <vector>
+
+#include <block-client/cpp/fake-device.h>
+#include <gtest/gtest.h>
+
+#include "third_party/f2fs/f2fs.h"
+#include "unit_lib.h"
+
+namespace f2fs {
+namespace {
+
+constexpr uint32_t kOrphanCnt = 10;
+
+TEST(OrphanInode, RecoverOrphanInode) {
+  std::unique_ptr<Bcache> bc;
+  unittest_lib::MkfsOnFakeDev(&bc);
+
+  std::unique_ptr<F2fs> fs;
+  MountOptions options{};
+  unittest_lib::MountWithOptions(options, &bc, &fs);
+
+  SbInfo &sbi = fs->GetSbInfo();
+
+  fbl::RefPtr<VnodeF2fs> root;
+  unittest_lib::CreateRoot(fs.get(), &root);
+  fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
+
+  ASSERT_FALSE(GetCheckpoint(&sbi)->ckpt_flags & kCpOrphanPresentFlag);
+
+  // 1. Create files
+  std::vector<fbl::RefPtr<VnodeF2fs>> vnodes;
+  std::vector<uint32_t> inos;
+
+  ASSERT_EQ(fs->ValidInodeCount(), static_cast<uint64_t>(1));
+  ASSERT_EQ(fs->ValidNodeCount(), static_cast<uint64_t>(1));
+  ASSERT_EQ(fs->ValidUserBlocks(), static_cast<uint64_t>(2));
+
+  unittest_lib::CreateChildren(fs.get(), vnodes, inos, root_dir, "orphan_", kOrphanCnt);
+  ASSERT_EQ(vnodes.size(), kOrphanCnt);
+  ASSERT_EQ(inos.size(), kOrphanCnt);
+
+  ASSERT_EQ(fs->ValidInodeCount(), static_cast<uint64_t>(kOrphanCnt + 1));
+  ASSERT_EQ(fs->ValidNodeCount(), static_cast<uint64_t>(kOrphanCnt + 1));
+  ASSERT_EQ(fs->ValidUserBlocks(), static_cast<uint64_t>(kOrphanCnt + 2));
+
+  for (const auto &iter : vnodes) {
+    ASSERT_EQ(iter->GetNlink(), static_cast<uint32_t>(1));
+  }
+
+  // 2. Make orphan inodes
+  ASSERT_EQ(fs->GetSbInfo().n_orphans, static_cast<uint64_t>(0));
+  unittest_lib::DeleteChildren(vnodes, root_dir, kOrphanCnt);
+  ASSERT_EQ(fs->GetSbInfo().n_orphans, kOrphanCnt);
+
+  for (const auto &iter : vnodes) {
+    ASSERT_EQ(iter->GetNlink(), (uint32_t)0);
+  }
+
+  fs->WriteCheckpoint(false, true);
+
+  // 3. Sudden power off
+  for (const auto &iter : vnodes) {
+    iter->Close();
+  }
+
+  vnodes.clear();
+  vnodes.shrink_to_fit();
+  ASSERT_EQ(root_dir->Close(), ZX_OK);
+  root_dir.reset();
+
+  unittest_lib::SuddenPowerOff(std::move(fs), &bc);
+
+  // 4. Remount and recover orphan inodes
+  unittest_lib::MountWithOptions(options, &bc, &fs);
+
+  ASSERT_EQ(fs->GetSbInfo().n_orphans, static_cast<uint64_t>(0));
+
+  ASSERT_EQ(fs->ValidInodeCount(), static_cast<uint64_t>(1));
+  ASSERT_EQ(fs->ValidNodeCount(), static_cast<uint64_t>(1));
+  ASSERT_EQ(fs->ValidUserBlocks(), static_cast<uint64_t>(2));
+
+  // Check Orphan nids has been freed
+  for (const auto &iter : inos) {
+    NodeInfo ni;
+    fs->Nodemgr().GetNodeInfo(iter, &ni);
+    ASSERT_EQ(ni.blk_addr, kNullAddr);
+  }
+
+  unittest_lib::Unmount(std::move(fs), &bc);
+}
+
+}  // namespace
+}  // namespace f2fs
diff --git a/test/unit/unit_lib.cc b/test/unit/unit_lib.cc
index 0e6004e..504f628 100644
--- a/test/unit/unit_lib.cc
+++ b/test/unit/unit_lib.cc
@@ -36,6 +36,20 @@
   fs.reset();
 }
 
+void SuddenPowerOff(std::unique_ptr<F2fs> fs, std::unique_ptr<Bcache> *bc) {
+  SbInfo &sbi = fs->GetSbInfo();
+
+  fs->GetVCache().Reset();
+
+  // destroy f2fs internal modules
+  fs->Nodemgr().DestroyNodeManager();
+  fs->Segmgr().DestroySegmentManager();
+
+  delete GetCheckpoint(&sbi);
+  fs->ResetBc(bc);
+  fs.reset();
+}
+
 void CreateRoot(F2fs *fs, fbl::RefPtr<VnodeF2fs> *out) {
   ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, out), ZX_OK);
   ASSERT_EQ((*out)->Open((*out)->ValidateOptions(fs::VnodeConnectionOptions()).value(), nullptr),
@@ -79,6 +93,16 @@
   }
 }
 
+void DeleteChildren(std::vector<fbl::RefPtr<VnodeF2fs>> &vnodes, fbl::RefPtr<Dir> &parent,
+                    uint32_t inode_cnt) {
+  uint32_t deleted_file_cnt = 0;
+  for (const auto &iter : vnodes) {
+    ASSERT_EQ(parent->Unlink(iter->GetName(), false), ZX_OK);
+    deleted_file_cnt++;
+  }
+  ASSERT_EQ(deleted_file_cnt, inode_cnt);
+}
+
 void VnodeWithoutParent(F2fs *fs, uint32_t mode, fbl::RefPtr<VnodeF2fs> &vnode) {
   nid_t inode_nid;
   ASSERT_TRUE(fs->Nodemgr().AllocNid(&inode_nid));
diff --git a/test/unit/unit_lib.h b/test/unit/unit_lib.h
index 0ba44a0..ef995f9 100644
--- a/test/unit/unit_lib.h
+++ b/test/unit/unit_lib.h
@@ -17,6 +17,7 @@
 void MountWithOptions(MountOptions &options, std::unique_ptr<Bcache> *bc,
                       std::unique_ptr<F2fs> *fs);
 void Unmount(std::unique_ptr<F2fs> fs, std::unique_ptr<Bcache> *bc);
+void SuddenPowerOff(std::unique_ptr<F2fs> fs, std::unique_ptr<Bcache> *bc);
 
 void CreateRoot(F2fs *fs, fbl::RefPtr<VnodeF2fs> *out);
 void Lookup(VnodeF2fs *parent, std::string_view name, fbl::RefPtr<fs::Vnode> *out);
@@ -26,6 +27,8 @@
 void CreateChildren(F2fs *fs, std::vector<fbl::RefPtr<VnodeF2fs>> &vnodes,
                     std::vector<uint32_t> &inos, fbl::RefPtr<Dir> &parent, std::string name,
                     uint32_t inode_cnt);
+void DeleteChildren(std::vector<fbl::RefPtr<VnodeF2fs>> &vnodes, fbl::RefPtr<Dir> &parent,
+                    uint32_t inode_cnt);
 
 void VnodeWithoutParent(F2fs *fs, uint32_t mode, fbl::RefPtr<VnodeF2fs> &vnode);