use core::mem;
use alloc::vec::Vec;
use zerocopy::byteorder::LittleEndian;
use zerocopy::{FromBytes, Immutable, KnownLayout, U16, U64, Unaligned};
use crate::error::{NtfsError, Result};
use crate::helpers::pod_from_prefix;
use crate::types::NtfsPosition;
pub(crate) const NTFS_BLOCK_SIZE: usize = 512;
const UPDATE_SEQUENCE_ENTRY_SIZE: usize = mem::size_of::<u16>();
#[derive(Clone, Copy, Debug, FromBytes, Immutable, KnownLayout, Unaligned)]
#[repr(C, packed)]
pub(crate) struct RecordHeader {
signature: [u8; 4],
update_sequence_offset: U16<LittleEndian>,
update_sequence_count: U16<LittleEndian>,
logfile_sequence_number: U64<LittleEndian>,
}
#[derive(Clone, Debug)]
pub(crate) struct Record {
data: Vec<u8>,
header: RecordHeader,
position: NtfsPosition,
}
#[derive(Debug)]
pub(crate) struct RecordRef<'r> {
data: &'r mut [u8],
header: RecordHeader,
position: NtfsPosition,
fixup_layout: Option<FixupLayout>,
}
#[derive(Clone, Copy, Debug)]
struct FixupLayout {
array_start: usize,
fixup_count: usize,
update_sequence_number: [u8; UPDATE_SEQUENCE_ENTRY_SIZE],
}
impl Record {
pub(crate) fn new(data: Vec<u8>, position: NtfsPosition) -> Result<Self> {
let header = pod_from_prefix::<RecordHeader, { mem::size_of::<RecordHeader>() }>(&data)
.ok_or(NtfsError::BufferTooSmall {
expected: mem::size_of::<RecordHeader>(),
actual: data.len(),
})?;
Ok(Self {
data,
header,
position,
})
}
pub(crate) fn data(&self) -> &[u8] {
&self.data
}
pub(crate) fn fixup(&mut self) -> Result<()> {
let layout = self.validate_fixup_layout()?;
for index in 0..layout.fixup_count {
let trailer_start = NTFS_BLOCK_SIZE * (index + 1) - UPDATE_SEQUENCE_ENTRY_SIZE;
let actual = [self.data[trailer_start], self.data[trailer_start + 1]];
if actual != layout.update_sequence_number {
return Err(NtfsError::UpdateSequenceNumberMismatch {
position: self.position + trailer_start,
expected: layout.update_sequence_number,
actual,
});
}
let replacement_start = layout.array_start + UPDATE_SEQUENCE_ENTRY_SIZE * (index + 1);
let replacement = [
self.data[replacement_start],
self.data[replacement_start + 1],
];
self.data[trailer_start..trailer_start + UPDATE_SEQUENCE_ENTRY_SIZE]
.copy_from_slice(&replacement);
}
Ok(())
}
pub(crate) fn into_data(self) -> Vec<u8> {
self.data
}
pub(crate) fn len(&self) -> u32 {
self.data.len() as u32
}
pub(crate) fn position(&self) -> NtfsPosition {
self.position
}
pub(crate) fn signature(&self) -> [u8; 4] {
self.header.signature
}
fn validate_fixup_layout(&self) -> Result<FixupLayout> {
let array_start = self.update_sequence_offset();
if !array_start.is_multiple_of(UPDATE_SEQUENCE_ENTRY_SIZE as u16) {
return Err(NtfsError::InvalidUpdateSequenceArrayOffset {
position: self.position,
offset: array_start,
});
}
let update_sequence_count = self.update_sequence_count();
let fixup_count =
update_sequence_count
.checked_sub(1)
.ok_or(NtfsError::InvalidUpdateSequenceCount {
position: self.position,
update_sequence_count,
})?;
let expected_fixup_count = self.data.len() / NTFS_BLOCK_SIZE;
if usize::from(fixup_count) != expected_fixup_count
|| !self.data.len().is_multiple_of(NTFS_BLOCK_SIZE)
{
return Err(NtfsError::InvalidUpdateSequenceCount {
position: self.position,
update_sequence_count,
});
}
let array_start = usize::from(array_start);
let array_size = usize::from(update_sequence_count)
.checked_mul(UPDATE_SEQUENCE_ENTRY_SIZE)
.ok_or(NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..usize::MAX,
size: self.data.len(),
})?;
let array_end = array_start.checked_add(array_size).ok_or(
NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..usize::MAX,
size: self.data.len(),
},
)?;
let maximum_array_end = NTFS_BLOCK_SIZE - UPDATE_SEQUENCE_ENTRY_SIZE;
if array_end > self.data.len() || array_end > maximum_array_end {
return Err(NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..array_end,
size: self.data.len().min(maximum_array_end),
});
}
Ok(FixupLayout {
array_start,
fixup_count: usize::from(fixup_count),
update_sequence_number: [self.data[array_start], self.data[array_start + 1]],
})
}
fn update_sequence_count(&self) -> u16 {
self.header.update_sequence_count.get()
}
fn update_sequence_offset(&self) -> u16 {
self.header.update_sequence_offset.get()
}
}
impl<'r> RecordRef<'r> {
pub(crate) fn new(data: &'r mut [u8], position: NtfsPosition) -> Result<Self> {
let header = pod_from_prefix::<RecordHeader, { mem::size_of::<RecordHeader>() }>(data)
.ok_or(NtfsError::BufferTooSmall {
expected: mem::size_of::<RecordHeader>(),
actual: data.len(),
})?;
Ok(Self {
data,
header,
position,
fixup_layout: None,
})
}
pub(crate) fn data(&self) -> &[u8] {
self.data
}
pub(crate) fn fixup(&mut self) -> Result<()> {
let layout = self.validate_fixup_layout()?;
for index in 0..layout.fixup_count {
let trailer_start = NTFS_BLOCK_SIZE * (index + 1) - UPDATE_SEQUENCE_ENTRY_SIZE;
let actual = [self.data[trailer_start], self.data[trailer_start + 1]];
if actual != layout.update_sequence_number {
return Err(NtfsError::UpdateSequenceNumberMismatch {
position: self.position + trailer_start,
expected: layout.update_sequence_number,
actual,
});
}
}
for index in 0..layout.fixup_count {
let trailer_start = NTFS_BLOCK_SIZE * (index + 1) - UPDATE_SEQUENCE_ENTRY_SIZE;
let replacement_start = layout.array_start + UPDATE_SEQUENCE_ENTRY_SIZE * (index + 1);
let replacement = [
self.data[replacement_start],
self.data[replacement_start + 1],
];
self.data[trailer_start..trailer_start + UPDATE_SEQUENCE_ENTRY_SIZE]
.copy_from_slice(&replacement);
}
self.fixup_layout = Some(layout);
Ok(())
}
pub(crate) fn len(&self) -> u32 {
self.data.len() as u32
}
pub(crate) fn position(&self) -> NtfsPosition {
self.position
}
pub(crate) fn signature(&self) -> [u8; 4] {
self.header.signature
}
fn validate_fixup_layout(&self) -> Result<FixupLayout> {
let array_start = self.header.update_sequence_offset.get();
if !array_start.is_multiple_of(UPDATE_SEQUENCE_ENTRY_SIZE as u16) {
return Err(NtfsError::InvalidUpdateSequenceArrayOffset {
position: self.position,
offset: array_start,
});
}
let update_sequence_count = self.header.update_sequence_count.get();
let fixup_count =
update_sequence_count
.checked_sub(1)
.ok_or(NtfsError::InvalidUpdateSequenceCount {
position: self.position,
update_sequence_count,
})?;
if usize::from(fixup_count) != self.data.len() / NTFS_BLOCK_SIZE
|| !self.data.len().is_multiple_of(NTFS_BLOCK_SIZE)
{
return Err(NtfsError::InvalidUpdateSequenceCount {
position: self.position,
update_sequence_count,
});
}
let array_start = usize::from(array_start);
let array_size = usize::from(update_sequence_count)
.checked_mul(UPDATE_SEQUENCE_ENTRY_SIZE)
.ok_or(NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..usize::MAX,
size: self.data.len(),
})?;
let array_end = array_start.checked_add(array_size).ok_or(
NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..usize::MAX,
size: self.data.len(),
},
)?;
let maximum_array_end = NTFS_BLOCK_SIZE - UPDATE_SEQUENCE_ENTRY_SIZE;
if array_end > self.data.len() || array_end > maximum_array_end {
return Err(NtfsError::InvalidUpdateSequenceArrayRange {
position: self.position,
range: array_start..array_end,
size: self.data.len().min(maximum_array_end),
});
}
Ok(FixupLayout {
array_start,
fixup_count: usize::from(fixup_count),
update_sequence_number: [self.data[array_start], self.data[array_start + 1]],
})
}
}
impl Drop for RecordRef<'_> {
fn drop(&mut self) {
let Some(layout) = self.fixup_layout else {
return;
};
for index in 0..layout.fixup_count {
let trailer_start = NTFS_BLOCK_SIZE * (index + 1) - UPDATE_SEQUENCE_ENTRY_SIZE;
self.data[trailer_start..trailer_start + UPDATE_SEQUENCE_ENTRY_SIZE]
.copy_from_slice(&layout.update_sequence_number);
}
}
}