use crate::chunk::{block_group as flags, key_type, objectid, DiskKey};
use crate::error::{Error, Result};
use crate::fs::Filesystem;
use crate::superblock::{le32, le64};
pub(crate) mod block_group_item {
pub const USED: usize = 0;
#[allow(dead_code)]
pub const CHUNK_OBJECTID: usize = 8;
pub const FLAGS: usize = 16;
pub const SIZE: usize = 24;
}
pub(crate) mod free_space_info {
pub const EXTENT_COUNT: usize = 0;
pub const FLAGS: usize = 4;
pub const SIZE: usize = 8;
}
const USING_BITMAPS: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockGroup {
pub start: u64,
pub length: u64,
pub used: u64,
pub flags: u64,
}
impl BlockGroup {
pub fn end(&self) -> u64 {
self.start.saturating_add(self.length)
}
pub fn holds_metadata(&self) -> bool {
self.flags & flags::METADATA != 0
}
pub fn holds_data(&self) -> bool {
self.flags & flags::DATA != 0
}
pub fn contains(&self, at: u64) -> bool {
at >= self.start && at < self.end()
}
pub fn free(&self) -> u64 {
self.length.saturating_sub(self.used)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FreeExtent {
pub start: u64,
pub len: u64,
}
impl FreeExtent {
pub fn end(&self) -> u64 {
self.start.saturating_add(self.len)
}
}
impl Filesystem {
pub fn block_groups(&self) -> Result<Vec<BlockGroup>> {
let root = self.tree_root(objectid::EXTENT_TREE)?;
let reader = self.pool_reader();
let tree = reader.tree();
let mut out = Vec::new();
tree.for_each(root, &mut |key: &DiskKey, data: &[u8]| {
if key.key_type == key_type::BLOCK_GROUP_ITEM && data.len() >= block_group_item::SIZE {
if key.objectid.checked_add(key.offset).is_none() {
return Err(Error::BadSuperblock(format!(
"the block group at {} says it is {} bytes long, which ends past \
the address space",
key.objectid, key.offset
)));
}
out.push(BlockGroup {
start: key.objectid,
length: key.offset,
used: le64(data, block_group_item::USED),
flags: le64(data, block_group_item::FLAGS),
});
}
Ok(true)
})?;
out.sort_by_key(|g| g.start);
Ok(out)
}
pub fn free_extents(&self, group: &BlockGroup) -> Result<Vec<FreeExtent>> {
let mut taken = self.allocated_extents(group)?;
taken.sort_by_key(|e| e.start);
Self::gaps(group, &taken)
}
pub fn free_extents_by_group(&self, groups: &[BlockGroup]) -> Result<Vec<Vec<FreeExtent>>> {
let mut taken = self.allocated_by_group(groups)?;
let mut out = Vec::with_capacity(groups.len());
for (group, runs) in groups.iter().zip(taken.iter_mut()) {
runs.sort_by_key(|e| e.start);
out.push(Self::gaps(group, runs)?);
}
Ok(out)
}
fn gaps(group: &BlockGroup, taken: &[FreeExtent]) -> Result<Vec<FreeExtent>> {
let mut free = Vec::new();
let mut pos = group.start;
for e in taken {
if e.start < pos {
return Err(Error::UnsupportedFeature(format!(
"the extent tree has an allocated run at {} that overlaps the one \
ending at {pos}, in the block group at {}",
e.start, group.start
)));
}
if e.start > pos {
free.push(FreeExtent {
start: pos,
len: e.start - pos,
});
}
pos = e.end();
}
if pos < group.end() {
free.push(FreeExtent {
start: pos,
len: group.end() - pos,
});
}
Ok(free)
}
fn allocated_extents(&self, group: &BlockGroup) -> Result<Vec<FreeExtent>> {
Ok(self
.allocated_by_group(std::slice::from_ref(group))?
.pop()
.unwrap_or_default())
}
fn allocated_by_group(&self, groups: &[BlockGroup]) -> Result<Vec<Vec<FreeExtent>>> {
let root = self.tree_root(objectid::EXTENT_TREE)?;
let reader = self.pool_reader();
let tree = reader.tree();
let nodesize = self.sb.nodesize as u64;
let mut out = vec![Vec::new(); groups.len()];
tree.for_each(root, &mut |key: &DiskKey, _data: &[u8]| {
let len = match key.key_type {
key_type::EXTENT_ITEM => key.offset,
key_type::METADATA_ITEM => nodesize,
_ => return Ok(true),
};
if let Some(i) = groups
.iter()
.position(|g| key.objectid >= g.start && key.objectid < g.end())
{
if key.objectid.checked_add(len).is_none() {
return Err(Error::BadSuperblock(format!(
"the allocated run at {} says it is {len} bytes long, which ends \
past the address space",
key.objectid
)));
}
out[i].push(FreeExtent {
start: key.objectid,
len,
});
}
Ok(true)
})?;
Ok(out)
}
pub fn cached_free_extents(&self, group: &BlockGroup) -> Result<Option<Vec<FreeExtent>>> {
let Ok(root) = self.tree_root(objectid::FREE_SPACE_TREE) else {
return Ok(None);
};
let reader = self.pool_reader();
let tree = reader.tree();
let sectorsize = self.sb.sectorsize as u64;
let mut info = None;
let mut out: Vec<FreeExtent> = Vec::new();
let mut err = None;
tree.for_each(root, &mut |key: &DiskKey, data: &[u8]| {
if key.objectid < group.start || key.objectid >= group.end() {
return Ok(true);
}
match key.key_type {
key_type::FREE_SPACE_INFO if data.len() >= free_space_info::SIZE => {
info = Some((
le32(data, free_space_info::EXTENT_COUNT),
le32(data, free_space_info::FLAGS),
));
}
key_type::FREE_SPACE_EXTENT => out.push(FreeExtent {
start: key.objectid,
len: key.offset,
}),
key_type::FREE_SPACE_BITMAP => {
let sectors = (key.offset / sectorsize) as usize;
if data.len() * 8 < sectors {
err = Some(format!(
"a free-space bitmap at {} covers {} bytes, which needs {sectors} \
bits, but the item holds {}",
key.objectid,
key.offset,
data.len() * 8
));
return Ok(false);
}
let mut run: Option<u64> = None;
for i in 0..sectors {
let free = data[i / 8] & (1 << (i % 8)) != 0;
let at = key.objectid + i as u64 * sectorsize;
match (free, run) {
(true, None) => run = Some(at),
(false, Some(from)) => {
out.push(FreeExtent {
start: from,
len: at - from,
});
run = None;
}
_ => {}
}
}
if let Some(from) = run {
let end = key.objectid + key.offset;
out.push(FreeExtent {
start: from,
len: end - from,
});
}
}
_ => {}
}
Ok(true)
})?;
if let Some(msg) = err {
return Err(Error::UnsupportedFeature(msg));
}
let Some((_count, info_flags)) = info else {
return Err(Error::UnsupportedFeature(format!(
"the free-space tree holds no info item for the block group at {}, so what \
it believes is free there cannot be established",
group.start
)));
};
let _ = info_flags & USING_BITMAPS;
out.sort();
Ok(Some(merge_adjacent(out)))
}
pub fn for_each_extent_item(&self, visit: &mut dyn FnMut(&DiskKey, &[u8])) -> Result<()> {
let root = self.tree_root(objectid::EXTENT_TREE)?;
let reader = self.pool_reader();
let tree = reader.tree();
tree.for_each(root, &mut |key: &DiskKey, data: &[u8]| {
visit(key, data);
Ok(true)
})
}
pub fn tree_root_public(&self, objectid: u64) -> Result<u64> {
self.tree_root(objectid)
}
pub fn for_each_item_in(
&self,
root: u64,
visit: &mut dyn FnMut(&DiskKey, &[u8]),
) -> Result<()> {
let reader = self.pool_reader();
let tree = reader.tree();
tree.for_each(root, &mut |key: &DiskKey, data: &[u8]| {
visit(key, data);
Ok(true)
})
}
pub(crate) fn tree_root(&self, objectid: u64) -> Result<u64> {
let reader = self.pool_reader();
let tree = reader.tree();
crate::fs::required_root_item_target(&tree, self.sb.root, objectid)
}
pub(crate) fn on_superblock_copy(&self, at: u64, len: u64) -> Result<bool> {
use crate::superblock::SUPER_OFFSETS;
let chunk = self.map.chunk_for(at).ok_or(Error::UnmappedLogical(at))?;
let row = chunk.stripe_len.max(1);
for mirror in 0..self.map.mirrors_at(at)? {
let m = self.map.map_mirror(at, mirror)?;
for stripe in chunk
.stripes
.iter()
.filter(|s| s.devid == m.devid && s.offset <= m.physical)
{
for © in SUPER_OFFSETS.iter().filter(|&&c| c >= stripe.offset) {
let start = stripe.offset + (copy - stripe.offset) / row * row;
if m.physical < start + row && start < m.physical + len {
return Ok(true);
}
}
}
}
Ok(false)
}
pub fn find_metadata_block(&self) -> Result<u64> {
let nodesize = self.sb.nodesize as u64;
let mut best_free = 0u64;
let groups: Vec<BlockGroup> = self
.block_groups()?
.into_iter()
.filter(|g| g.holds_metadata())
.collect();
for runs in self.free_extents_by_group(&groups)? {
for run in runs {
best_free = best_free.max(usable_in(run, nodesize));
let Some(mut at) = place_in_run(run, nodesize) else {
continue;
};
while at + nodesize <= run.end() {
if !self.on_superblock_copy(at, nodesize)? {
return Ok(at);
}
at += nodesize;
}
}
}
Err(Error::UnsupportedFeature(format!(
"no metadata block group has {nodesize} contiguous bytes aligned to a tree \
block; the longest usable run is {best_free}. Allocating a new block group \
is not implemented"
)))
}
}
impl Filesystem {
pub(crate) fn find_data_extents(&self, lens: &[u64]) -> Result<Vec<u64>> {
let sectorsize = u64::from(self.sb.sectorsize).max(1);
let groups: Vec<BlockGroup> = self
.block_groups()?
.into_iter()
.filter(|g| g.holds_data() && !g.holds_metadata())
.collect();
let mut runs: Vec<FreeExtent> = self
.free_extents_by_group(&groups)?
.into_iter()
.flatten()
.collect();
let mut out = Vec::with_capacity(lens.len());
'next: for &len in lens {
if len == 0 || len % sectorsize != 0 {
return Err(Error::UnsupportedFeature(format!(
"a data extent of {len} bytes is not a whole number of {sectorsize}-byte \
sectors"
)));
}
for i in 0..runs.len() {
let run = runs[i];
let mut at = run.start.next_multiple_of(sectorsize);
while at.checked_add(len).is_some_and(|end| end <= run.end()) {
if let Some(row_end) = self.superblock_row_in(at, len)? {
at = row_end.next_multiple_of(sectorsize);
continue;
}
runs.splice(
i..=i,
[
FreeExtent {
start: run.start,
len: at - run.start,
},
FreeExtent {
start: at + len,
len: run.end() - (at + len),
},
],
);
out.push(at);
continue 'next;
}
}
return Err(Error::UnsupportedFeature(format!(
"no data block group has {len} contiguous free bytes clear of the superblock \
copies; allocating a new block group is not implemented"
)));
}
Ok(out)
}
fn superblock_row_in(&self, at: u64, len: u64) -> Result<Option<u64>> {
let chunk = self.map.chunk_for(at).ok_or(Error::UnmappedLogical(at))?;
let row = chunk.stripe_len.max(1);
let end = at.saturating_add(len);
let mut pos = at;
while pos < end {
let row_end = (chunk.logical + (pos - chunk.logical) / row * row + row).min(end);
if self.on_superblock_copy(pos, row_end - pos)? {
return Ok(Some(row_end));
}
pos = row_end;
}
Ok(None)
}
}
fn usable_in(run: FreeExtent, nodesize: u64) -> u64 {
run.end()
.saturating_sub(run.start.next_multiple_of(nodesize))
}
fn place_in_run(run: FreeExtent, nodesize: u64) -> Option<u64> {
let start = run.start.next_multiple_of(nodesize);
if run.end().saturating_sub(start) >= nodesize {
Some(start)
} else {
None
}
}
pub(crate) fn merge_adjacent(runs: Vec<FreeExtent>) -> Vec<FreeExtent> {
let mut out: Vec<FreeExtent> = Vec::with_capacity(runs.len());
for run in runs {
match out.last_mut() {
Some(prev) if prev.end() == run.start => prev.len += run.len,
_ => out.push(run),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn run(start: u64, len: u64) -> FreeExtent {
FreeExtent { start, len }
}
#[test]
fn a_group_or_run_that_ends_past_the_address_space_is_refused() {
let hostile = BlockGroup {
start: u64::MAX - 4,
length: 0x1000,
used: 0,
flags: 0,
};
assert_eq!(hostile.end(), u64::MAX, "saturating, not wrapping");
let ordinary = BlockGroup {
start: 1 << 20,
length: 1 << 30,
used: 0,
flags: 0,
};
assert_eq!(ordinary.end(), (1 << 20) + (1 << 30));
let extent = FreeExtent {
start: u64::MAX - 4,
len: 0x1000,
};
assert_eq!(extent.end(), u64::MAX);
}
#[test]
fn adjacent_runs_merge_and_separated_ones_do_not() {
assert_eq!(
merge_adjacent(vec![run(0, 10), run(10, 5), run(20, 5)]),
vec![run(0, 15), run(20, 5)]
);
assert_eq!(merge_adjacent(vec![]), vec![]);
assert_eq!(merge_adjacent(vec![run(4096, 8192)]), vec![run(4096, 8192)]);
}
#[test]
fn a_group_reports_its_end_and_what_is_free() {
let g = BlockGroup {
start: 22_020_096,
length: 8_388_608,
used: 1_048_576,
flags: flags::METADATA | flags::DUP,
};
assert_eq!(g.end(), 22_020_096 + 8_388_608);
assert_eq!(g.free(), 8_388_608 - 1_048_576);
assert!(g.holds_metadata());
assert!(!g.holds_data());
}
#[test]
fn a_mixed_group_holds_data_and_metadata() {
let g = BlockGroup {
start: 0,
length: 1024,
used: 0,
flags: flags::DATA | flags::METADATA,
};
assert!(g.holds_metadata());
assert!(g.holds_data());
}
#[test]
fn a_misaligned_run_places_the_block_at_the_next_boundary() {
assert_eq!(place_in_run(run(1000, 4096 + 3096), 4096), Some(4096));
assert_eq!(place_in_run(run(0, 4096), 4096), Some(0));
assert_eq!(place_in_run(run(8192, 40960), 4096), Some(8192));
}
#[test]
fn a_run_that_only_fits_before_alignment_is_refused() {
assert_eq!(place_in_run(run(1000, 4096), 4096), None);
assert_eq!(usable_in(run(1000, 4096), 4096), 1000);
assert_eq!(place_in_run(run(0, 4095), 4096), None);
assert_eq!(place_in_run(run(0, 0), 4096), None);
}
#[test]
fn usable_length_pays_for_alignment_first() {
assert_eq!(usable_in(run(0, 8192), 4096), 8192);
assert_eq!(usable_in(run(4095, 8193), 4096), 8192, "loses one byte");
assert_eq!(usable_in(run(1, 10), 4096), 0, "nowhere near a boundary");
}
#[test]
fn free_space_saturates_rather_than_wrapping() {
let g = BlockGroup {
start: 0,
length: 1024,
used: 4096,
flags: flags::METADATA,
};
assert_eq!(g.free(), 0);
}
}