use crate::Result;
use crate::block::BlockDevice;
use super::SECTOR_SIZE;
use super::directory::DirRecord;
use super::vd::PrimaryVolumeDescriptor;
#[derive(Debug, Default, Clone)]
pub struct RockRidgeAttrs {
pub alternate_name: Option<String>,
pub symlink_target: Option<String>,
pub mode: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub mtime: Option<i64>,
}
pub fn parse_system_use(dev: &mut dyn BlockDevice, sua: &[u8]) -> Option<RockRidgeAttrs> {
let mut attrs = RockRidgeAttrs::default();
let mut name = String::new();
let mut symlink = String::new();
let mut had_any = false;
parse_block(sua, dev, &mut attrs, &mut name, &mut symlink, &mut had_any);
if !had_any {
return None;
}
if !name.is_empty() {
attrs.alternate_name = Some(name);
}
if !symlink.is_empty() {
attrs.symlink_target = Some(symlink);
}
Some(attrs)
}
pub fn root_has_rr(dev: &mut dyn BlockDevice, pvd: &PrimaryVolumeDescriptor) -> Result<bool> {
let mut buf = vec![0u8; SECTOR_SIZE as usize];
dev.read_at(
u64::from(pvd.root.extent_lba) * u64::from(SECTOR_SIZE),
&mut buf,
)?;
if buf.is_empty() || buf[0] == 0 {
return Ok(false);
}
let len_dr = buf[0] as usize;
if len_dr < 33 || len_dr > buf.len() {
return Ok(false);
}
let dot = DirRecord::decode(&buf[..len_dr])?;
for window in dot.system_use.windows(4) {
if &window[..2] == b"SP" && window[2] >= 7 {
let off = dot.system_use.as_ptr() as usize - dot.system_use.as_ptr() as usize; let _ = off;
for i in 0..(dot.system_use.len().saturating_sub(7)) {
if &dot.system_use[i..i + 2] == b"SP"
&& dot.system_use.get(i + 4) == Some(&0xBE)
&& dot.system_use.get(i + 5) == Some(&0xEF)
{
return Ok(true);
}
}
}
}
Ok(false)
}
fn parse_block(
sua: &[u8],
dev: &mut dyn BlockDevice,
attrs: &mut RockRidgeAttrs,
name_acc: &mut String,
symlink_acc: &mut String,
any: &mut bool,
) {
let mut i = 0;
while i + 4 <= sua.len() {
let sig = &sua[i..i + 2];
let len = sua[i + 2] as usize;
if len < 4 || i + len > sua.len() {
break;
}
let body = &sua[i + 4..i + len];
match sig {
b"NM" => {
if let Some(payload) = body.get(1..) {
let cont_flag = body.first().copied().unwrap_or(0);
name_acc.push_str(&String::from_utf8_lossy(payload));
if cont_flag & 0x01 == 0 {
}
*any = true;
}
}
b"PX" if body.len() >= 32 => {
if let Ok(mode) = super::vd::decode_both_endian_u32(&body[0..8], "PX.mode") {
attrs.mode = Some(mode);
}
if let Ok(uid) = super::vd::decode_both_endian_u32(&body[16..24], "PX.uid") {
attrs.uid = Some(uid);
}
if let Ok(gid) = super::vd::decode_both_endian_u32(&body[24..32], "PX.gid") {
attrs.gid = Some(gid);
}
*any = true;
}
b"SL" => {
let cont = body.first().copied().unwrap_or(0) & 0x01;
let mut j = 1;
while j + 2 <= body.len() {
let comp_flags = body[j];
let comp_len = body[j + 1] as usize;
if j + 2 + comp_len > body.len() {
break;
}
let comp_bytes = &body[j + 2..j + 2 + comp_len];
if !symlink_acc.is_empty() && (comp_flags & 0x08) == 0 {
symlink_acc.push('/');
}
if comp_flags & 0x02 != 0 {
symlink_acc.push('.');
} else if comp_flags & 0x04 != 0 {
symlink_acc.push_str("..");
} else if comp_flags & 0x08 != 0 {
symlink_acc.push('/');
} else {
symlink_acc.push_str(&String::from_utf8_lossy(comp_bytes));
}
j += 2 + comp_len;
}
let _ = cont;
*any = true;
}
b"CE" if body.len() >= 24 => {
let ce_lba = super::vd::decode_both_endian_u32(&body[0..8], "CE.lba").ok();
let ce_off = super::vd::decode_both_endian_u32(&body[8..16], "CE.offset").ok();
let ce_len = super::vd::decode_both_endian_u32(&body[16..24], "CE.len").ok();
if let (Some(lba), Some(off), Some(clen)) = (ce_lba, ce_off, ce_len) {
let mut buf = vec![0u8; clen as usize];
let abs = u64::from(lba) * u64::from(SECTOR_SIZE) + u64::from(off);
if dev.read_at(abs, &mut buf).is_ok() {
parse_block(&buf, dev, attrs, name_acc, symlink_acc, any);
}
}
}
b"TF" => {
let flags = body.first().copied().unwrap_or(0);
let long = flags & 0x80 != 0;
let entry_size = if long { 17 } else { 7 };
let bits = flags & 0x7F;
let mut k = 1;
let mut bit = 0u8;
while bit < 7 {
if bits & (1 << bit) != 0 && k + entry_size <= body.len() {
if bit == 1 {
attrs.mtime = Some(decode_iso_short_time(&body[k..k + entry_size]));
}
k += entry_size;
}
bit += 1;
}
*any = true;
}
b"SP" | b"RR" | b"ER" | b"ES" | b"PD" | b"ST" | b"PN" | b"CL" | b"PL" | b"SF" => {
*any = true;
}
_ => { }
}
i += len;
}
}
fn decode_iso_short_time(buf: &[u8]) -> i64 {
if buf.len() < 7 {
return 0;
}
let years = i64::from(buf[0]);
let month = i64::from(buf[1]);
let day = i64::from(buf[2]);
let hour = i64::from(buf[3]);
let minute = i64::from(buf[4]);
let second = i64::from(buf[5]);
let gmt_off_qh = i8::from_le_bytes([buf[6]]) as i64;
let y = 1900 + years - if month <= 2 { 1 } else { 0 };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let m_shift = if month > 2 { month - 3 } else { month + 9 };
let doy = (153 * m_shift + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146097 + doe - 719468;
days * 86400 + hour * 3600 + minute * 60 + second - gmt_off_qh * 15 * 60
}