[f2fs] Clean up fs_lock

It removes unnecessary fs_locks except for two.
Those are enough for blocking file operations
and keeping writes in order during checkpoint.

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

Change-Id: I8278ec4d6983a75775744a86e896d4eeffd9dd7f
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/568641
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/admin.h b/admin.h
index beebf15..45eaa72 100644
--- a/admin.h
+++ b/admin.h
@@ -7,8 +7,6 @@
 
 namespace f2fs {
 
-class F2fs;
-
 class AdminService final : public fidl::WireServer<fuchsia_fs::Admin>, public fs::Service {
  public:
   AdminService(async_dispatcher_t* dispatcher, F2fs* f2fs);
diff --git a/checkpoint.cc b/checkpoint.cc
index 7c4cbc9..4c02787 100644
--- a/checkpoint.cc
+++ b/checkpoint.cc
@@ -571,7 +571,6 @@
  */
 void F2fs::BlockOperations() TA_NO_THREAD_SAFETY_ANALYSIS {
   SbInfo &sbi = GetSbInfo();
-  int t;
   struct WritebackControl wbc = {
 #if 0  // porting needed
       // .nr_to_write = LONG_MAX,
@@ -581,46 +580,34 @@
 #endif
   };
 
-  /* Stop renaming operation */
-  mutex_lock_op(&sbi, LockType::kRename);
-  mutex_lock_op(&sbi, LockType::kDentryOps);
-
 retry_dents:
-  /* write all the dirty dentry pages */
+  // write all the dirty dentry pages
   SyncDirtyDirInodes();
 
-  mutex_lock_op(&sbi, LockType::kDataWrtie);
+  // Stop file operation
+  mutex_lock_op(&sbi, LockType::kFileOp);
   if (GetPages(&sbi, CountType::kDirtyDents)) {
-    mutex_unlock_op(&sbi, LockType::kDataWrtie);
+    mutex_unlock_op(&sbi, LockType::kFileOp);
     goto retry_dents;
   }
 
-  /* block all the operations */
-  for (t = static_cast<int>(LockType::kDataNew); t <= static_cast<int>(LockType::kNodeTrunc); t++)
-    mutex_lock_op(&sbi, static_cast<LockType>(t));
-
-  fbl::AutoLock write_inode(&sbi.write_inode);
-
-  /*
-   * POR: we should ensure that there is no dirty node pages
-   * until finishing nat/sit flush.
-   */
+  // POR: we should ensure that there is no dirty node pages
+  // until finishing nat/sit flush.
 retry:
   Nodemgr().SyncNodePages(0, &wbc);
 
-  mutex_lock_op(&sbi, LockType::kNodeWrite);
+  mutex_lock_op(&sbi, LockType::kNodeOp);
 
   if (GetPages(&sbi, CountType::kDirtyNodes)) {
-    mutex_unlock_op(&sbi, LockType::kNodeWrite);
+    mutex_unlock_op(&sbi, LockType::kNodeOp);
     goto retry;
   }
 }
 
 void F2fs::UnblockOperations() TA_NO_THREAD_SAFETY_ANALYSIS {
   SbInfo &sbi = GetSbInfo();
-  int t;
-  for (t = static_cast<int>(LockType::kNodeWrite); t >= static_cast<int>(LockType::kRename); t--)
-    mutex_unlock_op(&sbi, static_cast<LockType>(t));
+  mutex_unlock_op(&sbi, LockType::kNodeOp);
+  mutex_unlock_op(&sbi, LockType::kFileOp);
 }
 
 void F2fs::DoCheckpoint(bool is_umount) {
diff --git a/data.cc b/data.cc
index a57b10c..5938411 100644
--- a/data.cc
+++ b/data.cc
@@ -516,7 +516,7 @@
 #endif
 
   do {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDataWrtie)]);
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
     if (IsDir()) {
       DecPageCount(&sbi, CountType::kDirtyDents);
 #if 0  // porting needed
@@ -601,10 +601,9 @@
     return ZX_ERR_NO_MEMORY;
 #endif
 
+  fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+  std::lock_guard write_lock(io_lock_);
   do {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDataNew)]);
-    std::lock_guard write_lock(io_lock_);
-
     SetNewDnode(&dn, this, NULL, NULL, 0);
     if (zx_status_t err = Vfs()->Nodemgr().GetDnodeOfData(&dn, index, 0); err != ZX_OK) {
       F2fsPutPage(*pagep, 1);
diff --git a/dir.cc b/dir.cc
index 627a9a0..71f92a0 100644
--- a/dir.cc
+++ b/dir.cc
@@ -207,9 +207,6 @@
 }
 
 void Dir::SetLink(DirEntry *de, Page *page, VnodeF2fs *vnode) {
-  SbInfo &sbi = Vfs()->GetSbInfo();
-
-  fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDentryOps)]);
   std::lock_guard write_lock(io_lock_);
 #if 0  // porting needed
   // lock_page(page);
@@ -356,7 +353,6 @@
   f2fs_hash_t dentry_hash;
   DirEntry *de;
   unsigned int nbucket, nblock;
-  SbInfo &sbi = Vfs()->GetSbInfo();
   int namelen = name.length();
   Page *dentry_page = nullptr;
   DentryBlock *dentry_blk = nullptr;
@@ -366,7 +362,6 @@
 
   if (TestFlag(InodeInfoFlag::kInlineDentry)) {
     bool is_converted = false;
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDentryOps)]);
     std::lock_guard write_lock(io_lock_);
     if (err = AddInlineEntry(name, vnode, &is_converted); err != ZX_OK)
       return err;
@@ -397,63 +392,56 @@
     bidx = DirBlockIndex(level, (dentry_hash % nbucket));
 
     for (block = bidx; block <= (bidx + nblock - 1); block++) {
-      fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDentryOps)]);
       std::lock_guard write_lock(io_lock_);
       if (err = GetNewDataPage(block, true, &dentry_page); err != ZX_OK) {
         return err;
       }
 
-#if 0  // porting needed
+      // porting needed
       // dentry_blk = kmap(dentry_page);
-#else
       dentry_blk = reinterpret_cast<DentryBlock *>(dentry_page->data);
-#endif
       bit_pos = RoomForFilename(dentry_blk, slots);
       if (bit_pos < kNrDentryInBlock) {
-#if 0  // porting needed
-       // if (err = InitInodeMetadata(vnode, dentry); err == ZX_OK) {
-#else
+        // porting needed
+        // if (err = InitInodeMetadata(vnode, dentry); err == ZX_OK) {
         if (err = InitInodeMetadata(vnode); err == ZX_OK) {
-#endif
-        WaitOnPageWriteback(dentry_page);
+          WaitOnPageWriteback(dentry_page);
 
-        de = &dentry_blk->dentry[bit_pos];
-        de->hash_code = CpuToLe(dentry_hash);
-        de->name_len = CpuToLe(static_cast<uint16_t>(namelen));
-        memcpy(dentry_blk->filename[bit_pos], name.data(), namelen);
-        de->ino = CpuToLe(vnode->Ino());
-        SetDeType(de, vnode);
-        for (i = 0; i < slots; i++)
-          test_and_set_bit_le(bit_pos + i, &dentry_blk->dentry_bitmap);
+          de = &dentry_blk->dentry[bit_pos];
+          de->hash_code = CpuToLe(dentry_hash);
+          de->name_len = CpuToLe(static_cast<uint16_t>(namelen));
+          memcpy(dentry_blk->filename[bit_pos], name.data(), namelen);
+          de->ino = CpuToLe(vnode->Ino());
+          SetDeType(de, vnode);
+          for (i = 0; i < slots; i++)
+            test_and_set_bit_le(bit_pos + i, &dentry_blk->dentry_bitmap);
 #if 0  // porting needed
        // set_page_dirty(dentry_page);
 #else
           FlushDirtyDataPage(Vfs(), dentry_page);
 #endif
-        UpdateParentMetadata(vnode, current_depth);
+          UpdateParentMetadata(vnode, current_depth);
+        }
+
+        if (TestFlag(InodeInfoFlag::kUpdateDir)) {
+          WriteInode(nullptr);
+          ClearFlag(InodeInfoFlag::kUpdateDir);
+        }
+
+        // porting needed
+        // kunmap(dentry_page);
+        F2fsPutPage(dentry_page, 1);
+        return err;
       }
 
-      if (TestFlag(InodeInfoFlag::kUpdateDir)) {
-        WriteInode(nullptr);
-        ClearFlag(InodeInfoFlag::kUpdateDir);
-      }
-
-#if 0  // porting needed
-       // kunmap(dentry_page);
-#endif
+      // porting needed
+      // kunmap(dentry_page);
       F2fsPutPage(dentry_page, 1);
-      return err;
     }
 
-#if 0  // porting needed
-      // kunmap(dentry_page);
-#endif
-    F2fsPutPage(dentry_page, 1);
+    /* Move to next level to find the empty slot for new dentry */
+    ++level;
   }
-
-  /* Move to next level to find the empty slot for new dentry */
-  ++level;
-}
 }
 
 /**
@@ -471,7 +459,6 @@
   void *kaddr = PageAddress(page);
   int i;
 
-  fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDentryOps)]);
   std::lock_guard write_lock(io_lock_);
 
   if (TestFlag(InodeInfoFlag::kInlineDentry)) {
diff --git a/dir.h b/dir.h
index 1b6059e..e2a97aa 100644
--- a/dir.h
+++ b/dir.h
@@ -42,72 +42,73 @@
   // Required for memory management, see the class comment above Vnode for more.
   void fbl_recycle() { RecycleNode(); }
 
+  // Lookup
   zx_status_t Lookup(std::string_view name, fbl::RefPtr<fs::Vnode> *out) final;
+  zx_status_t DoLookup(std::string_view name, fbl::RefPtr<fs::Vnode> *out);
+  DirEntry *FindEntry(std::string_view name, Page **res_page) __TA_EXCLUDES(io_lock_);
+  DirEntry *FindInInlineDir(const std::string_view &name, Page **res_page);
+  DirEntry *FindInBlock(Page *dentry_page, const char *name, int namelen, int *max_slots,
+                        f2fs_hash_t namehash, Page **res_page);
+  DirEntry *FindInLevel(unsigned int level, std::string_view name, int namelen,
+                        f2fs_hash_t namehash, Page **res_page);
   zx_status_t Readdir(fs::VdirCookie *cookie, void *dirents, size_t len, size_t *out_actual) final
       __TA_EXCLUDES(io_lock_);
-  zx_status_t Create(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out) final;
-  zx_status_t Link(std::string_view name, fbl::RefPtr<fs::Vnode> _target) final;
-  zx_status_t Unlink(std::string_view name, bool must_be_dir) final;
+  zx_status_t ReadInlineDir(fs::VdirCookie *cookie, void *dirents, size_t len, size_t *out_actual);
+
+  // delete & set link
   zx_status_t Rename(fbl::RefPtr<fs::Vnode> _newdir, std::string_view oldname,
                      std::string_view newname, bool src_must_be_dir, bool dst_must_be_dir);
+  void SetLink(DirEntry *de, Page *page, VnodeF2fs *inode) __TA_EXCLUDES(io_lock_)
+      __TA_EXCLUDES(io_lock_);
+  DirEntry *ParentDir(Page **p);
+  DirEntry *ParentInlineDir(Page **p);
+  bool IsEmptyDir();
+  bool IsEmptyInlineDir();
+  zx::status<bool> IsSubdir(Dir *possible_dir);
 
+  // create
+  zx_status_t Link(std::string_view name, fbl::RefPtr<fs::Vnode> _target) final;
+  zx_status_t Create(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out) final;
+  zx_status_t DoCreate(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out);
+  zx_status_t NewInode(uint32_t mode, fbl::RefPtr<VnodeF2fs> *out);
+  zx_status_t Mkdir(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out);
+  zx_status_t AddLink(std::string_view name, VnodeF2fs *vnode) __TA_EXCLUDES(io_lock_)
+      __TA_EXCLUDES(io_lock_);
+  zx_status_t AddInlineEntry(std::string_view name, VnodeF2fs *vnode, bool *is_converted);
+  unsigned int RoomInInlineDir(InlineDentry *dentry_blk, int slots);
+  zx_status_t ConvertInlineDir(InlineDentry *inline_dentry);
+  int RoomForFilename(DentryBlock *dentry_blk, int slots);
+  void UpdateParentMetadata(VnodeF2fs *inode, unsigned int current_depth);
+  zx_status_t InitInodeMetadata(VnodeF2fs *vnode);
+  zx_status_t MakeEmpty(VnodeF2fs *vnode, VnodeF2fs *parent);
+  zx_status_t MakeEmptyInlineDir(VnodeF2fs *vnode, VnodeF2fs *parent);
+  void InitDentInode(VnodeF2fs *vnode, Page *ipage);
+
+  // delete
+  zx_status_t Unlink(std::string_view name, bool must_be_dir) final;
+  zx_status_t Rmdir(Dir *vnode, std::string_view name);
+  zx_status_t DoUnlink(VnodeF2fs *vnode, std::string_view name);
+  void DeleteEntry(DirEntry *dentry, Page *page, VnodeF2fs *vnode) __TA_EXCLUDES(io_lock_)
+      __TA_EXCLUDES(io_lock_);
+  void DeleteInlineEntry(DirEntry *dentry, Page *page, VnodeF2fs *vnode);
+
+  // helper
+  ino_t InodeByName(std::string_view name);
+  int IsMultimediaFile(const char *s, const char *sub);
+  void SetColdFile(const char *name, VnodeF2fs *vnode);
   uint64_t DirBlocks();
   static unsigned int DirBuckets(unsigned int level);
   static unsigned int BucketBlocks(unsigned int level);
   void SetDeType(DirEntry *de, VnodeF2fs *vnode);
   static uint64_t DirBlockIndex(unsigned int level, unsigned int idx);
   bool EarlyMatchName(const char *name, int namelen, f2fs_hash_t namehash, DirEntry *de);
-  DirEntry *FindInBlock(Page *dentry_page, const char *name, int namelen, int *max_slots,
-                        f2fs_hash_t namehash, Page **res_page);
-  DirEntry *FindInLevel(unsigned int level, std::string_view name, int namelen,
-                        f2fs_hash_t namehash, Page **res_page);
-  DirEntry *FindEntry(std::string_view name, Page **res_page) __TA_EXCLUDES(io_lock_);
-  DirEntry *ParentDir(Page **p);
-  ino_t InodeByName(std::string_view name);
-  void SetLink(DirEntry *de, Page *page, VnodeF2fs *inode) __TA_EXCLUDES(io_lock_);
-  void InitDentInode(VnodeF2fs *vnode, Page *ipage);
-#if 0  // porting needed
-  // zx_status_t InitInodeMetadata(VnodeF2fs *vnode, dentry *dentry);
-#else
-  zx_status_t InitInodeMetadata(VnodeF2fs *vnode);
-#endif
-  void UpdateParentMetadata(VnodeF2fs *inode, unsigned int current_depth);
-  int RoomForFilename(DentryBlock *dentry_blk, int slots);
-  zx_status_t AddLink(std::string_view name, VnodeF2fs *vnode) __TA_EXCLUDES(io_lock_);
-  void DeleteEntry(DirEntry *dentry, Page *page, VnodeF2fs *vnode) __TA_EXCLUDES(io_lock_);
-  zx_status_t MakeEmpty(VnodeF2fs *vnode, VnodeF2fs *parent);
-  bool IsEmptyDir();
 
-  zx_status_t NewInode(uint32_t mode, fbl::RefPtr<VnodeF2fs> *out);
-  int IsMultimediaFile(const char *s, const char *sub);
-  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);
 //   dentry *F2fsGetParent(dentry *child);
-#endif
-  zx_status_t DoUnlink(VnodeF2fs *vnode, std::string_view name);
-#if 0  // porting needed
 //   int F2fsSymlink(dentry *dentry, const char *symname);
-#endif
-  zx_status_t Mkdir(std::string_view name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out);
-  zx_status_t Rmdir(Dir *vnode, std::string_view name);
-#if 0  // porting needed
 //   int F2fsMknod(dentry *dentry, umode_t mode, dev_t rdev);
 #endif
-  zx_status_t DoLookup(std::string_view name, fbl::RefPtr<fs::Vnode> *out);
-  // Check if possible_child is its subdir
-  zx::status<bool> IsSubdir(Dir *possible_dir);
-
-  DirEntry *FindInInlineDir(const std::string_view &name, Page **res_page);
-  DirEntry *ParentInlineDir(Page **p);
-  zx_status_t MakeEmptyInlineDir(VnodeF2fs *vnode, VnodeF2fs *parent);
-  unsigned int RoomInInlineDir(InlineDentry *dentry_blk, int slots);
-  zx_status_t ConvertInlineDir(InlineDentry *inline_dentry);
-  zx_status_t AddInlineEntry(std::string_view name, VnodeF2fs *vnode, bool *is_converted);
-  void DeleteInlineEntry(DirEntry *dentry, Page *page, VnodeF2fs *vnode);
-  bool IsEmptyInlineDir();
-  zx_status_t ReadInlineDir(fs::VdirCookie *cookie, void *dirents, size_t len, size_t *out_actual);
 };
 
 }  // namespace f2fs
diff --git a/f2fs.h b/f2fs.h
index 056b677..6b79972 100644
--- a/f2fs.h
+++ b/f2fs.h
@@ -39,7 +39,6 @@
 #include "src/lib/storage/vfs/cpp/shared_mutex.h"
 #include "src/lib/storage/vfs/cpp/service.h"
 
-#include "admin.h"
 #include "f2fs_types.h"
 #include "f2fs_lib.h"
 #include "f2fs_layout.h"
@@ -51,11 +50,12 @@
 #include "file.h"
 #include "vnode_cache.h"
 #include "node.h"
-#include "query.h"
 #include "segment.h"
 #include "mkfs.h"
 #include "mount.h"
 #include "fsck.h"
+#include "admin.h"
+#include "query.h"
 // clang-format on
 
 namespace f2fs {
diff --git a/f2fs_internal.h b/f2fs_internal.h
index 4aaeb7a..52c9824 100644
--- a/f2fs_internal.h
+++ b/f2fs_internal.h
@@ -210,20 +210,11 @@
   kNrCountType,
 };
 
-// FS_LOCK nesting subclasses for the lock validator:
-//
 // The locking order between these classes is
-// LockType::kRename -> LockType::kDentryOps -> LockType::kDataWrtie -> LockType::kDataNew
-//    -> LockType::kDataTrunc -> LockType::kNodeWrite -> LockType::kNodeNew -> LockType::kNodeTrunc
+// LockType::FileOp -> LockType::kNodeOp
 enum class LockType {
-  kRename = 0,  // for renaming operations
-  kDentryOps,   // for directory operations
-  kDataWrtie,   // for data write
-  kDataNew,     // for data allocation
-  kDataTrunc,   // for data truncate
-  kNodeNew,     // for node allocation
-  kNodeTrunc,   // for node truncate
-  kNodeWrite,   // for node write
+  kFileOp,  // for file op
+  kNodeOp,  // for node op
   kNrLockType,
 };
 
@@ -267,7 +258,6 @@
   fbl::RefPtr<VnodeF2fs> meta_vnode;
   fbl::Mutex cp_mutex;                                               // for checkpoint procedure
   fs::SharedMutex fs_lock[static_cast<int>(LockType::kNrLockType)];  // for blocking FS operations
-  fbl::Mutex write_inode;                                            // mutex for write inode
   fbl::Mutex writepages;                                             // mutex for writepages()
   int por_doing = 0;                                                 // recovery is doing or not
 
@@ -352,8 +342,6 @@
 
 static inline void mutex_lock_op(SbInfo *sbi, LockType t)
     TA_ACQ(&sbi->fs_lock[static_cast<int>(t)]) {
-  // TODO: Too many locks.
-  // It seems that two locks (node/io) are enough to sync between cp and io path.
   sbi->fs_lock[static_cast<int>(t)].lock();
 }
 
diff --git a/namei.cc b/namei.cc
index 4be5915..8b0c28e 100644
--- a/namei.cc
+++ b/namei.cc
@@ -15,7 +15,7 @@
   VnodeF2fs *vnode = nullptr;
 
   do {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeNew)]);
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
     if (!Vfs()->Nodemgr().AllocNid(&ino)) {
       Iput(vnode);
       return ZX_ERR_NO_SPACE;
@@ -109,12 +109,15 @@
     SetColdFile(name.data(), vnode);
 
   vnode->SetFlag(InodeInfoFlag::kIncLink);
-  if (zx_status_t err = AddLink(name, vnode); err != ZX_OK) {
-    vnode->ClearNlink();
-    vnode->UnlockNewInode();
-    Iput(vnode);
-    Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
-    return err;
+  {
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+    if (zx_status_t err = AddLink(name, vnode); err != ZX_OK) {
+      vnode->ClearNlink();
+      vnode->UnlockNewInode();
+      Iput(vnode);
+      Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
+      return err;
+    }
   }
 
   Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
@@ -152,12 +155,15 @@
 #if 0  // porting needed
   // AtomicInc(&inode->i_count);
 #endif
-
-  target->SetFlag(InodeInfoFlag::kIncLink);
-  if (zx_status_t err = AddLink(name, target); err != ZX_OK) {
-    target->ClearFlag(InodeInfoFlag::kIncLink);
-    Iput(target);
-    return err;
+  {
+    SbInfo &sbi = Vfs()->GetSbInfo();
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+    target->SetFlag(InodeInfoFlag::kIncLink);
+    if (zx_status_t err = AddLink(name, target); err != ZX_OK) {
+      target->ClearFlag(InodeInfoFlag::kIncLink);
+      Iput(target);
+      return err;
+    }
   }
 
 #if 0  // porting needed
@@ -221,16 +227,20 @@
     return ZX_ERR_NOT_FOUND;
   }
 
-  if (zx_status_t err = Vfs()->CheckOrphanSpace(); err != ZX_OK) {
+  {
+    SbInfo &sbi = Vfs()->GetSbInfo();
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+    if (zx_status_t err = Vfs()->CheckOrphanSpace(); err != ZX_OK) {
 #if 0  // porting needed
     // if (!f2fs_has_inline_dentry(dir))
     //   kunmap(page);
 #endif
-    F2fsPutPage(page, 0);
-    return err;
-  }
+      F2fsPutPage(page, 0);
+      return err;
+    }
 
-  DeleteEntry(de, page, vnode);
+    DeleteEntry(de, page, vnode);
+  }
 
   Vfs()->Segmgr().BalanceFs();
 
@@ -289,15 +299,18 @@
 #endif
 
   vnode->SetFlag(InodeInfoFlag::kIncLink);
-  if (zx_status_t err = AddLink(name, vnode); err != ZX_OK) {
-    vnode->ClearFlag(InodeInfoFlag::kIncLink);
-    vnode->ClearNlink();
-    vnode->UnlockNewInode();
-    Iput(vnode);
-    Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
-    return err;
+  {
+    SbInfo &sbi = Vfs()->GetSbInfo();
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+    if (zx_status_t err = AddLink(name, vnode); err != ZX_OK) {
+      vnode->ClearFlag(InodeInfoFlag::kIncLink);
+      vnode->ClearNlink();
+      vnode->UnlockNewInode();
+      Iput(vnode);
+      Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
+      return err;
+    }
   }
-
   Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
 
 #if 0  // porting needed
@@ -434,7 +447,7 @@
   }
 
   do {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kRename)]);
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
 
     new_entry = new_dir->FindEntry(newname, &new_page);
     if (new_entry) {
diff --git a/node.cc b/node.cc
index 18f7e3c..19ed1c9 100644
--- a/node.cc
+++ b/node.cc
@@ -732,7 +732,6 @@
 
 // Caller should call f2fs_put_dnode(dn).
 zx_status_t NodeMgr::GetDnodeOfData(DnodeOfData *dn, pgoff_t index, int ro) {
-  SbInfo &sbi = fs_->GetSbInfo();
   Page *npage[4];
   Page *parent;
   int offset[4];
@@ -763,8 +762,6 @@
     bool done = false;
 
     if (!nids[i] && !ro) {
-      fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeNew)]);
-
       /* alloc new node */
       if (!AllocNid(&(nids[i]))) {
         err = ZX_ERR_NO_SPACE;
@@ -1103,13 +1100,11 @@
 }
 
 zx_status_t NodeMgr::RemoveInodePage(VnodeF2fs *vnode) {
-  SbInfo &sbi = fs_->GetSbInfo();
   Page *page = nullptr;
   nid_t ino = vnode->Ino();
   DnodeOfData dn;
   zx_status_t err = 0;
 
-  fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeTrunc)]);
   err = GetNodePage(ino, &page);
   if (err) {
     return err;
@@ -1147,18 +1142,15 @@
 }
 
 zx_status_t NodeMgr::NewInodePage(Dir *parent, VnodeF2fs *child) {
-  SbInfo &sbi = fs_->GetSbInfo();
   Page *page = nullptr;
   DnodeOfData dn;
   zx_status_t err = 0;
 
   /* allocate inode page for new inode */
   SetNewDnode(&dn, child, nullptr, nullptr, child->Ino());
-  {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeNew)]);
-    err = NewNodePage(&dn, 0, &page);
-    parent->InitDentInode(child, page);
-  }
+  err = NewNodePage(&dn, 0, &page);
+  parent->InitDentInode(child, page);
+
   if (err)
     return err;
   F2fsPutPage(page, 1);
@@ -1440,8 +1432,6 @@
 #endif
   WaitOnPageWriteback(page);
 
-  fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeWrite)]);
-
   /* get old block addr of this node page */
   nid = NidOfNode(page);
   nofs = OfsOfNode(page);
@@ -1454,12 +1444,15 @@
     return ZX_OK;
   }
 
-  SetPageWriteback(page);
+  {
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeOp)]);
+    SetPageWriteback(page);
 
-  /* insert node offset */
-  fs_->Segmgr().WriteNodePage(page, nid, ni.blk_addr, &new_addr);
-  SetNodeAddr(&ni, new_addr);
-  DecPageCount(&sbi, CountType::kDirtyNodes);
+    /* insert node offset */
+    fs_->Segmgr().WriteNodePage(page, nid, ni.blk_addr, &new_addr);
+    SetNodeAddr(&ni, new_addr);
+    DecPageCount(&sbi, CountType::kDirtyNodes);
+  }
 
   // TODO: IMPL
   // unlock_page(page);
diff --git a/node.h b/node.h
index 9341581..cfc2b18 100644
--- a/node.h
+++ b/node.h
@@ -95,6 +95,7 @@
   zx_status_t ReadNodePage(Page *page, nid_t nid, int type);
   zx_status_t GetNodePage(pgoff_t nid, Page **out);
 
+  // Caller should acquire LockType:kFileOp when |ro| = 0.
   zx_status_t GetDnodeOfData(DnodeOfData *dn, pgoff_t index, int ro);
 
   void FillNodeFooter(Page *page, nid_t nid, nid_t ino, uint32_t ofs, bool reset);
@@ -127,8 +128,11 @@
   void AllocNidDone(nid_t nid);
   zx_status_t TruncateInodeBlocks(VnodeF2fs *vnode, pgoff_t from);
 
+  // Caller should acquire LockType:kFileOp.
   zx_status_t RemoveInodePage(VnodeF2fs *vnode);
+  // Caller should acquire LockType:kFileOp.
   zx_status_t NewInodePage(Dir *parent, VnodeF2fs *child);
+
   int IsCheckpointedNode(nid_t nid);
 
   void ClearColdData(Page *page);
diff --git a/vnode.cc b/vnode.cc
index 0e461ac..8d5b6f3 100644
--- a/vnode.cc
+++ b/vnode.cc
@@ -304,29 +304,28 @@
 #endif
 }
 
-int VnodeF2fs::WriteInode(WritebackControl *wbc) TA_NO_THREAD_SAFETY_ANALYSIS {
+int VnodeF2fs::WriteInode(WritebackControl *wbc) {
   SbInfo &sbi = Vfs()->GetSbInfo();
   Page *node_page = nullptr;
   zx_status_t ret = ZX_OK;
 
-  if (ino_ == NodeIno(&sbi) || ino_ == MetaIno(&sbi))
+  if (ino_ == NodeIno(&sbi) || ino_ == MetaIno(&sbi)) {
     return ret;
+  }
 
-  if (ret = Vfs()->Nodemgr().GetNodePage(ino_, &node_page); ret != ZX_OK)
-    return ret;
+  if (!IsDirty()) {
+    return ZX_OK;
+  }
 
-  if (PageDirty(node_page) || IsDirty()) {
-    UpdateInode(node_page);
-    F2fsPutPage(node_page, 1);
-  } else {
-    F2fsPutPage(node_page, 1);
-    fbl::AutoLock lock(&sbi.write_inode);
+  {
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kNodeOp)]);
     if (ret = Vfs()->Nodemgr().GetNodePage(ino_, &node_page); ret != ZX_OK)
       return ret;
     UpdateInode(node_page);
-    F2fsPutPage(node_page, 1);
   }
 
+  F2fsPutPage(node_page, 1);
+
   return ZX_OK;
 }
 
@@ -414,7 +413,7 @@
   pgoff_t free_from = static_cast<pgoff_t>((from + blocksize - 1) >> (sbi.log_blocksize));
 
   {
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDataTrunc)]);
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
     std::lock_guard write_lock(io_lock_);
 
     do {
@@ -454,9 +453,7 @@
 
   for (index = pg_start; index < pg_end; index++) {
     DnodeOfData dn;
-    SbInfo &sbi = Vfs()->GetSbInfo();
 
-    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kDataTrunc)]);
     SetNewDnode(&dn, this, NULL, NULL, 0);
     if (zx_status_t err = Vfs()->Nodemgr().GetDnodeOfData(&dn, index, kRdOnlyNode); err != ZX_OK) {
       if (err == ZX_ERR_NOT_FOUND)
@@ -504,7 +501,10 @@
   if (HasBlocks())
     TruncateToSize();
 
-  Vfs()->Nodemgr().RemoveInodePage(this);
+  {
+    fs::SharedLock rlock(sbi.fs_lock[static_cast<int>(LockType::kFileOp)]);
+    Vfs()->Nodemgr().RemoveInodePage(this);
+  }
   Vfs()->EvictVnode(this);
   //  no_delete:
   //  ClearInode(this);