use std::io::{Read, Seek};
use crate::dir::for_each_fs_record_for_oid;
use crate::fsrecord::{decode_jkey, RecordType};
use crate::inode::Inode;
use crate::volume::ApfsVolume;
pub const J_FILE_EXTENT_LEN_MASK: u64 = 0x00ff_ffff_ffff_ffff;
const OFF_FEXT_KEY_LOGICAL: usize = 8;
const OFF_FEXT_LEN_AND_FLAGS: usize = 0;
const OFF_FEXT_PHYS_BLOCK: usize = 8;
const MAX_FILE_BYTES: u64 = 1 << 34;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct FileExtent {
pub logical_offset: u64,
pub len: u64,
pub phys_block_num: u64,
}
pub fn list_extents<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
stream_oid: u64,
block_size: usize,
) -> crate::Result<Vec<FileExtent>> {
let mut out = Vec::new();
for_each_fs_record_for_oid(reader, volume, block_size, stream_oid, &mut |key, value| {
let (oid, ty) = decode_jkey(crate::bytes::le_u64(key, 0));
if ty != Some(RecordType::FileExtent) || oid != stream_oid {
return;
}
let logical_offset = crate::bytes::le_u64(key, OFF_FEXT_KEY_LOGICAL);
let len = crate::bytes::le_u64(value, OFF_FEXT_LEN_AND_FLAGS) & J_FILE_EXTENT_LEN_MASK;
let phys_block_num = crate::bytes::le_u64(value, OFF_FEXT_PHYS_BLOCK);
out.push(FileExtent {
logical_offset,
len,
phys_block_num,
});
})?;
out.sort_by_key(|e| e.logical_offset);
Ok(out)
}
pub fn read_stream<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
stream_oid: u64,
logical_size: u64,
block_size: usize,
) -> crate::Result<Vec<u8>> {
if logical_size > MAX_FILE_BYTES {
return Err(crate::ApfsError::FieldOutOfRange {
structure: "j_dstream",
field: "size",
value: logical_size,
cap: MAX_FILE_BYTES,
});
}
let extents = list_extents(reader, volume, stream_oid, block_size)?;
let size = logical_size as usize;
let mut out = vec![0u8; size];
for ext in &extents {
let start = ext.logical_offset as usize;
if start >= size || ext.phys_block_num == 0 {
continue;
}
let avail = size - start;
let copy_len = (ext.len as usize).min(avail);
let byte_off = ext.phys_block_num.checked_mul(block_size as u64).ok_or(
crate::ApfsError::FieldOutOfRange {
structure: "j_file_extent",
field: "phys_block_num",
value: ext.phys_block_num,
cap: u64::MAX / block_size as u64,
},
)?;
reader.seek(std::io::SeekFrom::Start(byte_off))?;
reader.read_exact(&mut out[start..start + copy_len])?;
}
Ok(out)
}
pub fn read_data<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode: &Inode,
block_size: usize,
) -> crate::Result<Vec<u8>> {
if let Some(header) = crate::xattr::decmpfs_header(reader, volume, inode.oid, block_size)? {
return crate::compression::read_compressed(reader, volume, inode, &header, block_size);
}
let size = inode.size.unwrap_or(0);
read_stream(reader, volume, inode.private_id, size, block_size)
}