use crate::Result;
pub(crate) const FILE_RECORD_MAGIC: &[u8; 4] = b"FILE";
pub(crate) const BAAD_RECORD_MAGIC: &[u8; 4] = b"BAAD";
pub fn apply_fixup(buf: &mut [u8], sector_size: usize) -> Result<()> {
if buf.len() < 8 {
return Err(crate::Error::InvalidImage(
"ntfs: record too small for fixup header".into(),
));
}
let usa_offset = u16::from_le_bytes([buf[4], buf[5]]) as usize;
let usa_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
if usa_size < 2 {
return Err(crate::Error::InvalidImage(
"ntfs: USA size < 2 (no sectors)".into(),
));
}
let usa_bytes = usa_size * 2;
if usa_offset + usa_bytes > buf.len() {
return Err(crate::Error::InvalidImage(
"ntfs: USA extends past record".into(),
));
}
let sectors = usa_size - 1;
if buf.len() < sectors * sector_size {
return Err(crate::Error::InvalidImage(
"ntfs: record shorter than USA-covered sectors".into(),
));
}
let usn = [buf[usa_offset], buf[usa_offset + 1]];
for i in 0..sectors {
let tail_off = (i + 1) * sector_size - 2;
if buf[tail_off] != usn[0] || buf[tail_off + 1] != usn[1] {
return Err(crate::Error::InvalidImage(format!(
"ntfs: USA mismatch on sector {i} (torn write?)"
)));
}
let orig = [buf[usa_offset + 2 + i * 2], buf[usa_offset + 2 + i * 2 + 1]];
buf[tail_off] = orig[0];
buf[tail_off + 1] = orig[1];
}
Ok(())
}
pub fn install_fixup(buf: &mut [u8], sector_size: usize, usn: u16) {
let usa_offset = u16::from_le_bytes([buf[4], buf[5]]) as usize;
let usa_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
let sectors = usa_size - 1;
buf[usa_offset] = usn as u8;
buf[usa_offset + 1] = (usn >> 8) as u8;
for i in 0..sectors {
let tail_off = (i + 1) * sector_size - 2;
let orig = [buf[tail_off], buf[tail_off + 1]];
buf[usa_offset + 2 + i * 2] = orig[0];
buf[usa_offset + 2 + i * 2 + 1] = orig[1];
buf[tail_off] = usn as u8;
buf[tail_off + 1] = (usn >> 8) as u8;
}
}
#[derive(Debug, Clone)]
pub struct RecordHeader {
pub first_attribute_offset: u16,
pub flags: u16,
pub bytes_in_use: u32,
pub bytes_allocated: u32,
pub base_record_ref: u64,
}
impl RecordHeader {
pub const FLAG_IN_USE: u16 = 0x0001;
pub const FLAG_DIRECTORY: u16 = 0x0002;
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 56 {
return Err(crate::Error::InvalidImage(
"ntfs: record buffer too small for header".into(),
));
}
if &buf[0..4] == BAAD_RECORD_MAGIC {
return Err(crate::Error::InvalidImage(
"ntfs: encountered BAAD record".into(),
));
}
if &buf[0..4] != FILE_RECORD_MAGIC {
return Err(crate::Error::InvalidImage(format!(
"ntfs: bad record magic {:02x?}",
&buf[0..4]
)));
}
let first_attribute_offset = u16::from_le_bytes([buf[0x14], buf[0x15]]);
let flags = u16::from_le_bytes([buf[0x16], buf[0x17]]);
let bytes_in_use = u32::from_le_bytes(buf[0x18..0x1C].try_into().unwrap());
let bytes_allocated = u32::from_le_bytes(buf[0x1C..0x20].try_into().unwrap());
let base_record_ref = u64::from_le_bytes(buf[0x20..0x28].try_into().unwrap());
Ok(Self {
first_attribute_offset,
flags,
bytes_in_use,
bytes_allocated,
base_record_ref,
})
}
pub fn is_in_use(&self) -> bool {
self.flags & Self::FLAG_IN_USE != 0
}
pub fn is_directory(&self) -> bool {
self.flags & Self::FLAG_DIRECTORY != 0
}
}