[f2fs] Correct error handling in Write() and Truncate()
It adds handling for corner cases related to no space and max size.
Also, since f2fs needs at least 100MiB device size,
device_block_count in fs_test is reconfigured.
Test: fx test fs-tests large-fs-tests
Prerequisite:
FUCHSIA_DIR$ patch -p1 < \
third_party/f2fs/patches/0004-f2fs-Configure-deivce-size-as-minimum-size-for-f2fs.patch
Change-Id: Id1653554fe2e64becb270532d48b49dfec156257
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/539562
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/data.cc b/data.cc
index 7c70972..bd36b40 100644
--- a/data.cc
+++ b/data.cc
@@ -77,18 +77,18 @@
#endif
}
-int VnodeF2fs::ReserveNewBlock(DnodeOfData *dn) {
+zx_status_t VnodeF2fs::ReserveNewBlock(DnodeOfData *dn) {
SbInfo &sbi = fs_->GetSbInfo();
if (IsInodeFlagSet(&dn->vnode->fi_, InodeInfoFlag::kNoAlloc))
- return -EPERM;
+ return ZX_ERR_ACCESS_DENIED;
if (!IncValidBlockCount(&sbi, dn->vnode, 1))
- return -ENOSPC;
+ return ZX_ERR_NO_SPACE;
SetDataBlkaddr(dn, kNewAddr);
dn->data_blkaddr = kNewAddr;
fs_->Nodemgr().SyncInodePage(dn);
- return 0;
+ return ZX_OK;
}
#if 0 // porting needed
@@ -295,9 +295,9 @@
return err;
if (dn.data_blkaddr == kNullAddr) {
- if (ReserveNewBlock(&dn)) {
+ if (zx_status_t ret = ReserveNewBlock(&dn); ret != ZX_OK) {
F2fsPutDnode(&dn);
- return ZX_ERR_NO_SPACE;
+ return ret;
}
}
F2fsPutDnode(&dn);
diff --git a/file.cc b/file.cc
index 5817b38..98029f5 100644
--- a/file.cc
+++ b/file.cc
@@ -375,33 +375,65 @@
uint64_t blk_end = (offset + len) / kBlockSize;
size_t off_in_block = offset % kBlockSize;
size_t off_in_buf = 0;
- Page *data_page = nullptr;
void *data_buf = nullptr;
- uint64_t n;
size_t left = len;
- for (n = blk_start; n <= blk_end; n++) {
+ if (len == 0)
+ return ZX_OK;
+
+ if (offset + len > static_cast<size_t>(Vfs()->MaxFileSize(Vfs()->RawSb().log_blocksize)))
+ return ZX_ERR_INVALID_ARGS;
+
+ std::vector<Page *> data_pages(blk_end - blk_start + 1, nullptr);
+
+ for (uint64_t n = blk_start; n <= blk_end; n++) {
+ uint64_t index = n - blk_start;
size_t cur_len = std::min(static_cast<size_t>(kBlockSize - off_in_block), left);
- if (zx_status_t ret = WriteBegin(n * kBlockSize + off_in_block, cur_len, &data_page);
+
+ ZX_ASSERT(index < data_pages.size());
+
+ if (zx_status_t ret = WriteBegin(n * kBlockSize + off_in_block, cur_len, &data_pages[index]);
ret != ZX_OK) {
- if (off_in_buf > 0) {
- clock_gettime(CLOCK_REALTIME, &i_mtime_);
- i_ctime_ = i_mtime_;
-
- MarkInodeDirty(this);
+ // WriteBegin() tries to prepare in-memory buffers and direct node blocks for storing |data|
+ // If it succeeds, data_pages[index] has a page of valid virtual memory.
+ // If it fails with any reasons such as no space/memory,
+ // every data_pages[less than index] is released, and DoWrite() returns with the err code.
+ for (uint64_t m = 0; m < index; m++) {
+ if (data_pages[m] != nullptr)
+ F2fsPutPage(data_pages[m], 1);
}
- *out_actual = off_in_buf;
+ *out_actual = 0;
return ret;
}
+ off_in_block = 0;
+ off_in_buf += cur_len;
+ left -= cur_len;
+
+ if (left == 0)
+ break;
+ }
+
+ off_in_block = offset % kBlockSize;
+ off_in_buf = 0;
+ left = len;
+
+ for (uint64_t n = blk_start; n <= blk_end; n++) {
+ uint64_t index = n - blk_start;
+ size_t cur_len = std::min(static_cast<size_t>(kBlockSize - off_in_block), left);
+
+ ZX_ASSERT(index < data_pages.size());
+
+ Page *data_page = data_pages[index];
+
data_buf = PageAddress(data_page);
memcpy(static_cast<char *>(data_buf) + off_in_block,
static_cast<const char *>(data) + off_in_buf, cur_len);
+ off_in_block = 0;
off_in_buf += cur_len;
left -= cur_len;
- off_in_block = 0;
i_size_ = std::max(static_cast<size_t>(i_size_), offset + off_in_buf);
#if 0 // porting needed
@@ -410,7 +442,7 @@
FlushDirtyDataPage(Vfs(), data_page);
#endif
F2fsPutPage(data_page, 1);
- data_page = nullptr;
+ data_pages[index] = nullptr;
if (left == 0)
break;
@@ -449,9 +481,7 @@
if (len > static_cast<size_t>(Vfs()->MaxFileSize(Vfs()->RawSb().log_blocksize)))
return ZX_ERR_INVALID_ARGS;
- i_size_ = len;
-
- return DoTruncate();
+ return DoTruncate(len);
}
} // namespace f2fs
diff --git a/node.cc b/node.cc
index 3553fe6..1d416d3 100644
--- a/node.cc
+++ b/node.cc
@@ -674,7 +674,7 @@
* The maximum depth is four.
* Offset[0] will have raw inode offset.
*/
-int NodeMgr::GetNodePath(long block, int offset[4], uint32_t noffset[4]) {
+zx::status<int> NodeMgr::GetNodePath(long block, int offset[4], uint32_t noffset[4]) {
const long direct_index = kAddrsPerInode;
const long direct_blks = kAddrsPerBlock;
const long dptrs_per_blk = kNidsPerBlock;
@@ -738,10 +738,10 @@
level = 3;
goto got;
} else {
- ZX_ASSERT(0);
+ zx::error(ZX_ERR_NOT_FOUND);
}
got:
- return level;
+ return zx::ok(level);
}
/*
@@ -757,7 +757,11 @@
int level, i;
zx_status_t err = 0;
- level = GetNodePath(index, offset, noffset);
+ auto node_path = GetNodePath(index, offset, noffset);
+ if (node_path.is_error())
+ return node_path.error_value();
+
+ level = *node_path;
nids[0] = dn->vnode->Ino();
npage[0] = nullptr;
@@ -1028,7 +1032,11 @@
Page *page = nullptr;
zx_status_t err = 0;
- level = GetNodePath(from, offset, reinterpret_cast<uint32_t *>(noffset));
+ auto node_path = GetNodePath(from, offset, reinterpret_cast<uint32_t *>(noffset));
+ if (node_path.is_error())
+ return node_path.error_value();
+
+ level = *node_path;
err = GetNodePage(vnode->Ino(), &page);
if (err)
diff --git a/node.h b/node.h
index 83b6ce3..bb842f4 100644
--- a/node.h
+++ b/node.h
@@ -177,7 +177,7 @@
void SetNodeAddr(NodeInfo *ni, block_t new_blkaddr);
int TryToFreeNats(int nr_shrink);
- int GetNodePath(long block, int offset[4], uint32_t noffset[4]);
+ zx::status<int> GetNodePath(long block, int offset[4], uint32_t noffset[4]);
void TruncateNode(DnodeOfData *dn);
zx_status_t TruncateDnode(DnodeOfData *dn);
zx_status_t TruncateNodes(DnodeOfData *dn, uint32_t nofs, int ofs, int depth);
diff --git a/patches/0004-f2fs-Configure-deivce-size-as-minimum-size-for-f2fs.patch b/patches/0004-f2fs-Configure-deivce-size-as-minimum-size-for-f2fs.patch
new file mode 100644
index 0000000..ed8ece6
--- /dev/null
+++ b/patches/0004-f2fs-Configure-deivce-size-as-minimum-size-for-f2fs.patch
@@ -0,0 +1,68 @@
+From f748670fa7d6a1c8af5f53f33a5e4ab091b94bbe Mon Sep 17 00:00:00 2001
+From: Dongjin Kim <dongjin_.kim@samsung.com>
+Date: Mon, 7 Jun 2021 15:38:25 +0900
+Subject: [PATCH] [f2fs] Configure deivce size as minimum size for f2fs
+
+Since f2fs needs device size more than 100MiB,
+ configure device_block_count in rw-tests to provide the minimum size.
+
+Change-Id: Ia4a949fa9c505fa7142eaa8170b7e7487c592e1e
+---
+ src/storage/fs_test/directory_large.cc | 6 ++++++
+ src/storage/fs_test/rw.cc | 4 ++++
+ src/storage/fs_test/truncate_large.cc | 7 ++++++-
+ 3 files changed, 16 insertions(+), 1 deletion(-)
+
+diff --git a/src/storage/fs_test/directory_large.cc b/src/storage/fs_test/directory_large.cc
+index c397bbdcfc2..80689686d4a 100644
+--- a/src/storage/fs_test/directory_large.cc
++++ b/src/storage/fs_test/directory_large.cc
+@@ -59,6 +59,12 @@ INSTANTIATE_TEST_SUITE_P(
+ // memory is the limiting factor).
+ if (options.filesystem->GetTraits().in_memory)
+ return std::nullopt;
++
++ if (options.filesystem->GetTraits().name == "f2fs") {
++ options.device_block_count = 204800;
++ return options;
++ }
++
+ if (!options.filesystem->GetTraits().has_directory_size_limit) {
+ // Fatfs is slow and, other than the root directory on FAT12/16, is limited by the size
+ // of the ram-disk rather than a directory size limit, so use a small ram-disk to keep
+diff --git a/src/storage/fs_test/rw.cc b/src/storage/fs_test/rw.cc
+index c5fa0256b8b..380b50f1f10 100644
+--- a/src/storage/fs_test/rw.cc
++++ b/src/storage/fs_test/rw.cc
+@@ -209,6 +209,10 @@ INSTANTIATE_TEST_SUITE_P(
+ return std::nullopt;
+ }
+ // Run on a smaller ram-disk to keep run-time reasonable.
++ if (options.filesystem->GetTraits().name == "f2fs") {
++ options.device_block_count = 204800;
++ return options;
++ }
+ options.device_block_count = 8192;
+ options.fvm_slice_size = 32768;
+ return options;
+diff --git a/src/storage/fs_test/truncate_large.cc b/src/storage/fs_test/truncate_large.cc
+index 3d88727cf82..e07f2eb17d7 100644
+--- a/src/storage/fs_test/truncate_large.cc
++++ b/src/storage/fs_test/truncate_large.cc
+@@ -24,7 +24,12 @@ std::vector<LargeTruncateTestParamType> GetTestCombinations(
+ // Fatfs is slow, so skip larger buffer sizes.
+ continue;
+ }
+- options.device_block_count = 3 * (1LLU << 16);
++
++ if (options.filesystem->GetTraits().name == "f2fs") {
++ options.device_block_count = 204800;
++ } else {
++ options.device_block_count = 3 * (1LLU << 16);
++ }
+ options.device_block_size = 1LLU << 9;
+ options.fvm_slice_size = 1LLU << 23;
+ test_combinations.push_back(std::make_tuple(options, variation));
+--
+2.25.1
+
diff --git a/recovery.cc b/recovery.cc
index 24e37e2..b459b6c 100644
--- a/recovery.cc
+++ b/recovery.cc
@@ -283,9 +283,8 @@
if (src != dest && dest != kNewAddr && dest != kNullAddr) {
if (src == kNullAddr) {
- int err = vnode->ReserveNewBlock(&dn);
- /* We should not get -ENOSPC */
- ZX_ASSERT(!err);
+ zx_status_t err = vnode->ReserveNewBlock(&dn);
+ ZX_ASSERT(err == ZX_OK);
}
/* Check the previous node page having this index */
diff --git a/vnode.cc b/vnode.cc
index b2ba304..88af9db 100644
--- a/vnode.cc
+++ b/vnode.cc
@@ -373,10 +373,11 @@
return ZX_OK;
}
-zx_status_t VnodeF2fs::DoTruncate() {
+zx_status_t VnodeF2fs::DoTruncate(size_t len) {
zx_status_t ret;
- if (ret = TruncateBlocks(i_size_); ret == ZX_OK) {
+ if (ret = TruncateBlocks(len); ret == ZX_OK) {
+ i_size_ = len;
clock_gettime(CLOCK_REALTIME, &i_mtime_);
i_ctime_ = i_mtime_;
MarkInodeDirty(this);
@@ -446,11 +447,13 @@
SbInfo &sbi = Vfs()->GetSbInfo();
unsigned int blocksize = sbi.blocksize;
DnodeOfData dn;
- pgoff_t free_from;
int count = 0;
zx_status_t err;
- free_from = static_cast<pgoff_t>((from + blocksize - 1) >> (sbi.log_blocksize));
+ if (from > i_size_)
+ return ZX_OK;
+
+ pgoff_t free_from = static_cast<pgoff_t>((from + blocksize - 1) >> (sbi.log_blocksize));
mutex_lock_op(&sbi, LockType::kDataTrunc);
diff --git a/vnode.h b/vnode.h
index d1df008..56fd0b4 100644
--- a/vnode.h
+++ b/vnode.h
@@ -64,7 +64,7 @@
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);
- zx_status_t DoTruncate();
+ zx_status_t DoTruncate(size_t len);
int TruncateDataBlocksRange(DnodeOfData *dn, int count);
void TruncateDataBlocks(DnodeOfData *dn);
void TruncatePartialDataPage(uint64_t from);
@@ -76,7 +76,7 @@
#endif
void SetDataBlkaddr(DnodeOfData *dn, block_t new_addr);
- int ReserveNewBlock(DnodeOfData *dn);
+ zx_status_t ReserveNewBlock(DnodeOfData *dn);
#if 0 // porting needed
// static int CheckExtentCache(inode *inode, pgoff_t pgofs,