use std::io::{Read, Seek};
use crate::dir::for_each_fs_record_for_oid;
use crate::fsrecord::{decode_jkey, RecordType};
use crate::volume::ApfsVolume;
pub const XATTR_DATA_STREAM: u16 = 0x0001;
pub const XATTR_DATA_EMBEDDED: u16 = 0x0002;
pub const XATTR_NAME_DECMPFS: &str = "com.apple.decmpfs";
pub const XATTR_NAME_RESOURCE_FORK: &str = "com.apple.ResourceFork";
pub const XATTR_NAME_SYMLINK: &str = "com.apple.fs.symlink";
const OFF_XATTR_KEY_NAME_LEN: usize = 8;
const OFF_XATTR_KEY_NAME: usize = 10;
const OFF_XATTR_VAL_FLAGS: usize = 0;
const OFF_XATTR_VAL_XDATA_LEN: usize = 2;
const OFF_XATTR_VAL_XDATA: usize = 4;
const OFF_XDSTREAM_OBJ_ID: usize = 0;
const OFF_XDSTREAM_SIZE: usize = 8;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum XattrValue {
Embedded(Vec<u8>),
Stream { dstream_oid: u64, size: u64 },
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Xattr {
pub name: String,
pub flags: u16,
pub value: XattrValue,
}
fn parse_xattr(key: &[u8], value: &[u8]) -> Option<Xattr> {
let name_len = crate::bytes::le_u16(key, OFF_XATTR_KEY_NAME_LEN) as usize;
if name_len == 0 {
return None;
}
let name_bytes = key.get(OFF_XATTR_KEY_NAME..OFF_XATTR_KEY_NAME + name_len)?;
let name = decode_cstr(name_bytes);
let flags = crate::bytes::le_u16(value, OFF_XATTR_VAL_FLAGS);
let xdata_len = crate::bytes::le_u16(value, OFF_XATTR_VAL_XDATA_LEN) as usize;
let xdata = value
.get(OFF_XATTR_VAL_XDATA..OFF_XATTR_VAL_XDATA + xdata_len)
.unwrap_or(&[]);
let xvalue = if flags & XATTR_DATA_STREAM != 0 {
XattrValue::Stream {
dstream_oid: crate::bytes::le_u64(xdata, OFF_XDSTREAM_OBJ_ID),
size: crate::bytes::le_u64(xdata, OFF_XDSTREAM_SIZE),
}
} else {
XattrValue::Embedded(xdata.to_vec())
};
Some(Xattr {
name,
flags,
value: xvalue,
})
}
pub fn list_xattrs<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode_oid: u64,
block_size: usize,
) -> crate::Result<Vec<Xattr>> {
let mut out = Vec::new();
for_each_fs_record_for_oid(reader, volume, block_size, inode_oid, &mut |key, value| {
let (oid, ty) = decode_jkey(crate::bytes::le_u64(key, 0));
if ty != Some(RecordType::Xattr) || oid != inode_oid {
return;
}
if let Some(x) = parse_xattr(key, value) {
out.push(x);
}
})?;
Ok(out)
}
pub fn get_xattr<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode_oid: u64,
name: &str,
block_size: usize,
) -> crate::Result<Option<XattrValue>> {
let xattrs = list_xattrs(reader, volume, inode_oid, block_size)?;
Ok(xattrs.into_iter().find(|x| x.name == name).map(|x| x.value))
}
pub fn decmpfs_header<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode_oid: u64,
block_size: usize,
) -> crate::Result<Option<Vec<u8>>> {
match get_xattr(reader, volume, inode_oid, XATTR_NAME_DECMPFS, block_size)? {
Some(XattrValue::Embedded(bytes)) => Ok(Some(bytes)),
Some(XattrValue::Stream { .. }) | None => Ok(None),
}
}
pub fn resource_fork<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode_oid: u64,
block_size: usize,
) -> crate::Result<Option<Vec<u8>>> {
match get_xattr(
reader,
volume,
inode_oid,
XATTR_NAME_RESOURCE_FORK,
block_size,
)? {
Some(XattrValue::Embedded(bytes)) => Ok(Some(bytes)),
Some(XattrValue::Stream { dstream_oid, size }) => {
let bytes = crate::extent::read_stream(reader, volume, dstream_oid, size, block_size)?;
Ok(Some(bytes))
}
None => Ok(None),
}
}
pub fn symlink_target<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode_oid: u64,
block_size: usize,
) -> crate::Result<Option<String>> {
match get_xattr(reader, volume, inode_oid, XATTR_NAME_SYMLINK, block_size)? {
Some(XattrValue::Embedded(bytes)) => Ok(Some(decode_cstr(&bytes))),
Some(XattrValue::Stream { .. }) | None => Ok(None),
}
}
fn decode_cstr(data: &[u8]) -> String {
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
String::from_utf8_lossy(&data[..end]).into_owned()
}