blob: 24b840ae5718d040e9bf3c159f8a117b496644ce [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/dir.h"
#include "src/storage/f2fs/f2fs.h"
#include "src/storage/f2fs/node.h"
#include "src/storage/f2fs/node_page.h"
#include "src/storage/f2fs/segment.h"
#include "src/storage/f2fs/vnode.h"
namespace f2fs {
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) {
if (!ipage.IsDentDnode()) {
return ZX_OK;
}
auto vnode_or = GetVnode(LeToCpu(ipage.GetAddress<Node>()->i.i_pino));
if (vnode_or.is_error()) {
return vnode_or.status_value();
}
return fbl::RefPtr<Dir>::Downcast(*vnode_or)->RecoverLink(vnode).status_value();
}
zx_status_t F2fs::RecoverInode(VnodeF2fs &vnode, NodePage &node_page) {
Inode &inode = node_page.GetAddress<Node>()->i;
vnode.SetMode(LeToCpu(inode.i_mode));
vnode.SetTime<Timestamps::AccessTime>({static_cast<time_t>(LeToCpu(inode.i_atime)),
static_cast<time_t>(LeToCpu(inode.i_atime_nsec))});
vnode.SetTime<Timestamps::BirthTime>({static_cast<time_t>(LeToCpu(inode.i_ctime)),
static_cast<time_t>(LeToCpu(inode.i_ctime_nsec))});
vnode.SetTime<Timestamps::ModificationTime>({static_cast<time_t>(LeToCpu(inode.i_mtime)),
static_cast<time_t>(LeToCpu(inode.i_mtime_nsec))});
return RecoverDentry(node_page, vnode);
}
zx::result<F2fs::FsyncInodeList> F2fs::FindFsyncDnodes() {
CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegWarmNode);
// Get blkaddr from which it starts recovery
block_t blkaddr = segment_manager_->StartBlock(curseg->segno) + curseg->next_blkoff;
PageList inode_pages;
FsyncInodeList inode_list;
fs::SharedLock lock(f2fs::GetGlobalLock());
while (true) {
bool new_entry = false;
LockedPage page;
// We cannot get fsync node pages from GetNodePage which can retrieve only checkpointed node
// pages. Instead, GetMetaPage is used to read blocks on non-checkpointed warm nodes segment and
// get the corresponding pages.
if (zx_status_t ret = GetMetaPage(blkaddr, &page); ret != ZX_OK) {
return zx::error(ret);
}
if (superblock_info_->GetCheckpointVer(true) != page.GetPage<NodePage>().CpverOfNode()) {
break;
}
auto node_page = fbl::RefPtr<NodePage>::Downcast(page.CopyRefPtr());
if (!node_page->IsFsyncDnode()) {
blkaddr = page.GetPage<NodePage>().NextBlkaddrOfNode();
page->ClearUptodate();
if (node_page->IsInode() && node_page->IsDentDnode()) {
inode_pages.push_back(std::move(node_page));
}
continue;
}
fbl::RefPtr<NodePage> inode_page;
if (node_page->IsInode()) {
inode_page = node_page;
}
ino_t ino = node_page->InoOfNode();
auto entry_ptr = GetFsyncInode(inode_list, ino);
if (entry_ptr) {
entry_ptr->SetLastDnodeBlkaddr(blkaddr);
if (inode_page && inode_page->IsDentDnode()) {
entry_ptr->GetVnode().SetFlag(InodeInfoFlag::kIncLink);
}
} else {
if (!inode_page) {
for (auto &tmp : inode_pages) {
auto inode = fbl::RefPtr<NodePage>::Downcast(fbl::RefPtr<Page>(&tmp));
if (inode->NidOfNode() == ino) {
inode_page = inode;
}
}
}
if (inode_page && inode_page->IsDentDnode()) {
if (zx_status_t err = GetNodeManager().RecoverInodePage(*inode_page); err != ZX_OK) {
return zx::error(err);
}
}
auto vnode_or = GetVnode(ino);
if (vnode_or.is_error()) {
return vnode_or.take_error();
}
fbl::RefPtr<VnodeF2fs> vnode_refptr = *std::move(vnode_or);
auto entry = std::make_unique<FsyncInodeEntry>(std::move(vnode_refptr));
entry->SetLastDnodeBlkaddr(blkaddr);
inode_list.push_back(std::move(entry));
entry_ptr = GetFsyncInode(inode_list, ino);
new_entry = true;
}
if (inode_page) {
ZX_DEBUG_ASSERT(inode_page->InoOfNode() == page.GetPage<NodePage>().InoOfNode());
if (zx_status_t err = RecoverInode(entry_ptr->GetVnode(), *inode_page); err != ZX_OK) {
return zx::error(err);
}
entry_ptr->SetSize(LeToCpu(inode_page->GetAddress<Node>()->i.i_size));
} else if (new_entry) {
LockedPage ipage;
if (zx_status_t err = GetNodeVnode().GrabLockedPage(ino, &ipage); err != ZX_OK) {
return zx::error(err);
}
entry_ptr->SetSize(LeToCpu(ipage->GetAddress<Node>()->i.i_size));
}
// Get the next block addr
blkaddr = page.GetPage<NodePage>().NextBlkaddrOfNode();
page->ClearUptodate();
}
if (inode_list.is_empty()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(std::move(inode_list));
}
void F2fs::CheckIndexInPrevNodes(block_t blkaddr) {
uint32_t segno = segment_manager_->GetSegmentNumber(blkaddr);
size_t blkoff =
segment_manager_->GetSegOffFromSeg0(blkaddr) & (superblock_info_->GetBlocksPerSeg() - 1);
Summary sum;
const SegmentEntry &sentry = GetSegmentManager().GetSegmentEntry(segno);
if (!sentry.cur_valid_map.GetOne(ToMsbFirst(blkoff))) {
return;
}
// Get the previous summary
int i;
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;
ZX_ASSERT(GetSegmentManager().GetSumPage(segno, &sum_page) == ZX_OK);
SummaryBlock *sum_node;
sum_node = sum_page->GetAddress<SummaryBlock>();
sum = sum_node->entries[blkoff];
}
fbl::RefPtr<VnodeF2fs> vnode_refptr;
size_t bidx;
// 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;
}
nid_t ino = node_page.GetPage<NodePage>().InoOfNode();
auto vnode_or = GetVnode(ino);
ZX_ASSERT(vnode_or.is_ok());
vnode_refptr = *std::move(vnode_or);
bidx = node_page.GetPage<NodePage>().StartBidxOfNode(vnode_refptr->GetAddrsPerInode()) +
LeToCpu(sum.ofs_in_node);
}
// Deallocate previous index in the node page
vnode_refptr->TruncateHole(bidx, bidx + 1, true);
}
void F2fs::DoRecoverData(VnodeF2fs &vnode, NodePage &page) {
size_t start, end;
Summary sum;
NodeInfo ni;
if (vnode.RecoverInlineData(page) == ZX_OK) {
return;
}
start = page.StartBidxOfNode(vnode.GetAddrsPerInode());
if (page.IsInode()) {
end = start + vnode.GetAddrsPerInode();
} else {
end = start + kAddrsPerBlock;
}
auto path_or = vnode.GetNodePath(start);
if (path_or.is_error()) {
return;
}
auto page_or = GetNodeManager().GetLockedDnodePage(*path_or, vnode.IsDir());
if (page_or.is_error()) {
return;
}
vnode.IncBlocks(path_or->num_new_nodes);
LockedPage dnode_page = std::move(*page_or);
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());
size_t offset_in_dnode = GetOfsInDnode(*path_or);
for (; start < end; ++start) {
block_t src, dest;
src = dnode_page.GetPage<NodePage>().GetBlockAddr(offset_in_dnode);
dest = page.GetBlockAddr(offset_in_dnode);
if (src != dest && dest != kNewAddr && dest != kNullAddr) {
if (src == kNullAddr) {
ZX_ASSERT(vnode.ReserveNewBlock(dnode_page, offset_in_dnode) == ZX_OK);
}
// Check the previous node page having this index
CheckIndexInPrevNodes(dest);
SetSummary(&sum, dnode_page.GetPage<NodePage>().NidOfNode(), offset_in_dnode, ni.version);
// Write dummy data page
GetSegmentManager().RecoverDataPage(sum, src, dest);
dnode_page.WaitOnWriteback();
dnode_page.GetPage<NodePage>().SetDataBlkaddr(offset_in_dnode, dest);
dnode_page.SetDirty();
vnode.UpdateExtentCache(page.StartBidxOfNode(vnode.GetAddrsPerInode()) + offset_in_dnode,
dest);
}
++offset_in_dnode;
}
dnode_page.GetPage<NodePage>().CopyNodeFooterFrom(page);
dnode_page.GetPage<NodePage>().FillNodeFooter(ni.nid, ni.ino, page.OfsOfNode());
dnode_page.SetDirty();
}
void F2fs::RecoverData(FsyncInodeList &inode_list) {
block_t blkaddr = segment_manager_->NextFreeBlkAddr(CursegType::kCursegWarmNode);
while (true) {
LockedPage page;
// Eliminate duplicate node block reads using a meta inode cache.
if (zx_status_t ret = GetMetaVnode().GrabLockedPage(blkaddr, &page); ret != ZX_OK) {
return;
}
auto status = MakeReadOperation(page, blkaddr, PageType::kNode);
if (status.is_error()) {
break;
}
if (superblock_info_->GetCheckpointVer(true) != 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() {
// Step #1: find fsynced inode numbers
SetOnRecovery();
if (auto result = FindFsyncDnodes(); result.is_ok()) {
FsyncInodeList inode_list = std::move(*result);
// Step #2: recover data
for (auto &entry : inode_list) {
auto &vnode = entry.GetVnode();
ZX_ASSERT(vnode.InitFileCache(entry.GetSize()) == ZX_OK);
vnode.SetDirty();
}
RecoverData(inode_list);
ZX_DEBUG_ASSERT(inode_list.is_empty());
GetMetaVnode().InvalidatePages(GetSegmentManager().GetMainAreaStartBlock());
SyncFs(false);
}
GetMetaVnode().InvalidatePages(GetSegmentManager().GetMainAreaStartBlock());
ClearOnRecovery();
}
} // namespace f2fs