[f2fs] Fix tracking i_pino during inline directory rename

Previously, inline directory rename causes i_pino inconsistency.
In the case of the following scenario, a bug can occur.

Let's suppose there are three directories, "/a", "/b", and "/a/c".
- pino of "/a/c" is DIR_A_INO.

Then,
1. Rename "/a/c" to "/b/c"
2. Remount
The parent inode number of "/b/c" should be DIR_B_INO, but it is still DIR_A_INO.

It is because Dir::Rename has old_dir_page(the page having ".." dentry of target directory) and ipage(the inode page of target directory) simultaneously.
Once Dir::Rename is called, it updates i_pino of ipage and flushes it.
Then, it updates ".." dentry of old_dir_page and flushes it.
When target directory supports inline directory entry, old_dir_page becomes same to inode page.
So, old_dir_page overwrites i_pino information of inode page and rolls back to the previous parent inode number.

To fix it, i_pino is updated only in vnode, and updated to the page later.

Change-Id: Id82a36e3188954a1e6307920ad096cad386dd9df
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/613550
Commit-Queue: Chris Suter <csuter@google.com>
Reviewed-by: Chris Suter <csuter@google.com>
diff --git a/src/storage/f2fs/dir.cc b/src/storage/f2fs/dir.cc
index bdd540b..cd24d23 100644
--- a/src/storage/f2fs/dir.cc
+++ b/src/storage/f2fs/dir.cc
@@ -328,7 +328,6 @@
 
   /* copy dentry info. to this inode page */
   rn = static_cast<Node *>(PageAddress(ipage));
-  rn->i.i_pino = CpuToLe(Ino());
   rn->i.i_namelen = CpuToLe(vnode->GetNameLen());
   memcpy(rn->i.i_name, vnode->GetName(), vnode->GetNameLen());
 #if 0  // porting needed
diff --git a/src/storage/f2fs/namei.cc b/src/storage/f2fs/namei.cc
index f59cd00..3fee78b 100644
--- a/src/storage/f2fs/namei.cc
+++ b/src/storage/f2fs/namei.cc
@@ -539,6 +539,7 @@
       }
     }
 
+    old_vnode->SetParentNid(new_dir->Ino());
     old_vnode->SetCTime(cur_time);
     old_vnode->SetFlag(InodeInfoFlag::kNeedCp);
     old_vnode->MarkInodeDirty();
diff --git a/src/storage/f2fs/test/unit/inline.cc b/src/storage/f2fs/test/unit/inline.cc
index 24f516b..c9561cc 100644
--- a/src/storage/f2fs/test/unit/inline.cc
+++ b/src/storage/f2fs/test/unit/inline.cc
@@ -285,5 +285,90 @@
   EXPECT_EQ(Fsck(std::move(bc)), ZX_OK);
 }
 
+TEST(InlineDirTest, InlineDirPino) {
+  std::unique_ptr<Bcache> bc;
+  FileTester::MkfsOnFakeDev(&bc);
+
+  std::unique_ptr<F2fs> fs;
+  MountOptions options{};
+
+  // Enable inline dir option
+  ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 1), ZX_OK);
+  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+  FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
+
+  fbl::RefPtr<VnodeF2fs> root;
+  FileTester::CreateRoot(fs.get(), &root);
+
+  fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
+
+  // Inline dir creation
+  fbl::RefPtr<fs::Vnode> vnode;
+  ASSERT_EQ(root_dir->Create("a", S_IFDIR, &vnode), ZX_OK);
+  fbl::RefPtr<Dir> a_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
+  ASSERT_EQ(a_dir->GetParentNid(), root_dir->Ino());
+
+  ASSERT_EQ(root_dir->Create("b", S_IFDIR, &vnode), ZX_OK);
+  fbl::RefPtr<Dir> b_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
+  ASSERT_EQ(b_dir->GetParentNid(), root_dir->Ino());
+
+  ASSERT_EQ(a_dir->Create("c", S_IFDIR, &vnode), ZX_OK);
+  fbl::RefPtr<Dir> c_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
+  ASSERT_EQ(c_dir->GetParentNid(), a_dir->Ino());
+
+  ASSERT_EQ(a_dir->Create("d", S_IFREG, &vnode), ZX_OK);
+  fbl::RefPtr<File> d1_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
+  ASSERT_EQ(d1_file->GetParentNid(), a_dir->Ino());
+
+  ASSERT_EQ(b_dir->Create("d", S_IFREG, &vnode), ZX_OK);
+  fbl::RefPtr<File> d2_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
+  ASSERT_EQ(d2_file->GetParentNid(), b_dir->Ino());
+
+  // rename "/a/c" to "/b/c" and "/a/d" to "/b/d"
+  ASSERT_EQ(a_dir->Rename(b_dir, "c", "c", true, true), ZX_OK);
+  ASSERT_EQ(a_dir->Rename(b_dir, "d", "d", false, false), ZX_OK);
+
+  // Check i_pino of renamed directory
+  ASSERT_EQ(c_dir->GetParentNid(), b_dir->Ino());
+  ASSERT_EQ(d1_file->GetParentNid(), b_dir->Ino());
+
+  ASSERT_EQ(d1_file->Close(), ZX_OK);
+  ASSERT_EQ(d2_file->Close(), ZX_OK);
+  ASSERT_EQ(c_dir->Close(), ZX_OK);
+  ASSERT_EQ(b_dir->Close(), ZX_OK);
+  ASSERT_EQ(a_dir->Close(), ZX_OK);
+  ASSERT_EQ(root_dir->Close(), ZX_OK);
+  root_dir = a_dir = b_dir = c_dir = nullptr;
+  d1_file = d2_file = nullptr;
+
+  // Remount
+  FileTester::Unmount(std::move(fs), &bc);
+  FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
+
+  FileTester::CreateRoot(fs.get(), &root);
+  root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
+
+  FileTester::Lookup(root_dir.get(), "b", &vnode);
+  b_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
+  FileTester::Lookup(b_dir.get(), "c", &vnode);
+  c_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
+  FileTester::Lookup(b_dir.get(), "d", &vnode);
+  d1_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
+
+  // Check i_pino of renamed directory
+  ASSERT_EQ(c_dir->GetParentNid(), b_dir->Ino());
+  ASSERT_EQ(d1_file->GetParentNid(), b_dir->Ino());
+
+  ASSERT_EQ(d1_file->Close(), ZX_OK);
+  ASSERT_EQ(c_dir->Close(), ZX_OK);
+  ASSERT_EQ(b_dir->Close(), ZX_OK);
+  ASSERT_EQ(root_dir->Close(), ZX_OK);
+  root_dir = b_dir = c_dir = nullptr;
+  d1_file = nullptr;
+
+  FileTester::Unmount(std::move(fs), &bc);
+  EXPECT_EQ(Fsck(std::move(bc), &bc), ZX_OK);
+}
+
 }  // namespace
 }  // namespace f2fs
diff --git a/src/storage/f2fs/vnode.cc b/src/storage/f2fs/vnode.cc
index 9e5a3a2..20f848f0 100644
--- a/src/storage/f2fs/vnode.cc
+++ b/src/storage/f2fs/vnode.cc
@@ -319,6 +319,7 @@
   ri->i_current_depth = CpuToLe(static_cast<uint32_t>(GetCurDirDepth()));
   ri->i_xattr_nid = CpuToLe(GetXattrNid());
   ri->i_flags = CpuToLe(GetInodeFlags());
+  ri->i_pino = CpuToLe(GetParentNid());
   ri->i_generation = CpuToLe(GetGeneration());
   ri->i_dir_level = GetDirLevel();