pub const MAX_NM_LEN: usize = 4096;
pub type ShortTimestamp = [u8; 7];
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RockRidgeTimestamps {
pub creation: Option<ShortTimestamp>,
pub modify: Option<ShortTimestamp>,
pub access: Option<ShortTimestamp>,
pub attributes: Option<ShortTimestamp>,
pub backup: Option<ShortTimestamp>,
pub expiration: Option<ShortTimestamp>,
pub effective: Option<ShortTimestamp>,
}
pub fn timestamps(system_use: &[u8]) -> Option<RockRidgeTimestamps> {
let mut offset = 0;
while offset + 3 <= system_use.len() {
let sig = &system_use[offset..offset + 2];
let len = system_use[offset + 2] as usize;
if len < 3 || offset + len > system_use.len() {
break;
}
if sig == b"TF" && len >= 5 {
let flags = system_use[offset + 4];
if flags & 0x80 != 0 {
offset += len.max(1);
continue;
}
let mut result = RockRidgeTimestamps::default();
let mut pos = offset + 5;
for bit in 0..7u8 {
if flags & (1 << bit) != 0 {
if pos + 7 > offset + len {
break;
}
let ts: ShortTimestamp = system_use[pos..pos + 7].try_into().unwrap();
match bit {
0 => result.creation = Some(ts),
1 => result.modify = Some(ts),
2 => result.access = Some(ts),
3 => result.attributes = Some(ts),
4 => result.backup = Some(ts),
5 => result.expiration = Some(ts),
6 => result.effective = Some(ts),
_ => {}
}
pos += 7;
}
}
return Some(result);
}
offset += len.max(1);
}
None
}
pub fn symlink_target(system_use: &[u8]) -> Option<String> {
const COMP_CONTINUE: u8 = 0x01;
const COMP_CURRENT: u8 = 0x02;
const COMP_PARENT: u8 = 0x04;
const COMP_ROOT: u8 = 0x08;
let mut path = String::new();
let mut found = false;
let mut needs_sep = false;
let mut in_cont = false;
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"SL" && len >= 5 {
found = true;
let comp_area = &system_use[off + 5..off + len];
let mut ci = 0;
while ci + 2 <= comp_area.len() {
let cf = comp_area[ci];
let cl = comp_area[ci + 1] as usize;
let cd = if ci + 2 + cl <= comp_area.len() {
&comp_area[ci + 2..ci + 2 + cl]
} else {
break;
};
if !in_cont {
if cf & COMP_ROOT != 0 {
path.push('/');
needs_sep = false; } else {
if needs_sep {
path.push('/');
}
needs_sep = true;
if cf & COMP_PARENT != 0 {
path.push_str("..");
} else if cf & COMP_CURRENT != 0 {
path.push('.');
} else {
path.push_str(std::str::from_utf8(cd).unwrap_or(""));
}
}
} else {
path.push_str(std::str::from_utf8(cd).unwrap_or(""));
}
in_cont = cf & COMP_CONTINUE != 0;
ci += 2 + cl;
}
}
off += len.max(1);
}
if found {
Some(path)
} else {
None
}
}
pub fn child_link(system_use: &[u8]) -> Option<u32> {
lba_entry(system_use, b"CL")
}
pub fn parent_link(system_use: &[u8]) -> Option<u32> {
lba_entry(system_use, b"PL")
}
pub fn is_relocated(system_use: &[u8]) -> bool {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"RE" {
return true;
}
off += len.max(1);
}
false
}
fn lba_entry(system_use: &[u8], target: &[u8; 2]) -> Option<u32> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if &sig[..2] == target && len >= 12 {
return Some(u32::from_le_bytes(system_use[off + 4..off + 8].try_into().unwrap()));
}
off += len.max(1);
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PosixAttrs {
pub mode: u32,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub ino: Option<u64>,
}
pub fn posix_attrs(system_use: &[u8]) -> Option<PosixAttrs> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"PX" && len >= 36 {
let le32 = |i: usize| u32::from_le_bytes(system_use[i..i + 4].try_into().unwrap());
return Some(PosixAttrs {
mode: le32(off + 4),
nlink: le32(off + 12),
uid: le32(off + 20),
gid: le32(off + 28),
ino: if len >= 44 { Some(le32(off + 36) as u64) } else { None },
});
}
off += len.max(1);
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnyTimestamp {
Short([u8; 7]),
Long([u8; 17]),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RockRidgeAnyTimestamps {
pub creation: Option<AnyTimestamp>,
pub modify: Option<AnyTimestamp>,
pub access: Option<AnyTimestamp>,
pub attributes: Option<AnyTimestamp>,
pub backup: Option<AnyTimestamp>,
pub expiration: Option<AnyTimestamp>,
pub effective: Option<AnyTimestamp>,
}
pub fn timestamps_any(system_use: &[u8]) -> Option<RockRidgeAnyTimestamps> {
let mut offset = 0;
while offset + 3 <= system_use.len() {
let sig = &system_use[offset..offset + 2];
let len = system_use[offset + 2] as usize;
if len < 3 || offset + len > system_use.len() {
break;
}
if sig == b"TF" && len >= 5 {
let flags = system_use[offset + 4];
let long_fmt = flags & 0x80 != 0;
let ts_size = if long_fmt { 17 } else { 7 };
let mut result = RockRidgeAnyTimestamps::default();
let mut pos = offset + 5;
for bit in 0..7u8 {
if flags & (1 << bit) != 0 {
if pos + ts_size > offset + len {
break;
}
let slot: &mut Option<AnyTimestamp> = match bit {
0 => &mut result.creation,
1 => &mut result.modify,
2 => &mut result.access,
3 => &mut result.attributes,
4 => &mut result.backup,
5 => &mut result.expiration,
6 => &mut result.effective,
_ => unreachable!(),
};
*slot = Some(if long_fmt {
AnyTimestamp::Long(system_use[pos..pos + 17].try_into().unwrap())
} else {
AnyTimestamp::Short(system_use[pos..pos + 7].try_into().unwrap())
});
pos += ts_size;
}
}
return Some(result);
}
offset += len.max(1);
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ContinuationArea {
pub lba: u32,
pub offset: u32,
pub len: u32,
}
pub fn continuation(system_use: &[u8]) -> Option<ContinuationArea> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"CE" && len >= 28 {
let le32 = |i: usize| u32::from_le_bytes(system_use[i..i + 4].try_into().unwrap());
return Some(ContinuationArea {
lba: le32(off + 4),
offset: le32(off + 12),
len: le32(off + 20),
});
}
off += len.max(1);
}
None
}
pub fn alternate_name(system_use: &[u8]) -> Option<String> {
let mut name = String::new();
let mut offset = 0;
while offset + 3 <= system_use.len() {
let sig = &system_use[offset..offset + 2];
let len = system_use[offset + 2] as usize;
if len < 3 || offset + len > system_use.len() {
break;
}
if sig == b"NM" && len >= 6 {
let flags = system_use[offset + 4];
let component = &system_use[offset + 5..offset + len];
let fragment = std::str::from_utf8(component).unwrap_or("");
let remaining = MAX_NM_LEN.saturating_sub(name.len());
if remaining > 0 {
let take = fragment.len().min(remaining);
name.push_str(&fragment[..take]);
}
if flags & 0x01 == 0 {
return if name.is_empty() { None } else { Some(name) };
}
}
offset += len.max(1);
}
if name.is_empty() {
None
} else {
Some(name)
}
}
pub fn posix_mode(system_use: &[u8]) -> Option<u32> {
posix_attrs(system_use).map(|a| a.mode)
}
pub fn has_sp_entry(system_use: &[u8]) -> bool {
system_use.windows(7).any(|w| w[0..2] == *b"SP" && w[4..6] == [0xBE, 0xEF])
}
pub fn sp_skip(system_use: &[u8]) -> usize {
system_use
.windows(7)
.find(|w| w[0..2] == *b"SP" && w[4..6] == [0xBE, 0xEF])
.map(|w| w[6] as usize)
.unwrap_or(0)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExtensionsReference {
pub id: String,
pub descriptor: String,
pub source: String,
pub version: u8,
}
pub fn extensions_reference(system_use: &[u8]) -> Option<ExtensionsReference> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"ER" && len >= 8 {
let len_id = system_use[off + 4] as usize;
let len_des = system_use[off + 5] as usize;
let len_src = system_use[off + 6] as usize;
let ext_ver = system_use[off + 7];
let mut p = off + 8;
let take = |start: usize, n: usize| -> String {
let end = (start + n).min(off + len);
if start >= end {
String::new()
} else {
String::from_utf8_lossy(&system_use[start..end]).trim_end().to_string()
}
};
let id = take(p, len_id);
p += len_id;
let descriptor = take(p, len_des);
p += len_des;
let source = take(p, len_src);
return Some(ExtensionsReference { id, descriptor, source, version: ext_ver });
}
off += len.max(1);
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PosixDevice {
pub dev_high: u32,
pub dev_low: u32,
}
impl PosixDevice {
#[must_use]
pub fn dev(self) -> u64 {
(u64::from(self.dev_high) << 32) | u64::from(self.dev_low)
}
}
pub fn posix_device(system_use: &[u8]) -> Option<PosixDevice> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"PN" && len >= 20 {
let le32 = |i: usize| u32::from_le_bytes(system_use[i..i + 4].try_into().unwrap());
return Some(PosixDevice { dev_high: le32(off + 4), dev_low: le32(off + 12) });
}
off += len.max(1);
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SparseFile {
pub virtual_size: u64,
pub table_depth: u8,
}
pub fn sparse_file(system_use: &[u8]) -> Option<SparseFile> {
let mut off = 0;
while off + 3 <= system_use.len() {
let sig = &system_use[off..off + 2];
let len = system_use[off + 2] as usize;
if len < 3 || off + len > system_use.len() {
break;
}
if sig == b"SF" && len >= 21 {
let le32 = |i: usize| u32::from_le_bytes(system_use[i..i + 4].try_into().unwrap());
let high = u64::from(le32(off + 4));
let low = u64::from(le32(off + 12));
return Some(SparseFile {
virtual_size: (high << 32) | low,
table_depth: system_use[off + 20],
});
}
off += len.max(1);
}
None
}