blob: 096925d89c03db56a69e65421c6a7160b273e65f [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 {
bool F2fs::SpaceForRollForward() {
SuperblockInfo &superblock_info = GetSuperblockInfo();
std::lock_guard stat_lock(superblock_info.GetStatLock());
if (superblock_info.GetLastValidBlockCount() + superblock_info.GetAllocValidBlockCount() >
superblock_info.GetUserBlockCount())
return false;
return true;
}
F2fs::FsyncInodeEntry *F2fs::GetFsyncInode(FsyncInodeList &inode_list, nid_t ino) {
auto inode_entry =
inode_list.find_if([ino](const auto &entry) { return entry.GetVnode().Ino() == ino; });
if (inode_entry == inode_list.end())
return nullptr;
return &(*inode_entry);
}
zx_status_t F2fs::RecoverDentry(NodePage &ipage, VnodeF2fs &vnode) {
Node *raw_node = ipage.GetAddress<Node>();
Inode *raw_inode = &(raw_node->i);
fbl::RefPtr<VnodeF2fs> dir_refptr;
zx_status_t err = ZX_OK;
if (!ipage.IsDentDnode()) {
return ZX_OK;
}
if (err = VnodeF2fs::Vget(this, LeToCpu(raw_inode->i_pino), &dir_refptr); err != ZX_OK) {
return err;
}
auto dir = static_cast<Dir *>(dir_refptr.get());
fbl::RefPtr<Page> page;
auto dir_entry = dir->FindEntry(vnode.GetNameView(), &page);
if (dir_entry == nullptr) {
dir->AddLink(vnode.GetNameView(), &vnode);
} else if (dir_entry && vnode.Ino() != LeToCpu(dir_entry->ino)) {
// Remove old dentry
fbl::RefPtr<VnodeF2fs> old_vnode_refptr;
if (err = VnodeF2fs::Vget(this, dir_entry->ino, &old_vnode_refptr); err != ZX_OK) {
return err;
}
dir->DeleteEntry(dir_entry, page, old_vnode_refptr.get());
ZX_ASSERT(dir->FindEntry(vnode.GetNameView()).status_value() == ZX_ERR_NOT_FOUND);
dir->AddLink(vnode.GetNameView(), &vnode);
}
return err;
}
zx_status_t F2fs::RecoverInode(VnodeF2fs &vnode, NodePage &node_page) {
struct Node *raw_node = node_page.GetAddress<Node>();
struct Inode *raw_inode = &(raw_node->i);
vnode.SetMode(LeToCpu(raw_inode->i_mode));
vnode.SetSize(LeToCpu(raw_inode->i_size));
vnode.SetATime(LeToCpu(raw_inode->i_atime), LeToCpu(raw_inode->i_atime_nsec));
vnode.SetCTime(LeToCpu(raw_inode->i_ctime), LeToCpu(raw_inode->i_ctime_nsec));
vnode.SetMTime(LeToCpu(raw_inode->i_mtime), LeToCpu(raw_inode->i_mtime_nsec));
return RecoverDentry(node_page, vnode);
}
zx_status_t F2fs::FindFsyncDnodes(FsyncInodeList &inode_list) {
SuperblockInfo &superblock_info = GetSuperblockInfo();
uint64_t cp_ver = LeToCpu(superblock_info.GetCheckpoint().checkpoint_ver);
fbl::RefPtr<VnodeF2fs> vnode_refptr;
zx_status_t err = ZX_OK;
// Retrieve the curseg information of kCursegWarmNode
CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegWarmNode);
// Get blkaddr from which it starts finding fsynced dnode block
block_t blkaddr = segment_manager_->StartBlock(curseg->segno) + curseg->next_blkoff;
while (true) {
LockedPage page;
// Since node inode cache cannot be used for recovery, use meta inode cache temporarily and
// delete it later. Meta vnode is indexed by LBA, it can be used to read node blocks. This
// method eliminates duplicate node block reads.
if (zx_status_t ret = GetMetaPage(blkaddr, &page); ret != ZX_OK) {
return ret;
}
if (cp_ver != page.GetPage<NodePage>().CpverOfNode()) {
break;
}
if (!page.GetPage<NodePage>().IsFsyncDnode()) {
// Check next segment
blkaddr = page.GetPage<NodePage>().NextBlkaddrOfNode();
page->ClearUptodate();
continue;
}
auto entry_ptr = GetFsyncInode(inode_list, page.GetPage<NodePage>().InoOfNode());
if (entry_ptr) {
entry_ptr->SetLastDnodeBlkaddr(blkaddr);
if (IsInode(*page) && page.GetPage<NodePage>().IsDentDnode()) {
entry_ptr->GetVnode().SetFlag(InodeInfoFlag::kIncLink);
}
} else {
if (IsInode(*page) && page.GetPage<NodePage>().IsDentDnode()) {
if (err = GetNodeManager().RecoverInodePage(page.GetPage<NodePage>()); err != ZX_OK) {
break;
}
}
if (err = VnodeF2fs::Vget(this, page.GetPage<NodePage>().InoOfNode(), &vnode_refptr);
err != ZX_OK) {
break;
}
// Add this fsync inode to the list
auto entry = std::make_unique<FsyncInodeEntry>(std::move(vnode_refptr));
entry_ptr = entry.get();
entry->SetLastDnodeBlkaddr(blkaddr);
inode_list.push_back(std::move(entry));
}
if (IsInode(*page)) {
if (err = RecoverInode(entry_ptr->GetVnode(), page.GetPage<NodePage>()); err != ZX_OK) {
break;
}
}
// Get the next block information from footer
blkaddr = page.GetPage<NodePage>().NextBlkaddrOfNode();
page->ClearUptodate();
}
return err;
}
void F2fs::DestroyFsyncDnodes(FsyncInodeList &inode_list) {
while (!inode_list.is_empty()) {
inode_list.pop_front();
}
}
void F2fs::CheckIndexInPrevNodes(block_t blkaddr) {
SuperblockInfo &superblock_info = GetSuperblockInfo();
uint32_t segno = segment_manager_->GetSegmentNumber(blkaddr);
uint16_t blkoff = static_cast<uint16_t>(segment_manager_->GetSegOffFromSeg0(blkaddr) &
(superblock_info.GetBlocksPerSeg() - 1));
Summary sum;
nid_t ino;
block_t bidx;
int i;
SegmentEntry &sentry = GetSegmentManager().GetSegmentEntry(segno);
if (!TestValidBitmap(blkoff, sentry.cur_valid_map.get())) {
return;
}
// Get the previous summary
for (i = static_cast<int>(CursegType::kCursegWarmData);
i <= static_cast<int>(CursegType::kCursegColdData); ++i) {
CursegInfo *curseg = segment_manager_->CURSEG_I(static_cast<CursegType>(i));
if (curseg->segno == segno) {
sum = curseg->sum_blk->entries[blkoff];
break;
}
}
if (i > static_cast<int>(CursegType::kCursegColdData)) {
LockedPage sum_page;
GetSegmentManager().GetSumPage(segno, &sum_page);
SummaryBlock *sum_node;
sum_node = sum_page->GetAddress<SummaryBlock>();
sum = sum_node->entries[blkoff];
}
// Get the node page
{
LockedPage node_page;
if (zx_status_t err = GetNodeManager().GetNodePage(LeToCpu(sum.nid), &node_page);
err != ZX_OK) {
FX_LOGS(ERROR) << "F2fs::CheckIndexInPrevNodes, GetNodePage Error!!!";
return;
}
bidx = node_page.GetPage<NodePage>().StartBidxOfNode() + LeToCpu(sum.ofs_in_node);
ino = node_page.GetPage<NodePage>().InoOfNode();
}
// Deallocate previous index in the node page
fbl::RefPtr<VnodeF2fs> vnode_refptr;
VnodeF2fs::Vget(this, ino, &vnode_refptr);
vnode_refptr->TruncateHole(bidx, bidx + 1);
}
void F2fs::DoRecoverData(VnodeF2fs &vnode, NodePage &page) {
uint32_t start, end;
Summary sum;
NodeInfo ni;
if (vnode.RecoverInlineData(page) == ZX_OK) {
// Restored from inline data.
return;
}
start = page.StartBidxOfNode();
if (IsInode(page)) {
end = start + kAddrsPerInode;
} else {
end = start + kAddrsPerBlock;
}
LockedPage dnode_page;
if (GetNodeManager().GetLockedDnodePage(vnode, start, &dnode_page) != ZX_OK) {
return;
}
dnode_page->WaitOnWriteback();
GetNodeManager().GetNodeInfo(dnode_page.GetPage<NodePage>().NidOfNode(), ni);
ZX_DEBUG_ASSERT(ni.ino == page.InoOfNode());
ZX_DEBUG_ASSERT(dnode_page.GetPage<NodePage>().OfsOfNode() == page.OfsOfNode());
zx::status<uint32_t> result;
if (result = GetNodeManager().GetOfsInDnode(vnode, start); result.is_error()) {
return;
}
uint32_t offset_in_dnode = result.value();
for (; start < end; ++start) {
block_t src, dest;
src = DatablockAddr(&dnode_page.GetPage<NodePage>(), offset_in_dnode);
dest = DatablockAddr(&page, offset_in_dnode);
if (src != dest && dest != kNewAddr && dest != kNullAddr) {
if (src == kNullAddr) {
ZX_ASSERT(vnode.ReserveNewBlock(dnode_page.GetPage<NodePage>(), offset_in_dnode) == ZX_OK);
}
// Check the previous node page having this index
CheckIndexInPrevNodes(dest);
GetSegmentManager().SetSummary(&sum, dnode_page.GetPage<NodePage>().NidOfNode(),
offset_in_dnode, ni.version);
// Write dummy data page
GetSegmentManager().RecoverDataPage(sum, src, dest);
vnode.SetDataBlkaddr(dnode_page.GetPage<NodePage>(), offset_in_dnode, dest);
vnode.UpdateExtentCache(dest, page.StartBidxOfNode());
}
++offset_in_dnode;
}
// Write node page in place
GetSegmentManager().SetSummary(&sum, dnode_page.GetPage<NodePage>().NidOfNode(), 0, 0);
if (IsInode(*dnode_page)) {
vnode.MarkInodeDirty();
}
dnode_page.GetPage<NodePage>().CopyNodeFooterFrom(page);
dnode_page.GetPage<NodePage>().FillNodeFooter(ni.nid, ni.ino, page.OfsOfNode(), false);
dnode_page->SetDirty();
}
void F2fs::RecoverData(FsyncInodeList &inode_list, CursegType type) {
SuperblockInfo &superblock_info = GetSuperblockInfo();
uint64_t cp_ver = LeToCpu(superblock_info.GetCheckpoint().checkpoint_ver);
block_t blkaddr = segment_manager_->NextFreeBlkAddr(type);
while (true) {
LockedPage page;
// Eliminate duplicate node block reads using a meta inode cache.
if (zx_status_t ret = GetMetaVnode().GrabCachePage(blkaddr, &page); ret != ZX_OK) {
return;
}
if (MakeOperation(storage::OperationType::kRead, page, blkaddr, PageType::kNode)) {
break;
}
if (cp_ver != page.GetPage<NodePage>().CpverOfNode()) {
break;
}
if (auto entry = GetFsyncInode(inode_list, page.GetPage<NodePage>().InoOfNode());
entry != nullptr) {
DoRecoverData(entry->GetVnode(), page.GetPage<NodePage>());
if (entry->GetLastDnodeBlkaddr() == blkaddr) {
inode_list.erase(*entry);
}
}
// check next segment
blkaddr = page.GetPage<NodePage>().NextBlkaddrOfNode();
page->ClearUptodate();
}
GetSegmentManager().AllocateNewSegments();
}
void F2fs::RecoverFsyncData() {
SuperblockInfo &superblock_info = GetSuperblockInfo();
FsyncInodeList inode_list;
// Step #1: find fsynced inode numbers
if (auto result = FindFsyncDnodes(inode_list); result == ZX_OK) {
// Step #2: recover data
if (!inode_list.is_empty()) {
superblock_info.SetOnRecovery();
RecoverData(inode_list, CursegType::kCursegWarmNode);
superblock_info.ClearOnRecovery();
ZX_DEBUG_ASSERT(inode_list.is_empty());
GetMetaVnode().InvalidatePages(GetSegmentManager().GetMainAreaStartBlock());
WriteCheckpoint(false, false);
}
}
// TODO: Handle error cases
GetMetaVnode().InvalidatePages(GetSegmentManager().GetMainAreaStartBlock());
DestroyFsyncDnodes(inode_list);
}
} // namespace f2fs