use crate::block_io::BlockDevice;
use crate::error::Result;
use crate::inode::Inode;
use crate::xattr;
pub fn read_all(
dev: &dyn BlockDevice,
inode: &Inode,
inode_raw: &[u8],
inode_size: u16,
block_size: u32,
) -> Result<Vec<u8>> {
let total = inode.size as usize;
let inline_max = 60;
let from_block = total.min(inline_max);
let mut out = Vec::with_capacity(total.min(64 * 1024));
out.extend_from_slice(&inode.block[..from_block]);
if total <= inline_max {
return Ok(out);
}
let need = total - inline_max;
let extra = xattr::get(dev, inode, inode_raw, inode_size, block_size, "system.data")?.ok_or(
crate::error::Error::Corrupt("inline file larger than 60 bytes has no system.data xattr"),
)?;
if extra.len() < need {
return Err(crate::error::Error::Corrupt(
"inline file's system.data xattr is shorter than its size claims",
));
}
out.extend_from_slice(&extra[..need]);
Ok(out)
}
pub fn read_range(
dev: &dyn BlockDevice,
inode: &Inode,
inode_raw: &[u8],
inode_size: u16,
block_size: u32,
offset: u64,
dst: &mut [u8],
) -> Result<usize> {
let total = inode.size;
if offset >= total {
return Ok(0);
}
let full = read_all(dev, inode, inode_raw, inode_size, block_size)?;
let want = ((total - offset) as usize).min(dst.len());
let avail = full.len().saturating_sub(offset as usize);
let n = want.min(avail);
dst[..n].copy_from_slice(&full[offset as usize..offset as usize + n]);
Ok(n)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inode::{Inode, OFF_MODE, OFF_SIZE_LO};
struct EmptyDev;
impl BlockDevice for EmptyDev {
fn read_at(&self, _off: u64, buf: &mut [u8]) -> Result<()> {
buf.fill(0);
Ok(())
}
fn write_at(&self, _off: u64, _buf: &[u8]) -> Result<()> {
Ok(())
}
fn size_bytes(&self) -> u64 {
1 << 20
}
}
fn inline_inode(size: u32) -> (Inode, Vec<u8>) {
let mut raw = vec![0u8; 128];
raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&0o100_644u16.to_le_bytes());
raw[OFF_SIZE_LO..OFF_SIZE_LO + 4].copy_from_slice(&size.to_le_bytes());
for (i, b) in raw[0x28..0x28 + 60].iter_mut().enumerate() {
*b = b'A'.wrapping_add((i % 26) as u8);
}
let inode = Inode::parse(&raw).expect("parse synthetic inode");
(inode, raw)
}
#[test]
fn a_file_within_the_inline_area_reads_without_an_xattr() {
let (inode, raw) = inline_inode(60);
let got =
read_all(&EmptyDev, &inode, &raw, 128, 4096).expect("60 bytes are all in i_block");
assert_eq!(got.len(), 60);
}
#[test]
fn a_missing_spill_xattr_is_corruption_not_an_empty_tail() {
let (inode, raw) = inline_inode(100);
let err = read_all(&EmptyDev, &inode, &raw, 128, 4096)
.expect_err("100 bytes cannot fit in the 60-byte inline area without a spill");
assert!(
format!("{err:?}").contains("system.data"),
"the error should name the missing xattr, got: {err:?}"
);
}
}