blob: 807373c69d39a0ec6d7674e3532009a7345a846d [file] [log] [blame]
// 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 "src/storage/f2fs/f2fs.h"
namespace f2fs {
zx_status_t F2fs::GrabMetaPage(pgoff_t index, LockedPage *out) {
if (zx_status_t ret = GetMetaVnode().GrabCachePage(index, out); ret != ZX_OK) {
ZX_ASSERT_MSG(false, "GrabMetaPage() fails [addr: 0x%lx, ret: %d]\n", index, ret);
return ret;
}
// We wait writeback only inside GrabMetaPage()
(*out)->WaitOnWriteback();
(*out)->SetUptodate();
return ZX_OK;
}
zx_status_t F2fs::GetMetaPage(pgoff_t index, LockedPage *out) {
LockedPage page;
if (zx_status_t ret = GetMetaVnode().GrabCachePage(index, &page); ret != ZX_OK) {
ZX_ASSERT_MSG(false, "GetMetaPage() fails [addr: 0x%lx, ret: %d]\n", index, ret);
return ret;
}
if (auto status =
MakeReadOperation(page, safemath::checked_cast<block_t>(index), PageType::kMeta);
status.is_error()) {
return status.status_value();
}
#if 0 // porting needed
// mark_page_accessed(page);
#endif
*out = std::move(page);
return ZX_OK;
}
pgoff_t F2fs::FlushDirtyMetaPages(bool is_commit) {
if (superblock_info_->GetPageCount(CountType::kDirtyMeta) == 0) {
return 0;
}
WritebackOperation operation;
if (is_commit) {
operation.bSync = true;
operation.page_cb = [](fbl::RefPtr<Page> page, bool is_last_page) {
if (is_last_page) {
page->SetCommit();
}
return ZX_OK;
};
sync_completion_t completion;
ScheduleWriter(&completion);
ZX_ASSERT_MSG(sync_completion_wait(&completion, zx::sec(kWriteTimeOut).get()) == ZX_OK,
"FlushDirtyMetaPages() timeout");
}
return GetMetaVnode().Writeback(operation);
}
zx_status_t F2fs::CheckOrphanSpace() {
/*
* considering 512 blocks in a segment 5 blocks are needed for cp
* and log segment summaries. Remaining blocks are used to keep
* orphan entries with the limitation one reserved segment
* for cp pack we can have max 1020*507 orphan entries
*/
size_t max_orphans = (superblock_info_->GetBlocksPerSeg() - 5) * kOrphansPerBlock;
if (GetVnodeSetSize(VnodeSet::kOrphan) >= max_orphans) {
inspect_tree_->OnOutOfSpace();
return ZX_ERR_NO_SPACE;
}
return ZX_OK;
}
void F2fs::PurgeOrphanInode(nid_t ino) {
fbl::RefPtr<VnodeF2fs> vnode;
ZX_ASSERT(VnodeF2fs::Vget(this, ino, &vnode) == ZX_OK);
vnode->ClearNlink();
// truncate all the data and nodes in VnodeF2fs::Recycle()
}
zx_status_t F2fs::PurgeOrphanInodes() {
if (!(superblock_info_->TestCpFlags(CpFlag::kCpOrphanPresentFlag))) {
return ZX_OK;
}
SetOnRecovery();
block_t start_blk = superblock_info_->StartCpAddr() + superblock_info_->GetNumCpPayload() + 1;
block_t orphan_blkaddr = superblock_info_->StartSumAddr() - 1;
for (block_t i = 0; i < orphan_blkaddr; ++i) {
LockedPage page;
if (zx_status_t ret = GetMetaPage(start_blk + i, &page); ret != ZX_OK) {
return ret;
}
OrphanBlock *orphan_blk;
orphan_blk = page->GetAddress<OrphanBlock>();
uint32_t entry_count = LeToCpu(orphan_blk->entry_count);
// TODO: Need to set NeedChkp flag to repair the fs when fsck repair is available.
// For now, we trigger assertion.
ZX_ASSERT(entry_count <= kOrphansPerBlock);
for (block_t j = 0; j < entry_count; ++j) {
nid_t ino = LeToCpu(orphan_blk->ino[j]);
PurgeOrphanInode(ino);
}
}
// clear Orphan Flag
superblock_info_->ClearCpFlags(CpFlag::kCpOrphanPresentFlag);
ClearOnRecovery();
return ZX_OK;
}
void F2fs::WriteOrphanInodes(block_t start_blk) {
OrphanBlock *orphan_blk = nullptr;
LockedPage page;
uint32_t nentries = 0;
uint16_t index = 1;
uint16_t orphan_blocks;
orphan_blocks = static_cast<uint16_t>(
(GetVnodeSetSize(VnodeSet::kOrphan) + (kOrphansPerBlock - 1)) / kOrphansPerBlock);
ForAllVnodeSet(VnodeSet::kOrphan, [&](nid_t ino) {
if (nentries == kOrphansPerBlock) {
// an orphan block is full of 1020 entries,
// then we need to flush current orphan blocks
// and bring another one in memory
orphan_blk->blk_addr = CpuToLe(index);
orphan_blk->blk_count = CpuToLe(orphan_blocks);
orphan_blk->entry_count = CpuToLe(nentries);
page.SetDirty();
page.reset();
++index;
++start_blk;
nentries = 0;
}
if (!page) {
GrabMetaPage(start_blk, &page);
orphan_blk = page->GetAddress<OrphanBlock>();
memset(orphan_blk, 0, sizeof(*orphan_blk));
page.SetDirty();
}
orphan_blk->ino[nentries++] = CpuToLe(ino);
});
if (page) {
orphan_blk->blk_addr = CpuToLe(index);
orphan_blk->blk_count = CpuToLe(orphan_blocks);
orphan_blk->entry_count = CpuToLe(nentries);
page.SetDirty();
}
}
zx_status_t F2fs::ValidateCheckpoint(block_t cp_addr, uint64_t *version, LockedPage *out) {
LockedPage cp_page_1, cp_page_2;
uint64_t blk_size = superblock_info_->GetBlocksize();
Checkpoint *cp_block;
uint64_t cur_version = 0, pre_version = 0;
uint32_t crc = 0;
uint32_t crc_offset;
// Read the 1st cp block in this CP pack
if (zx_status_t ret = GetMetaPage(cp_addr, &cp_page_1); ret != ZX_OK) {
return ret;
}
// get the version number
cp_block = cp_page_1->GetAddress<Checkpoint>();
crc_offset = LeToCpu(cp_block->checksum_offset);
if (crc_offset >= blk_size) {
return ZX_ERR_BAD_STATE;
}
crc = *reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(cp_block) + crc_offset);
if (!F2fsCrcValid(crc, cp_block, crc_offset)) {
return ZX_ERR_BAD_STATE;
}
pre_version = LeToCpu(cp_block->checkpoint_ver);
// Read the 2nd cp block in this CP pack
cp_addr += LeToCpu(cp_block->cp_pack_total_block_count) - 1;
if (zx_status_t ret = GetMetaPage(cp_addr, &cp_page_2); ret != ZX_OK) {
return ret;
}
cp_block = cp_page_2->GetAddress<Checkpoint>();
crc_offset = LeToCpu(cp_block->checksum_offset);
if (crc_offset >= blk_size) {
return ZX_ERR_BAD_STATE;
}
crc = *reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(cp_block) + crc_offset);
if (!F2fsCrcValid(crc, cp_block, crc_offset)) {
return ZX_ERR_BAD_STATE;
}
cur_version = LeToCpu(cp_block->checkpoint_ver);
if (cur_version == pre_version) {
*version = cur_version;
*out = std::move(cp_page_1);
return ZX_OK;
}
return ZX_ERR_BAD_STATE;
}
zx_status_t F2fs::GetValidCheckpoint() {
LockedPage cp1, cp2;
fbl::RefPtr<Page> cur_page;
uint64_t cp1_version = 0, cp2_version = 0;
block_t cp_start_blk_no;
/*
* Finding out valid cp block involves read both
* sets( cp pack1 and cp pack 2)
*/
block_t start_addr = LeToCpu(superblock_info_->GetSuperblock().cp_blkaddr);
cp_start_blk_no = start_addr;
ValidateCheckpoint(cp_start_blk_no, &cp1_version, &cp1);
/* The second checkpoint pack should start at the next segment */
cp_start_blk_no += 1 << superblock_info_->GetLogBlocksPerSeg();
ValidateCheckpoint(cp_start_blk_no, &cp2_version, &cp2);
if (cp1 && cp2) {
if (VerAfter(cp2_version, cp1_version)) {
if (superblock_info_->SetCheckpoint(cp2.CopyRefPtr()) != ZX_OK) {
cur_page = cp1.CopyRefPtr();
cp_start_blk_no = start_addr;
}
} else {
if (superblock_info_->SetCheckpoint(cp1.CopyRefPtr()) != ZX_OK) {
cur_page = cp2.CopyRefPtr();
} else {
cp_start_blk_no = start_addr;
}
}
} else if (cp1) {
cur_page = cp1.CopyRefPtr();
cp_start_blk_no = start_addr;
} else if (cp2) {
cur_page = cp2.CopyRefPtr();
} else {
return ZX_ERR_INVALID_ARGS;
}
if (cur_page) {
if (zx_status_t status = superblock_info_->SetCheckpoint(cur_page); status != ZX_OK) {
return status;
}
}
block_t num_payload = superblock_info_->GetNumCpPayload();
if (num_payload) {
size_t blk_size = superblock_info_->GetBlocksize();
superblock_info_->SetExtraSitBitmap(num_payload * blk_size);
for (uint32_t i = 0; i < num_payload; ++i) {
LockedPage cp_page;
if (zx_status_t ret = GetMetaPage(cp_start_blk_no + 1 + i, &cp_page); ret != ZX_OK) {
return ret;
}
CloneBits(superblock_info_->GetExtraSitBitmap(), cp_page->GetAddress(),
GetBitSize(i * blk_size), GetBitSize(blk_size));
}
}
return ZX_OK;
}
pgoff_t F2fs::FlushDirtyDataPages(WritebackOperation &operation, bool wait_writer) {
pgoff_t total_nwritten = 0;
GetVCache().ForDirtyVnodesIf(
[&](fbl::RefPtr<VnodeF2fs> &vnode) {
if (!vnode->IsValid()) {
ZX_ASSERT(vnode->ClearDirty());
} else if (vnode->GetDirtyPageCount()) {
auto nwritten = vnode->Writeback(operation);
total_nwritten = safemath::CheckAdd<pgoff_t>(total_nwritten, nwritten).ValueOrDie();
if (nwritten >= operation.to_write) {
return ZX_ERR_STOP;
}
operation.to_write -= nwritten;
}
return ZX_OK;
},
std::move(operation.if_vnode));
if (wait_writer) {
// Wait until writers finish and release the ref of dirty vnodes.
sync_completion_t completion;
ScheduleWriter(&completion);
ZX_ASSERT_MSG(sync_completion_wait(&completion, zx::sec(kWriteTimeOut).get()) == ZX_OK,
"FlushDirtyDataPages() timeout");
}
return total_nwritten;
}
zx::result<pgoff_t> F2fs::FlushDirtyNodePages(WritebackOperation &operation) {
if (zx_status_t status = GetVCache().ForDirtyVnodesIf(
[](fbl::RefPtr<VnodeF2fs> &vnode) {
ZX_ASSERT(vnode->IsValid());
ZX_ASSERT(vnode->ClearDirty());
return ZX_OK;
},
[this](fbl::RefPtr<VnodeF2fs> &vnode) {
// Update the node page of every dirty vnode before writeback.
LockedPage node_page;
if (zx_status_t ret = node_manager_->GetNodePage(vnode->GetKey(), &node_page);
ret != ZX_OK) {
return ret;
}
if (vnode->GetDirtyPageCount()) {
return ZX_ERR_NEXT;
}
vnode->UpdateInodePage(node_page);
return ZX_OK;
});
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(GetNodeVnode().Writeback(operation));
}
void F2fs::FlushDirsAndNodes() {
do {
// Write out all dirty dentry pages and remove orphans from dirty list.
WritebackOperation op;
op.if_vnode = [](fbl::RefPtr<VnodeF2fs> &vnode) {
if (vnode->IsDir() || !vnode->IsValid()) {
return ZX_OK;
}
return ZX_ERR_NEXT;
};
FlushDirtyDataPages(op, true);
} while (superblock_info_->GetPageCount(CountType::kDirtyDents));
// POR: we should ensure that there is no dirty node pages
// until finishing nat/sit flush.
do {
WritebackOperation op;
auto ret = FlushDirtyNodePages(op);
ZX_ASSERT_MSG(ret.is_ok(), "Failed to flush node pages (%ul) %s",
superblock_info_->GetPageCount(CountType::kDirtyNodes), ret.status_string());
} while (superblock_info_->GetPageCount(CountType::kDirtyNodes));
}
zx_status_t F2fs::DoCheckpoint(bool is_umount) {
// Flush all the NAT/SIT pages
SuperblockInfo &superblock_info = GetSuperblockInfo();
while (superblock_info.GetPageCount(CountType::kDirtyMeta)) {
FlushDirtyMetaPages(false);
}
ScheduleWriter();
auto &ckpt_block = superblock_info.GetCheckpointBlock();
if (auto last_nid_or = GetNodeManager().GetNextFreeNid(); last_nid_or.is_ok()) {
ckpt_block->next_free_nid = CpuToLe(*last_nid_or);
} else {
ckpt_block->next_free_nid = CpuToLe(GetNodeManager().GetNextScanNid());
}
// modify checkpoint
// version number is already updated
ckpt_block->elapsed_time = CpuToLe(GetSegmentManager().GetMtime());
ckpt_block->valid_block_count = CpuToLe(superblock_info.GetValidBlockCount());
ckpt_block->free_segment_count = CpuToLe(GetSegmentManager().FreeSegments());
for (int i = 0; i < 3; ++i) {
ckpt_block->cur_node_segno[i] =
CpuToLe(GetSegmentManager().CursegSegno(i + static_cast<int>(CursegType::kCursegHotNode)));
ckpt_block->cur_node_blkoff[i] =
CpuToLe(GetSegmentManager().CursegBlkoff(i + static_cast<int>(CursegType::kCursegHotNode)));
ckpt_block->alloc_type[i + static_cast<int>(CursegType::kCursegHotNode)] =
GetSegmentManager().CursegAllocType(i + static_cast<int>(CursegType::kCursegHotNode));
}
for (int i = 0; i < 3; ++i) {
ckpt_block->cur_data_segno[i] =
CpuToLe(GetSegmentManager().CursegSegno(i + static_cast<int>(CursegType::kCursegHotData)));
ckpt_block->cur_data_blkoff[i] =
CpuToLe(GetSegmentManager().CursegBlkoff(i + static_cast<int>(CursegType::kCursegHotData)));
ckpt_block->alloc_type[i + static_cast<int>(CursegType::kCursegHotData)] =
GetSegmentManager().CursegAllocType(i + static_cast<int>(CursegType::kCursegHotData));
}
ckpt_block->valid_node_count = CpuToLe(superblock_info.GetValidNodeCount());
ckpt_block->valid_inode_count = CpuToLe(superblock_info.GetValidInodeCount());
// 2 cp + n data seg summary + orphan inode blocks
uint32_t data_sum_blocks = GetSegmentManager().NpagesForSummaryFlush();
if (data_sum_blocks < 3) {
superblock_info.SetCpFlags(CpFlag::kCpCompactSumFlag);
} else {
superblock_info.ClearCpFlags(CpFlag::kCpCompactSumFlag);
}
block_t num_cp_payload = superblock_info.GetNumCpPayload();
uint32_t orphan_blocks = static_cast<uint32_t>(
(GetVnodeSetSize(VnodeSet::kOrphan) + kOrphansPerBlock - 1) / kOrphansPerBlock);
uint32_t cp_pack_total_block_count = 2 + data_sum_blocks + orphan_blocks + num_cp_payload;
if (is_umount) {
superblock_info.SetCpFlags(CpFlag::kCpUmountFlag);
cp_pack_total_block_count += kNrCursegNodeType;
} else {
superblock_info.ClearCpFlags(CpFlag::kCpUmountFlag);
}
ckpt_block->cp_pack_start_sum = CpuToLe(1 + orphan_blocks + num_cp_payload);
ckpt_block->cp_pack_total_block_count = CpuToLe(cp_pack_total_block_count);
if (GetVnodeSetSize(VnodeSet::kOrphan) > 0) {
superblock_info.SetCpFlags(CpFlag::kCpOrphanPresentFlag);
} else {
superblock_info.ClearCpFlags(CpFlag::kCpOrphanPresentFlag);
}
// update SIT/NAT bitmap
GetSegmentManager().GetSitBitmap(superblock_info.GetSitBitmap());
GetNodeManager().GetNatBitmap(superblock_info.GetNatBitmap());
uint32_t crc32 = CpuToLe(F2fsCrc32(&ckpt_block, LeToCpu(ckpt_block->checksum_offset)));
std::memcpy(ckpt_block.get<uint8_t>() + LeToCpu(ckpt_block->checksum_offset), &crc32,
sizeof(uint32_t));
block_t start_blk = superblock_info.StartCpAddr();
// Prepare Pages for this checkpoint pack
{
LockedPage cp_page;
GrabMetaPage(start_blk++, &cp_page);
cp_page->Write(&ckpt_block, 0, superblock_info.GetBlocksize());
cp_page.SetDirty();
}
size_t offset = 0;
for (size_t i = 0; i < num_cp_payload; ++i) {
LockedPage cp_page;
GrabMetaPage(start_blk++, &cp_page);
memcpy(cp_page->GetAddress(), &superblock_info.GetSitBitmap()[offset], cp_page->Size());
cp_page.SetDirty();
offset += cp_page->Size();
}
if (GetVnodeSetSize(VnodeSet::kOrphan) > 0) {
WriteOrphanInodes(start_blk);
start_blk += orphan_blocks;
}
GetSegmentManager().WriteDataSummaries(start_blk);
start_blk += data_sum_blocks;
if (is_umount) {
GetSegmentManager().WriteNodeSummaries(start_blk);
start_blk += kNrCursegNodeType;
}
// Write out this checkpoint pack.
FlushDirtyMetaPages(false);
// Prepare the commit block.
{
LockedPage cp_page;
GrabMetaPage(start_blk, &cp_page);
cp_page->Write(&ckpt_block, 0, superblock_info.GetBlocksize());
cp_page.SetDirty();
}
// Update the valid block count.
superblock_info.ResetAllocBlockCount();
// Commit.
if (!superblock_info.TestCpFlags(CpFlag::kCpErrorFlag)) {
ZX_ASSERT(superblock_info.GetPageCount(CountType::kDirtyMeta) == 1);
FlushDirtyMetaPages(true);
if (superblock_info_->TestCpFlags(CpFlag::kCpErrorFlag)) {
return ZX_ERR_UNAVAILABLE;
}
GetSegmentManager().ClearPrefreeSegments();
superblock_info.ClearDirty();
meta_vnode_->InvalidatePages();
ClearVnodeSet();
}
return ZX_OK;
}
uint32_t F2fs::GetFreeSectionsForDirtyPages() {
uint32_t pages_per_sec =
safemath::CheckMul<uint32_t>((1 << superblock_info_->GetLogBlocksPerSeg()),
superblock_info_->GetSegsPerSec())
.ValueOrDie();
uint32_t node_secs =
safemath::CheckDiv<uint32_t>(
((superblock_info_->GetPageCount(CountType::kDirtyNodes) + pages_per_sec - 1) >>
superblock_info_->GetLogBlocksPerSeg()),
superblock_info_->GetSegsPerSec())
.ValueOrDie();
uint32_t dent_secs =
safemath::CheckDiv<uint32_t>(
((superblock_info_->GetPageCount(CountType::kDirtyDents) + pages_per_sec - 1) >>
superblock_info_->GetLogBlocksPerSeg()),
superblock_info_->GetSegsPerSec())
.ValueOrDie();
return (node_secs + safemath::CheckMul<uint32_t>(dent_secs, 2)).ValueOrDie();
}
// Release-acquire ordering between the writeback (loader) and others such as checkpoint and gc.
bool F2fs::CanReclaim() const { return !stop_reclaim_flag_.test(std::memory_order_acquire); }
bool F2fs::IsTearDown() const { return teardown_flag_.test(std::memory_order_relaxed); }
void F2fs::SetTearDown() { teardown_flag_.test_and_set(std::memory_order_relaxed); }
// We guarantee that this checkpoint procedure should not fail.
zx_status_t F2fs::WriteCheckpoint(bool blocked, bool is_umount) {
if (superblock_info_->TestCpFlags(CpFlag::kCpErrorFlag)) {
return ZX_ERR_BAD_STATE;
}
// Stop writeback during checkpoint.
FlagAcquireGuard flag(&stop_reclaim_flag_);
if (flag.IsAcquired()) {
ZX_ASSERT(WaitForWriteback().is_ok());
}
ZX_DEBUG_ASSERT(segment_manager_->FreeSections() > GetFreeSectionsForDirtyPages());
FlushDirsAndNodes();
// update checkpoint pack index
// Increase the version number so that
// SIT entries and seg summaries are written at correct place
superblock_info_->UpdateCheckpointVer();
// write cached NAT/SIT entries to NAT/SIT area
if (zx_status_t ret = GetNodeManager().FlushNatEntries(); ret != ZX_OK) {
return ret;
}
if (zx_status_t ret = GetSegmentManager().FlushSitEntries(); ret != ZX_OK) {
return ret;
}
// unlock all the fs_lock[] in do_checkpoint()
if (zx_status_t ret = DoCheckpoint(is_umount); ret != ZX_OK) {
return ret;
}
if (is_umount && !(superblock_info_->TestCpFlags(CpFlag::kCpErrorFlag))) {
ZX_ASSERT(superblock_info_->GetPageCount(CountType::kDirtyDents) == 0);
ZX_ASSERT(superblock_info_->GetPageCount(CountType::kDirtyData) == 0);
ZX_ASSERT(superblock_info_->GetPageCount(CountType::kWriteback) == 0);
ZX_ASSERT(superblock_info_->GetPageCount(CountType::kDirtyMeta) == 0);
ZX_ASSERT(superblock_info_->GetPageCount(CountType::kDirtyNodes) == 0);
}
return ZX_OK;
}
} // namespace f2fs