#![forbid(unsafe_code)]
use crate::error::{Ext4Error, Result};
pub const XATTR_MAGIC: u32 = 0xEA02_0000;
fn le16(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes([buf[off], buf[off + 1]])
}
fn le32(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XattrNamespace {
User,
Security,
Trusted,
System,
Unknown(u8),
}
impl From<u8> for XattrNamespace {
fn from(v: u8) -> Self {
match v {
1 => XattrNamespace::User,
2 | 3 => XattrNamespace::Security,
4 => XattrNamespace::Trusted,
6 | 7 => XattrNamespace::System,
other => XattrNamespace::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XattrBlockHeader {
pub magic: u32,
pub refcount: u32,
pub blocks: u32,
pub hash: u32,
pub checksum: u32,
}
impl XattrBlockHeader {
pub const MIN_SIZE: usize = 32;
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::MIN_SIZE {
return Err(Ext4Error::TooShort {
structure: "XattrBlockHeader",
expected: Self::MIN_SIZE,
found: buf.len(),
});
}
let magic = le32(buf, 0);
if magic != XATTR_MAGIC {
return Err(Ext4Error::CorruptMetadata {
structure: "XattrBlockHeader",
detail: format!("bad magic: 0x{magic:08X} (expected 0x{XATTR_MAGIC:08X})"),
});
}
Ok(XattrBlockHeader {
magic,
refcount: le32(buf, 4),
blocks: le32(buf, 8),
hash: le32(buf, 12),
checksum: le32(buf, 16),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XattrEntry {
pub name_index: XattrNamespace,
pub name: Vec<u8>,
pub value_offset: u16,
pub value_inum: u32,
pub value_size: u32,
pub hash: u32,
pub entry_size: usize,
}
impl XattrEntry {
const HEADER_SIZE: usize = 16;
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::HEADER_SIZE {
return Err(Ext4Error::TooShort {
structure: "XattrEntry",
expected: Self::HEADER_SIZE,
found: buf.len(),
});
}
let name_len = buf[0] as usize;
let name_index = XattrNamespace::from(buf[1]);
let value_offset = le16(buf, 2);
let value_inum = le32(buf, 4);
let value_size = le32(buf, 8);
let hash = le32(buf, 12);
let name_end = Self::HEADER_SIZE + name_len;
if buf.len() < name_end {
return Err(Ext4Error::TooShort {
structure: "XattrEntry name",
expected: name_end,
found: buf.len(),
});
}
let name = buf[Self::HEADER_SIZE..name_end].to_vec();
let entry_size = (Self::HEADER_SIZE + name_len + 3) & !3;
Ok(XattrEntry {
name_index,
name,
value_offset,
value_inum,
value_size,
hash,
entry_size,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_xattr_header() {
let mut buf = vec![0u8; 32];
buf[0..4].copy_from_slice(&0xEA02_0000u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes()); buf[8..12].copy_from_slice(&1u32.to_le_bytes()); let hdr = XattrBlockHeader::parse(&buf).unwrap();
assert_eq!(hdr.magic, XATTR_MAGIC);
assert_eq!(hdr.refcount, 1);
assert_eq!(hdr.blocks, 1);
}
#[test]
fn reject_bad_xattr_magic() {
let buf = vec![0u8; 32];
let err = XattrBlockHeader::parse(&buf).unwrap_err();
assert!(matches!(
err,
crate::error::Ext4Error::CorruptMetadata { .. }
));
}
#[test]
fn parse_xattr_entry() {
let mut buf = vec![0u8; 20];
buf[0] = 4; buf[1] = 1; buf[2..4].copy_from_slice(&100u16.to_le_bytes()); buf[8..12].copy_from_slice(&5u32.to_le_bytes()); buf[16..20].copy_from_slice(b"test"); let entry = XattrEntry::parse(&buf).unwrap();
assert_eq!(entry.name_index, XattrNamespace::User);
assert_eq!(entry.name, b"test");
assert_eq!(entry.value_offset, 100);
assert_eq!(entry.value_size, 5);
}
}