blob: 9b110c90af1a26c552fd0f1ad25d250fb5451768 [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/bcache.h"
#include "src/storage/f2fs/f2fs.h"
#include "src/storage/f2fs/inspect.h"
#include "src/storage/f2fs/node.h"
#include "src/storage/f2fs/segment.h"
#include "src/storage/f2fs/superblock_info.h"
#include "src/storage/f2fs/vnode.h"
#include "src/storage/f2fs/vnode_cache.h"
#include "src/storage/f2fs/writeback.h"
namespace f2fs {
zx_status_t F2fs::GrabMetaPage(pgoff_t index, LockedPage *out) {
if (zx_status_t ret = GetMetaVnode().GrabLockedPage(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().GrabLockedPage(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 = {.bReclaim = GetMemoryStatus(MemoryStatus::kNeedReclaim)};
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;
};
// Ensure that all pending Pages in |writer_| are written out on storage before the commit.
writer_->Sync();
}
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;
}
zx::result<> F2fs::PurgeOrphanInode(nid_t ino) {
zx::result vnode_or = GetVnode(ino);
if (vnode_or.is_error()) {
return vnode_or.take_error();
}
vnode_or->ClearNlink();
// Here, |*vnode_or| should be deleted to purge its metadata.
return zx::ok();
}
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]);
if (PurgeOrphanInode(ino).is_error()) {
FX_LOGS(WARNING) << "failed to purge an orphan file (ino: " << 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) {
uint64_t checkpoint_version = 0;
constexpr size_t kFirstCheckpointHeaderBlock = 0;
LockedPage header;
for (size_t i = 0; i < kNumCheckpointHeaderBlocks; ++i) {
LockedPage cp_page;
// Read the header page in this checkpoint pack
if (zx_status_t ret = GetMetaPage(cp_addr, &cp_page); ret != ZX_OK) {
return ret;
}
// get the version number
Checkpoint *cp_block = cp_page->GetAddress<Checkpoint>();
zx::result crc_or = superblock_info_->GetCrcFromCheckpointBlock(cp_block);
if (crc_or.is_error()) {
return crc_or.error_value();
}
if (!F2fsCrcValid(*crc_or, cp_block, LeToCpu(cp_block->checksum_offset))) {
return ZX_ERR_BAD_STATE;
}
// The checkpoint header and its copy are located in the first and last blocks on a checkpoint
// pack respectively.
if (i == kFirstCheckpointHeaderBlock) {
checkpoint_version = LeToCpu(cp_block->checkpoint_ver);
header = std::move(cp_page);
// Set |cp_addr| to read a copy of the header page in this checkpoint pack
cp_addr += LeToCpu(cp_block->cp_pack_total_block_count) - 1;
} else if (checkpoint_version != LeToCpu(cp_block->checkpoint_ver)) {
return ZX_ERR_BAD_STATE;
}
}
*version = checkpoint_version;
*out = std::move(header);
return ZX_OK;
}
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);
BlockBuffer<Checkpoint> checkpoint_block;
if (cp1 && cp2) {
if (VerAfter(cp2_version, cp1_version)) {
cp2->Read(checkpoint_block.get(), 0, kBlockSize);
if (superblock_info_->SetCheckpoint(checkpoint_block) != ZX_OK) {
cur_page = cp1.CopyRefPtr();
cp_start_blk_no = start_addr;
}
} else {
cp1->Read(checkpoint_block.get(), 0, kBlockSize);
if (superblock_info_->SetCheckpoint(checkpoint_block) != 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) {
cur_page->Read(checkpoint_block.get(), 0, kBlockSize);
if (zx_status_t status = superblock_info_->SetCheckpoint(checkpoint_block); 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) {
pgoff_t total_nwritten = 0;
GetVCache().ForDirtyVnodesIf(
[&](fbl::RefPtr<VnodeF2fs> &vnode) TA_NO_THREAD_SAFETY_ANALYSIS {
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));
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) {
if (vnode->GetDirtyPageCount()) {
return ZX_ERR_NEXT;
}
// 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;
}
vnode->UpdateInodePage(node_page, true);
return ZX_OK;
});
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(GetNodeVnode().Writeback(operation));
}
void F2fs::FlushDirsAndNodes() {
// Here, we just schedule dirty Pages to be written back on storage.
do {
// Write out all dirty dentry pages and remove orphans from dirty list.
WritebackOperation op = {.bReclaim = GetMemoryStatus(MemoryStatus::kNeedReclaim)};
op.if_vnode = [](fbl::RefPtr<VnodeF2fs> &vnode) {
if (vnode->IsDir() || !vnode->IsValid()) {
return ZX_OK;
}
return ZX_ERR_NEXT;
};
FlushDirtyDataPages(op);
} 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 = {.bReclaim = GetMemoryStatus(MemoryStatus::kNeedReclaim)};
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);
}
GetWriter().ScheduleWriteBlocks();
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);
}
// Make use crc and version for recovery
superblock_info.SetCpFlags(CpFlag::kCpCrcRecoveryFlag);
// 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_->CleanupCache();
ClearVnodeSet();
}
return ZX_OK;
}
uint32_t F2fs::GetFreeSectionsForCheckpoint() {
uint32_t pages_per_sec =
safemath::CheckMul<uint32_t>(kDefaultBlocksPerSegment, superblock_info_->GetSegsPerSec())
.ValueOrDie();
uint32_t node_secs = CheckedDivRoundUp<uint32_t>(
superblock_info_->GetPageCount(CountType::kDirtyNodes), pages_per_sec);
uint32_t dent_secs = CheckedDivRoundUp<uint32_t>(
superblock_info_->GetPageCount(CountType::kDirtyDents), pages_per_sec);
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::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 is_umount) {
std::lock_guard gc_lock(f2fs::GetGlobalLock());
return WriteCheckpointUnsafe(is_umount);
}
zx_status_t F2fs::WriteCheckpointUnsafe(bool is_umount) {
if (superblock_info_->TestCpFlags(CpFlag::kCpErrorFlag)) {
return ZX_ERR_BAD_STATE;
}
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;
}
// Here, we ensure that all writeback pages are written out.
if (GetMemoryStatus(MemoryStatus::kNeedReclaim) || is_umount) {
// Clear clean pages for all vnodes, and remove inactive vnodes that have no pages.
node_vnode_->CleanupCache();
GetVCache().Shrink();
}
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