blob: d642141ead87822ef18a2b6f95d4c8d3e270a5bf [file] [edit]
// Copyright 2022 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/node_page.h"
#include "src/storage/f2fs/common.h"
#include "src/storage/f2fs/node.h"
namespace f2fs {
void NodePage::FillNodeFooter(nid_t nid, nid_t ino, size_t ofs) {
NodeFooter &raw_footer = node().footer;
raw_footer.nid = CpuToLe(nid);
raw_footer.ino = CpuToLe(ino);
raw_footer.flag = CpuToLe(
safemath::checked_cast<uint32_t>(ofs << static_cast<uint32_t>(BitShift::kOffsetBitShift)));
}
void NodePage::CopyNodeFooterFrom(NodePage &src) {
memcpy(&node().footer, &src.node().footer, sizeof(NodeFooter));
}
void NodePage::FillNodeFooterBlkaddr(block_t blkaddr, uint64_t ver) {
NodeFooter &raw_footer = node().footer;
raw_footer.cp_ver = CpuToLe(ver);
raw_footer.next_blkaddr = CpuToLe(blkaddr);
}
nid_t NodePage::InoOfNode() const { return LeToCpu(node().footer.ino); }
nid_t NodePage::NidOfNode() const { return LeToCpu(node().footer.nid); }
uint32_t NodePage::OfsOfNode() const {
uint32_t flag = LeToCpu(node().footer.flag);
return flag >> static_cast<int>(BitShift::kOffsetBitShift);
}
uint64_t NodePage::CpverOfNode() const { return LeToCpu(node().footer.cp_ver); }
block_t NodePage::NextBlkaddrOfNode() const { return LeToCpu(node().footer.next_blkaddr); }
// f2fs assigns the following node offsets described as (num).
// N = kNidsPerBlock
//
// Inode block (0)
// |- direct node (1)
// |- direct node (2)
// |- indirect node (3)
// | `- direct node (4 => 4 + N - 1)
// |- indirect node (4 + N)
// | `- direct node (5 + N => 5 + 2N - 1)
// `- double indirect node (5 + 2N)
// `- indirect node (6 + 2N)
// `- direct node (x(N + 1))
bool NodePage::IsDnode() const {
uint32_t ofs = OfsOfNode();
if (ofs == kOfsIndirectNode1 || ofs == kOfsIndirectNode2 || ofs == kOfsDoubleIndirectNode) {
return false;
}
if (ofs >= kOfsDoubleIndirectNode + 1) {
ofs -= kOfsDoubleIndirectNode + 1;
// In the double-indirect subtree, nodes appear in repeating groups of (N + 1) nodes:
// 1 Indirect Node followed by N Direct Nodes. Therefore, if the relative offset
// is a multiple of (N + 1), it is an Indirect Node, not a Direct Node.
if (ofs % (kNidsPerBlock + 1) == 0) {
return false;
}
}
return true;
}
void NodePage::SetNid(size_t off, nid_t nid) {
if (IsInode()) {
node().i.i_nid[off - kNodeDir1Block] = CpuToLe(nid);
} else {
node().in.nid[off] = CpuToLe(nid);
}
}
nid_t NodePage::GetNid(size_t off) const {
if (IsInode()) {
return LeToCpu(node().i.i_nid[off - kNodeDir1Block]);
}
return LeToCpu(node().in.nid[off]);
}
bool NodePage::IsColdNode() const {
uint32_t flag = LeToCpu(node().footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kColdBitShift)).ValueOrDie();
return flag & bit;
}
bool NodePage::IsFsyncDnode() const {
uint32_t flag = LeToCpu(node().footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kFsyncBitShift)).ValueOrDie();
return flag & bit;
}
bool NodePage::IsDentDnode() const {
uint32_t flag = LeToCpu(node().footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kDentBitShift)).ValueOrDie();
return flag & bit;
}
void NodePage::SetColdNode(const bool is_dir) {
Node &raw_node = node();
uint32_t flag = LeToCpu(raw_node.footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kColdBitShift)).ValueOrDie();
if (is_dir) {
flag &= ~bit;
} else {
flag |= bit;
}
raw_node.footer.flag = CpuToLe(flag);
}
void NodePage::SetFsyncMark(bool mark) {
Node &raw_node = node();
uint32_t flag = LeToCpu(raw_node.footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kFsyncBitShift)).ValueOrDie();
if (mark) {
flag |= bit;
} else {
flag &= ~bit;
}
raw_node.footer.flag = CpuToLe(flag);
}
void NodePage::SetDentryMark(bool mark) {
Node &raw_node = node();
uint32_t flag = LeToCpu(raw_node.footer.flag);
uint32_t bit =
safemath::CheckLsh(1U, static_cast<uint32_t>(BitShift::kDentBitShift)).ValueOrDie();
if (mark) {
flag |= bit;
} else {
flag &= ~bit;
}
raw_node.footer.flag = CpuToLe(flag);
}
size_t NodePage::StartBidxOfNode(size_t num_addrs) const {
size_t node_ofs = OfsOfNode();
size_t num_of_indirect_nodes = 0;
if (node_ofs == kOfsInode) {
return 0;
}
if (node_ofs <= kOfsDirectNode2) {
num_of_indirect_nodes = 0;
} else if (node_ofs >= kOfsIndirectNode1 && node_ofs < kOfsIndirectNode2) {
num_of_indirect_nodes = 1;
} else if (node_ofs >= kOfsIndirectNode2 && node_ofs < kOfsDoubleIndirectNode) {
num_of_indirect_nodes = 2;
} else if (node_ofs == kOfsDoubleIndirectNode || node_ofs == kOfsDoubleIndirectNode + 1) {
num_of_indirect_nodes = 3;
} else {
// Add 4 to account for preceding indirect nodes:
// - 3 indirect nodes in levels 1 and 2 (kOfsIndirectNode1, kOfsIndirectNode2,
// kOfsDoubleIndirectNode)
// - 1 intermediate indirect node (offset kOfsDoubleIndirectNode + 1) inside this Level 3
// subtree.
num_of_indirect_nodes = (node_ofs - kOfsDoubleIndirectNode - 2) / (kNidsPerBlock + 1) + 4;
}
size_t bidx = node_ofs - num_of_indirect_nodes - 1;
return (num_addrs + safemath::CheckMul(bidx, kAddrsPerBlock)).ValueOrDie();
}
bool NodePage::IsInode() const {
NodeFooter &raw_footer = node().footer;
return raw_footer.nid == raw_footer.ino;
}
// Linux f2fs indexes an inode's addresses through the vnode's already-validated i_extra_isize
// and reads the on-disk field only where no vnode exists. This method has no vnode to consult
// -- recovery and GC reach it straight from a page -- so it bounds the start it reads rather
// than trusting it.
std::span<const block_t> NodePage::addrs_array() const {
const Node &raw_node = node();
if (!IsInode()) {
return {raw_node.dn.addr, kAddrsPerBlock};
}
const Inode &inode = raw_node.i;
size_t start = 0;
if (inode.i_inline & kExtraAttr) {
// Read straight from the page, which recovery and GC reach without a vnode to reject a
// corrupted layout first, so the start can name an entry the array does not have.
start = LeToCpu(inode.i_extra_isize) / sizeof(uint32_t);
if (start >= kAddrsPerInode) {
return {};
}
}
return std::span<const block_t>(inode.i_addr, kAddrsPerInode).subspan(start);
}
std::span<block_t> NodePage::addrs_array() {
// This overload is chosen only for a NodePage that is not const, so the array it names is
// not const either and both overloads can share the one bounds calculation.
const std::span<const block_t> addrs = static_cast<const NodePage *>(this)->addrs_array();
return {const_cast<block_t *>(addrs.data()), addrs.size()};
}
block_t NodePage::GetBlockAddr(const size_t offset) const {
std::span<const block_t> addrs = addrs_array();
if (offset >= addrs.size()) {
FX_LOGS(WARNING) << "node " << NidOfNode() << " holds " << addrs.size()
<< " block addresses, but offset " << offset << " was requested";
return kNullAddr;
}
return LeToCpu(addrs[offset]);
}
void NodePage::SetDataBlkaddr(size_t ofs_in_node, block_t new_addr) {
std::span<block_t> addrs = addrs_array();
if (ofs_in_node >= addrs.size()) {
// The array bounds come from the image, so this is a corrupted node rather than a caller
// that got its arithmetic wrong. Drop the update instead of writing outside the block.
FX_LOGS(WARNING) << "node " << NidOfNode() << " holds " << addrs.size()
<< " block addresses, so " << new_addr << " cannot be recorded at "
<< ofs_in_node;
return;
}
// A newly reserved block may only take the place of a hole, and any other address may only
// replace one that is already there. Violating that is a caller bug, not a corrupted image.
const block_t old_addr = LeToCpu(addrs[ofs_in_node]);
const bool replaces_hole_iff_new = (new_addr == kNewAddr) == (old_addr == kNullAddr);
if (!replaces_hole_iff_new) {
FX_LOGS(WARNING) << "node " << NidOfNode() << " records " << new_addr << " at " << ofs_in_node
<< " over " << old_addr;
ZX_DEBUG_ASSERT(replaces_hole_iff_new);
}
addrs[ofs_in_node] = CpuToLe(new_addr);
}
} // namespace f2fs