fast_ntfs 1.0.1

Forked a low-level NTFS filesystem library
Documentation
// Copyright 2021-2026 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0

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;

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(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 {
        // A record is never larger than a u32.
        // Usually, it shouldn't even exceed a u16, but our code could handle that.
        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()
    }
}