[f2fs] Support truncate cmd

Add truncate handler to File class

Test: fx test truncate-tests
Change-Id: I80b6d6cc77720adb78a9dd9c554bbf0c4337bf38
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/536506
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/f2fs.h b/f2fs.h
index d3ffa4f..5c391ef 100644
--- a/f2fs.h
+++ b/f2fs.h
@@ -125,8 +125,8 @@
   // dentry *F2fsFhToDentry(fid *fid, int fh_len, int fh_type);
   // dentry *F2fsFhToParent(fid *fid, int fh_len, int fh_type);
   // int ParseOptions(char *options);
-  // loff_t MaxFileSize(unsigned bits);
 #endif
+  loff_t MaxFileSize(unsigned bits);
   int SanityCheckRawSuper();
   int SanityCheckCkpt();
   void InitSbInfo();
diff --git a/file.cc b/file.cc
index 61f5b54..8b1b4d5 100644
--- a/file.cc
+++ b/file.cc
@@ -436,9 +436,15 @@
   }
 
   for (n = blk_start; n <= blk_end; n++) {
+    bool is_empty_page = false;
+
     if (zx_status_t ret = GetLockDataPage(n, &data_page); ret != ZX_OK) {
-      *out_actual = off_in_buf;
-      return ret;
+      if (ret == ZX_ERR_NOT_FOUND) {  // truncated page
+        is_empty_page = true;
+      } else {
+        *out_actual = off_in_buf;
+        return ret;
+      }
     }
 
     size_t cur_len = std::min(static_cast<size_t>(kBlockSize - off_in_block), left);
@@ -447,16 +453,22 @@
         cur_len = std::min(cur_len, static_cast<size_t>(i_size_ % kBlockSize));
     }
 
-    data_buf = PageAddress(data_page);
-    memcpy(static_cast<char *>(data) + off_in_buf, static_cast<char *>(data_buf) + off_in_block,
-           cur_len);
+    if (is_empty_page) {
+      memset(static_cast<char *>(data) + off_in_buf, 0, cur_len);
+    } else {
+      data_buf = PageAddress(data_page);
+      memcpy(static_cast<char *>(data) + off_in_buf, static_cast<char *>(data_buf) + off_in_block,
+             cur_len);
+    }
 
     off_in_buf += cur_len;
     left -= cur_len;
     off_in_block = 0;
 
-    F2fsPutPage(data_page, 1);
-    data_page = nullptr;
+    if (!is_empty_page) {
+      F2fsPutPage(data_page, 1);
+      data_page = nullptr;
+    }
 
     if (left == 0)
       break;
@@ -535,4 +547,16 @@
   return ZX_OK;
 }
 
+zx_status_t File::Truncate(size_t len) {
+  if (len == i_size_)
+    return ZX_OK;
+
+  if (len > static_cast<size_t>(Vfs()->MaxFileSize(Vfs()->RawSb().log_blocksize)))
+    return ZX_ERR_INVALID_ARGS;
+
+  i_size_ = len;
+
+  return DoTruncate();
+}
+
 }  // namespace f2fs
diff --git a/file.h b/file.h
index 29e9946..b99e9c3 100644
--- a/file.h
+++ b/file.h
@@ -29,6 +29,7 @@
 
   zx_status_t Read(void* data, size_t len, size_t off, size_t* out_actual);
   zx_status_t Write(const void* data, size_t len, size_t offset, size_t* out_actual);
+  zx_status_t Truncate(size_t len);
 };
 
 }  // namespace f2fs
diff --git a/super.cc b/super.cc
index e5ec589..642718e 100644
--- a/super.cc
+++ b/super.cc
@@ -257,27 +257,27 @@
   // }
   // return 0;
 // }
-
-// loff_t F2fs::MaxFileSize(unsigned bits) {
-//   loff_t result = kAddrsPerInode;
-//   loff_t leaf_count = kAddrsPerBlock;
-
-//   /* two direct node blocks */
-//   result += (leaf_count * 2);
-
-//   /* two indirect node blocks */
-//   leaf_count *= kNidsPerBlock;
-//   result += (leaf_count * 2);
-
-//   /* one double indirect node block */
-//   leaf_count *= kNidsPerBlock;
-//   result += leaf_count;
-
-//   result <<= bits;
-//   return result;
-// }
 #endif
 
+loff_t F2fs::MaxFileSize(unsigned bits) {
+  loff_t result = kAddrsPerInode;
+  loff_t leaf_count = kAddrsPerBlock;
+
+  /* two direct node blocks */
+  result += (leaf_count * 2);
+
+  /* two indirect node blocks */
+  leaf_count *= kNidsPerBlock;
+  result += (leaf_count * 2);
+
+  /* one double indirect node block */
+  leaf_count *= kNidsPerBlock;
+  result += leaf_count;
+
+  result <<= bits;
+  return result;
+}
+
 int F2fs::SanityCheckRawSuper() {
   unsigned int blocksize;
 
diff --git a/vnode.cc b/vnode.cc
index fb99172..c4cc0c0 100644
--- a/vnode.cc
+++ b/vnode.cc
@@ -350,23 +350,21 @@
   return ZX_OK;
 }
 
-#if 0  // porting needed
-// void VnodeF2fs::F2fsTruncate() {
-//   if (!(S_ISREG(i_mode_) || S_ISDIR(i_mode_) || S_ISLNK(i_mode_)))
-//     return;
+zx_status_t VnodeF2fs::DoTruncate() {
+  zx_status_t ret;
 
-//   if (!TruncateBlocks(i_size)) {
-//     auto cur_time = time(nullptr);
-//     i_mtime.tv_sec = cur_time;
-//     i_mtime.tv_nsec = 0;
-//     i_ctime.tv_sec = cur_time;
-//     i_ctime.tv_nsec = 0;
-//     MarkInodeDirty(this);
-//   }
+  if (ret = TruncateBlocks(i_size_); ret == ZX_OK) {
+    auto cur_time = time(nullptr);
+    i_mtime_.tv_sec = cur_time;
+    i_mtime_.tv_nsec = 0;
+    i_ctime_.tv_sec = cur_time;
+    i_ctime_.tv_nsec = 0;
+    MarkInodeDirty(this);
+  }
 
-//   Vfs()->Segmgr().BalanceFs();
-// }
-#endif
+  Vfs()->Segmgr().BalanceFs();
+  return ret;
+}
 
 int VnodeF2fs::TruncateDataBlocksRange(DnodeOfData *dn, int count) {
   int nr_free = 0, ofs = dn->ofs_in_node;
@@ -402,7 +400,7 @@
 void VnodeF2fs::TruncateDataBlocks(DnodeOfData *dn) { TruncateDataBlocksRange(dn, kAddrsPerBlock); }
 
 void VnodeF2fs::TruncatePartialDataPage(uint64_t from) {
-  unsigned offset = from & (kPageCacheSize - 1);
+  size_t offset = from & (kPageCacheSize - 1);
   Page *page = nullptr;
 
   if (!offset)
@@ -424,51 +422,51 @@
   F2fsPutPage(page, 1);
 }
 
-#if 0  // porting needed
-// int VnodeF2fs::TruncateBlocks(uint64_t from) {
-//   SbInfo &sbi = Vfs()->GetSbInfo();
-//   unsigned int blocksize = sbi.blocksize;
-//   DnodeOfData dn;
-//   pgoff_t free_from;
-//   int count = 0;
-//   int err;
+zx_status_t VnodeF2fs::TruncateBlocks(uint64_t from) {
+  SbInfo &sbi = Vfs()->GetSbInfo();
+  unsigned int blocksize = sbi.blocksize;
+  DnodeOfData dn;
+  pgoff_t free_from;
+  int count = 0;
+  zx_status_t err;
 
-//   free_from = (pgoff_t)((from + blocksize - 1) >> (sbi.log_blocksize));
+  free_from = static_cast<pgoff_t>((from + blocksize - 1) >> (sbi.log_blocksize));
 
-//   mutex_lock_op(&sbi, LockType::kDataTrunc);
+  mutex_lock_op(&sbi, LockType::kDataTrunc);
 
-//   SetNewDnode(&dn, this, NULL, NULL, 0);
-//   err = Vfs()->Nodemgr().GetDnodeOfData(&dn, free_from, kRdOnlyNode);
-//   if (err) {
-//     if (err == ZX_ERR_NOT_FOUND)
-//       goto free_next;
-//     mutex_unlock_op(&sbi, LockType::kDataTrunc);
-//     return err;
-//   }
+  do {
+    SetNewDnode(&dn, this, nullptr, nullptr, 0);
+    err = Vfs()->Nodemgr().GetDnodeOfData(&dn, free_from, kRdOnlyNode);
+    if (err) {
+      if (err == ZX_ERR_NOT_FOUND)
+        break;
+      mutex_unlock_op(&sbi, LockType::kDataTrunc);
+      return err;
+    }
 
-//   if (IsInode(dn.node_page))
-//     count = kAddrsPerInode;
-//   else
-//     count = kAddrsPerBlock;
+    if (IsInode(dn.node_page))
+      count = kAddrsPerInode;
+    else
+      count = kAddrsPerBlock;
 
-//   count -= dn.ofs_in_node;
-//   BUG_ON(count < 0);
-//   if (dn.ofs_in_node || IsInode(dn.node_page)) {
-//     TruncateDataBlocksRange(&dn, count);
-//     free_from += count;
-//   }
+    count -= dn.ofs_in_node;
+    ZX_ASSERT(count >= 0);
+    if (dn.ofs_in_node || IsInode(dn.node_page)) {
+      TruncateDataBlocksRange(&dn, count);
+      free_from += count;
+    }
 
-//   F2fsPutDnode(&dn);
-// free_next:
-//   err = Vfs()->Nodemgr().TruncateInodeBlocks(this, free_from);
-//   mutex_unlock_op(&sbi, LockType::kDataTrunc);
+    F2fsPutDnode(&dn);
+  } while (false);
 
-//   /* lastly zero out the first data page */
-//   TruncatePartialDataPage(from);
+  err = Vfs()->Nodemgr().TruncateInodeBlocks(this, free_from);
+  mutex_unlock_op(&sbi, LockType::kDataTrunc);
 
-//   return err;
-// }
-#endif
+  /* lastly zero out the first data page */
+  TruncatePartialDataPage(from);
+
+  return err;
+}
 
 zx_status_t VnodeF2fs::TruncateHole(pgoff_t pg_start, pgoff_t pg_end) {
   pgoff_t index;
diff --git a/vnode.h b/vnode.h
index 7cd5d09..1077770 100644
--- a/vnode.h
+++ b/vnode.h
@@ -58,15 +58,11 @@
   static zx_status_t Vget(F2fs *fs, uint64_t ino, fbl::RefPtr<VnodeF2fs> *out);
   void UpdateInode(Page *node_page);
   zx_status_t WriteInode(WritebackControl *wbc);
-#if 0  // porting needed
-  // void F2fsTruncate();
-#endif
+  zx_status_t DoTruncate();
   int TruncateDataBlocksRange(DnodeOfData *dn, int count);
   void TruncateDataBlocks(DnodeOfData *dn);
   void TruncatePartialDataPage(uint64_t from);
-#if 0  // porting needed
-  // int TruncateBlocks(uint64_t from);
-#endif
+  zx_status_t TruncateBlocks(uint64_t from);
   zx_status_t TruncateHole(pgoff_t pg_start, pgoff_t pg_end);
 #if 0  // porting needed
   // void F2fsEvictInode();