use std::borrow::Cow;
use crate::attribute::raw::data_as_hex;
use crate::impl_serialize_for_bitflags;
use crate::utils::{read_u16, read_u32};
use bitflags::bitflags;
use byteorder::{ByteOrder, LittleEndian};
use serde::Serialize;
#[derive(Serialize, Clone, Debug)]
pub struct EaAttr<'a> {
pub entries: Vec<EaEntry<'a>>,
}
#[derive(Serialize, Clone, Debug)]
pub struct EaEntry<'a> {
pub flags: EaFlags,
pub name: Cow<'a, str>,
pub value: EaValue<'a>,
}
bitflags! {
#[derive(Default, Clone, Debug, PartialEq)]
pub struct EaFlags: u8 {
const NEED_EA = 0x80;
}
}
impl_serialize_for_bitflags! {EaFlags}
#[derive(Serialize, Clone, Debug, PartialEq)]
#[serde(tag = "type")]
pub enum EaValue<'a> {
LxInteger { value: u32 },
LxDevice { major: u32, minor: u32 },
CatalogHint { catalog: Cow<'a, str> },
Other {
#[serde(serialize_with = "data_as_hex")]
data: &'a [u8],
},
}
const ENTRY_HEADER_LEN: usize = 8;
impl<'a> EaAttr<'a> {
pub fn from_slice(value: &'a [u8]) -> Option<Self> {
let mut entries = Vec::new();
let mut offset = 0;
while offset < value.len() {
let entry = &value[offset..];
let next_entry_offset = read_u32(entry, 0)? as usize;
let flags = EaFlags::from_bits_retain(*entry.get(4)?);
let name_len = usize::from(*entry.get(5)?);
let value_len = usize::from(read_u16(entry, 6)?);
let name = entry.get(ENTRY_HEADER_LEN..ENTRY_HEADER_LEN + name_len)?;
if *entry.get(ENTRY_HEADER_LEN + name_len)? != 0 {
return None;
}
let value_start = ENTRY_HEADER_LEN + name_len + 1;
let entry_len = value_start + value_len;
let data = entry.get(value_start..entry_len)?;
let name = String::from_utf8_lossy(name);
let value = EaValue::decode(&name, data);
entries.push(EaEntry { flags, name, value });
match next_entry_offset {
0 => break,
next if next < entry_len => return None,
next => offset = offset.checked_add(next)?,
}
}
Some(EaAttr { entries })
}
}
impl<'a> EaValue<'a> {
fn decode(name: &str, data: &'a [u8]) -> Self {
match (name, data.len()) {
("$LXUID" | "$LXGID" | "$LXMOD", 4) => EaValue::LxInteger {
value: LittleEndian::read_u32(data),
},
("$LXDEV", 8) => EaValue::LxDevice {
major: LittleEndian::read_u32(&data[0..4]),
minor: LittleEndian::read_u32(&data[4..8]),
},
("$CI.CATALOGHINT", _) => Self::catalog_hint(data).unwrap_or(EaValue::Other { data }),
_ => EaValue::Other { data },
}
}
fn catalog_hint(data: &'a [u8]) -> Option<Self> {
let version = read_u16(data, 0)?;
let len = usize::from(read_u16(data, 2)?);
if version != 1 {
return None;
}
let catalog = String::from_utf8_lossy(data.get(4..4 + len)?);
Some(EaValue::CatalogHint { catalog })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(flags: u8, name: &str, value: &[u8]) -> Vec<u8> {
let mut out = vec![0; 4];
out.push(flags);
out.push(name.len() as u8);
out.extend_from_slice(&(value.len() as u16).to_le_bytes());
out.extend_from_slice(name.as_bytes());
out.push(0);
out.extend_from_slice(value);
out
}
fn link(entries: &[Vec<u8>]) -> Vec<u8> {
let mut out = Vec::new();
for entry in entries {
let start = out.len();
out.extend(entry);
out.resize(out.len().next_multiple_of(4), 0);
let len = (out.len() - start) as u32;
out[start..start + 4].copy_from_slice(&len.to_le_bytes());
}
out
}
#[test]
fn parses_wsl_entries_padded_to_four_bytes() {
let value = link(&[
entry(0, "$LXUID", &1000u32.to_le_bytes()),
entry(0, "$LXGID", &1000u32.to_le_bytes()),
entry(0, "$LXMOD", &0o100644u32.to_le_bytes()),
entry(
0,
"$LXDEV",
&[8u32.to_le_bytes(), 1u32.to_le_bytes()].concat(),
),
]);
let attribute = EaAttr::from_slice(&value).unwrap();
let values: Vec<_> = attribute
.entries
.iter()
.map(|e| (e.name.as_ref(), &e.value))
.collect();
assert_eq!(
values,
[
("$LXUID", &EaValue::LxInteger { value: 1000 }),
("$LXGID", &EaValue::LxInteger { value: 1000 }),
("$LXMOD", &EaValue::LxInteger { value: 0o100644 }),
("$LXDEV", &EaValue::LxDevice { major: 8, minor: 1 }),
]
);
}
#[test]
fn keeps_unknown_and_malformed_values_as_bytes() {
let value = link(&[
entry(0x80, "$LXUID", &[1, 2]),
entry(0, "$CI.CATALOGHINT", &[2, 0, 1, 0, b'x']),
]);
let attribute = EaAttr::from_slice(&value).unwrap();
assert_eq!(attribute.entries[0].flags, EaFlags::NEED_EA);
assert_eq!(attribute.entries[0].value, EaValue::Other { data: &[1, 2] });
assert_eq!(
attribute.entries[1].value,
EaValue::Other {
data: &[2, 0, 1, 0, b'x']
}
);
}
#[test]
fn rejects_entries_that_run_past_the_value_or_overlap() {
let value = entry(0, "$LXUID", &1000u32.to_le_bytes());
assert!(EaAttr::from_slice(&value[..value.len() - 1]).is_none());
let mut misaligned = value.clone();
misaligned[5] -= 1;
assert!(EaAttr::from_slice(&misaligned).is_none());
let mut overlapping = value.clone();
overlapping[0..4].copy_from_slice(&4u32.to_le_bytes());
overlapping.extend(&value);
assert!(EaAttr::from_slice(&overlapping).is_none());
}
}