use core::cell::RefCell;
use core::cmp::Ordering;
use core::marker::PhantomData;
use alloc::vec;
use alloc::vec::Vec;
use crate::attribute::{NtfsAttributeItem, NtfsAttributeType};
use crate::error::{NtfsError, Result};
use crate::index_entry::{
IndexEntryRange, IndexNodeEntryIndex, IndexNodeEntryRanges, IndexNodeSearchResult,
NtfsIndexEntry, NtfsIndexEntryFlags,
};
use crate::index_record::validate_index_record_size;
use crate::indexes::{NtfsIndexEntryKey, NtfsIndexEntryType};
use crate::io::{Read, Seek};
use crate::structured_values::{NtfsIndexAllocation, NtfsIndexAllocationDataRuns, NtfsIndexRoot};
use crate::types::{NtfsPosition, Vcn};
const FINDER_SUBNODE_CACHE_CAPACITY: usize = 256;
const MAX_INDEX_TREE_DEPTH: usize = 64;
fn validate_index_descent(ancestor_vcns: &[Vcn], vcn: Vcn, position: NtfsPosition) -> Result<()> {
if ancestor_vcns.contains(&vcn) {
return Err(NtfsError::CyclicIndexSubnode { position, vcn });
}
if ancestor_vcns.len() + 2 > MAX_INDEX_TREE_DEPTH {
return Err(NtfsError::IndexTreeDepthExceeded {
position,
max_depth: MAX_INDEX_TREE_DEPTH,
});
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct NtfsIndex<'n, 'f, E>
where
E: NtfsIndexEntryType,
{
index_record_size: u32,
index_root_entry_ranges: IndexNodeEntryRanges<E>,
index_root_position: NtfsPosition,
index_allocation_item: Option<NtfsAttributeItem<'n, 'f>>,
index_allocation_data_runs: RefCell<Option<NtfsIndexAllocationDataRuns>>,
entry_type: PhantomData<E>,
}
impl<'n, 'f, E> NtfsIndex<'n, 'f, E>
where
E: NtfsIndexEntryType,
{
pub fn new(
index_root_item: NtfsAttributeItem<'n, 'f>,
index_allocation_item: Option<NtfsAttributeItem<'n, 'f>>,
) -> Result<Self> {
let index_root_attribute = index_root_item.to_attribute()?;
index_root_attribute.ensure_ty(NtfsAttributeType::IndexRoot)?;
let index_root = index_root_attribute.resident_structured_value::<NtfsIndexRoot>()?;
if let Some(item) = &index_allocation_item {
let attribute = item.to_attribute()?;
attribute.ensure_ty(NtfsAttributeType::IndexAllocation)?;
}
Self::from_index_root(index_root, index_allocation_item)
}
fn from_index_root(
index_root: NtfsIndexRoot<'_>,
index_allocation_item: Option<NtfsAttributeItem<'n, 'f>>,
) -> Result<Self> {
if index_allocation_item.is_none() && index_root.is_large_index() {
return Err(NtfsError::MissingIndexAllocation {
position: index_root.position(),
});
}
let index_record_size = index_root.index_record_size();
validate_index_record_size(index_record_size, index_root.position())?;
let index_root_entry_ranges = index_root.entry_ranges();
let index_root_position = index_root.position();
let entry_type = PhantomData;
Ok(Self {
index_record_size,
index_root_entry_ranges,
index_root_position,
index_allocation_item,
index_allocation_data_runs: RefCell::new(None),
entry_type,
})
}
pub(crate) fn new_with_fs_from_index_root<T>(
index_root: NtfsIndexRoot<'_>,
index_allocation_item: Option<NtfsAttributeItem<'n, 'f>>,
fs: &mut T,
) -> Result<Self>
where
T: Read + Seek,
{
let index = Self::from_index_root(index_root, index_allocation_item)?;
if let Some(item) = &index.index_allocation_item {
let attribute = item.to_attribute()?;
let allocation = attribute.structured_value::<_, NtfsIndexAllocation>(fs)?;
*index.index_allocation_data_runs.borrow_mut() = Some(allocation.into_data_runs());
}
Ok(index)
}
pub fn entries<'i>(&'i self) -> NtfsIndexEntries<'n, 'f, 'i, E> {
NtfsIndexEntries::new(self)
}
pub fn finder<'i>(&'i self) -> NtfsIndexFinder<'n, 'f, 'i, E> {
NtfsIndexFinder::new(self)
}
fn subnode_entry_ranges_with_buffer<T>(
&self,
fs: &mut T,
vcn: Vcn,
buffer: Vec<u8>,
) -> Result<IndexNodeEntryRanges<E>>
where
T: Read + Seek,
{
let item =
self.index_allocation_item
.as_ref()
.ok_or(NtfsError::MissingIndexAllocation {
position: self.index_root_position,
})?;
if let Some(data_runs) = self.index_allocation_data_runs.borrow().as_ref() {
let record =
data_runs.record_from_vcn_with_buffer(fs, self.index_record_size, vcn, buffer)?;
return Ok(record.into_entry_ranges());
}
let attribute = item.to_attribute()?;
let allocation = attribute.structured_value::<_, NtfsIndexAllocation>(fs)?;
let record = allocation.record_from_vcn(fs, self.index_record_size, vcn)?;
*self.index_allocation_data_runs.borrow_mut() = Some(allocation.data_runs().clone());
Ok(record.into_entry_ranges())
}
}
#[derive(Clone, Debug)]
pub struct NtfsIndexEntries<'n, 'f, 'i, E>
where
E: NtfsIndexEntryType,
{
index: &'i NtfsIndex<'n, 'f, E>,
inner_iterators: Vec<IndexNodeEntryRanges<E>>,
following_entries: Vec<Option<IndexEntryRange<E>>>,
ancestor_vcns: Vec<Vcn>,
buffer: Option<Vec<u8>>,
}
impl<'n, 'f, 'i, E> NtfsIndexEntries<'n, 'f, 'i, E>
where
E: NtfsIndexEntryType,
{
fn new(index: &'i NtfsIndex<'n, 'f, E>) -> Self {
let inner_iterators = vec![index.index_root_entry_ranges.clone()];
let following_entries = Vec::new();
let ancestor_vcns = Vec::new();
let buffer = None;
Self {
index,
inner_iterators,
following_entries,
ancestor_vcns,
buffer,
}
}
pub fn next<'a, T>(&'a mut self, fs: &mut T) -> Option<Result<NtfsIndexEntry<'a, E>>>
where
T: Read + Seek,
{
let entry_range = loop {
let iter = self.inner_iterators.last_mut()?;
if let Some(entry_range) = iter.next() {
let entry_range = iter_try!(entry_range);
let entry = iter_try!(entry_range.to_entry(iter.data()));
let is_last_entry = entry.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY);
if let Some(subnode_vcn) = entry.subnode_vcn() {
let subnode_vcn = iter_try!(subnode_vcn);
iter_try!(validate_index_descent(
&self.ancestor_vcns,
subnode_vcn,
entry.position(),
));
let buffer = self.buffer.take().unwrap_or_default();
let subnode_iter = iter_try!(self.index.subnode_entry_ranges_with_buffer(
fs,
subnode_vcn,
buffer
));
let following_entry = if !is_last_entry {
Some(entry_range)
} else {
None
};
self.inner_iterators.push(subnode_iter);
self.following_entries.push(following_entry);
self.ancestor_vcns.push(subnode_vcn);
} else if !is_last_entry {
break entry_range;
}
} else {
let is_root_iterator = self.inner_iterators.len() == 1;
if let Some(iter) = self.inner_iterators.pop()
&& !is_root_iterator
{
self.ancestor_vcns
.pop()
.expect("every subnode iterator has a matching ancestor VCN");
let data = iter.into_data();
if self
.buffer
.as_ref()
.is_none_or(|buffer| buffer.capacity() < data.capacity())
{
self.buffer = Some(data);
}
}
if let Some(entry_range) = self.following_entries.pop()? {
break entry_range;
}
}
};
let iter = self.inner_iterators.last().unwrap();
let entry = iter_try!(entry_range.to_entry(iter.data()));
Some(Ok(entry))
}
}
pub struct NtfsIndexFinder<'n, 'f, 'i, E>
where
E: NtfsIndexEntryType,
{
index: &'i NtfsIndex<'n, 'f, E>,
root_entry_index: Option<IndexNodeEntryIndex<E>>,
subnodes: FinderSubnodeCache<E>,
}
struct FinderCachedSubnode<E>
where
E: NtfsIndexEntryType,
{
vcn: Vcn,
entry_ranges: IndexNodeEntryRanges<E>,
entry_index: IndexNodeEntryIndex<E>,
last_used: u64,
}
struct FinderSubnodeCache<E>
where
E: NtfsIndexEntryType,
{
capacity: usize,
generation: u64,
keys: Vec<(Vcn, usize)>,
nodes: Vec<Option<FinderCachedSubnode<E>>>,
}
impl<E> FinderSubnodeCache<E>
where
E: NtfsIndexEntryType,
{
fn new(capacity: usize) -> Self {
debug_assert!(capacity > 0);
Self {
capacity,
generation: 0,
keys: Vec::new(),
nodes: Vec::new(),
}
}
fn find_slot(&self, vcn: Vcn) -> Option<usize> {
self.keys
.binary_search_by_key(&vcn, |(cached_vcn, _)| *cached_vcn)
.ok()
.map(|key_index| self.keys[key_index].1)
}
fn get(&mut self, vcn: Vcn) -> Option<usize> {
let slot = self.find_slot(vcn)?;
let generation = self.next_generation();
self.nodes[slot].as_mut().unwrap().last_used = generation;
Some(slot)
}
fn node(&self, slot: usize) -> &FinderCachedSubnode<E> {
self.nodes[slot].as_ref().unwrap()
}
fn prepare_insert(&mut self) -> (usize, Vec<u8>) {
if self.keys.len() == self.capacity {
let slot = self
.nodes
.iter()
.enumerate()
.filter_map(|(slot, node)| node.as_ref().map(|node| (slot, node.last_used)))
.min_by_key(|(_, last_used)| *last_used)
.unwrap()
.0;
let node = self.nodes[slot].take().unwrap();
let key_index = self
.keys
.binary_search_by_key(&node.vcn, |(cached_vcn, _)| *cached_vcn)
.unwrap();
self.keys.remove(key_index);
return (slot, node.entry_ranges.into_data());
}
if let Some(slot) = self.nodes.iter().position(Option::is_none) {
return (slot, Vec::new());
}
let slot = self.nodes.len();
self.nodes.push(None);
(slot, Vec::new())
}
fn insert(
&mut self,
vcn: Vcn,
slot: usize,
entry_ranges: IndexNodeEntryRanges<E>,
entry_index: IndexNodeEntryIndex<E>,
) {
debug_assert!(self.keys.len() < self.capacity);
debug_assert!(self.nodes[slot].is_none());
let key_index = self
.keys
.binary_search_by_key(&vcn, |(cached_vcn, _)| *cached_vcn)
.unwrap_err();
self.keys.insert(key_index, (vcn, slot));
let last_used = self.next_generation();
self.nodes[slot] = Some(FinderCachedSubnode {
vcn,
entry_ranges,
entry_index,
last_used,
});
}
fn next_generation(&mut self) -> u64 {
if self.generation == u64::MAX {
for node in self.nodes.iter_mut().flatten() {
node.last_used /= 2;
}
self.generation /= 2;
}
self.generation += 1;
self.generation
}
}
impl<'n, 'f, 'i, E> NtfsIndexFinder<'n, 'f, 'i, E>
where
E: NtfsIndexEntryType,
{
fn new(index: &'i NtfsIndex<'n, 'f, E>) -> Self {
let root_entry_index = None;
let subnodes = FinderSubnodeCache::new(FINDER_SUBNODE_CACHE_CAPACITY);
Self {
index,
root_entry_index,
subnodes,
}
}
pub fn find<'a, T, F>(&'a mut self, fs: &mut T, cmp: F) -> Option<Result<NtfsIndexEntry<'a, E>>>
where
T: Read + Seek,
F: Fn(&E::KeyType) -> Ordering,
{
self.find_by(fs, |slice, position| {
let key = E::KeyType::key_from_slice(slice, position)?;
Ok(cmp(&key))
})
}
pub(crate) fn find_by<'a, T, F>(
&'a mut self,
fs: &mut T,
cmp: F,
) -> Option<Result<NtfsIndexEntry<'a, E>>>
where
T: Read + Seek,
F: Fn(&[u8], NtfsPosition) -> Result<Ordering>,
{
if self.root_entry_index.is_none() {
self.root_entry_index =
Some(iter_try!(self.index.index_root_entry_ranges.entry_index()));
}
let mut subnode_index: Option<usize> = None;
let mut ancestor_vcns = Vec::new();
loop {
let search_result = if let Some(index) = subnode_index {
let subnode = self.subnodes.node(index);
subnode
.entry_index
.find_by(subnode.entry_ranges.data(), &cmp)
} else {
self.root_entry_index
.as_ref()
.unwrap()
.find_by(self.index.index_root_entry_ranges.data(), &cmp)
};
match iter_try!(search_result?) {
IndexNodeSearchResult::Found(entry_range) => {
let data = if let Some(index) = subnode_index {
self.subnodes.node(index).entry_ranges.data()
} else {
self.index.index_root_entry_ranges.data()
};
return Some(entry_range.to_entry(data));
}
IndexNodeSearchResult::Subnode { vcn, position } => {
iter_try!(validate_index_descent(&ancestor_vcns, vcn, position));
if let Some(slot) = self.subnodes.get(vcn) {
subnode_index = Some(slot);
} else {
let (slot, buffer) = self.subnodes.prepare_insert();
let subnode =
iter_try!(self.index.subnode_entry_ranges_with_buffer(fs, vcn, buffer));
let entry_index = iter_try!(subnode.entry_index());
self.subnodes.insert(vcn, slot, subnode, entry_index);
subnode_index = Some(slot);
}
ancestor_vcns.push(vcn);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexes::NtfsFileNameIndex;
#[test]
fn index_descent_rejects_cycles() {
let ancestors = [Vcn::from(3), Vcn::from(8)];
let error =
validate_index_descent(&ancestors, Vcn::from(3), NtfsPosition::none()).unwrap_err();
assert!(matches!(
error,
NtfsError::CyclicIndexSubnode { vcn, .. } if vcn == Vcn::from(3)
));
}
#[test]
fn index_descent_rejects_excessive_depth() {
let ancestors: Vec<_> = (0..MAX_INDEX_TREE_DEPTH - 1)
.map(|vcn| Vcn::from(vcn as i64))
.collect();
let error = validate_index_descent(
&ancestors,
Vcn::from(MAX_INDEX_TREE_DEPTH as i64),
NtfsPosition::none(),
)
.unwrap_err();
assert!(matches!(
error,
NtfsError::IndexTreeDepthExceeded {
max_depth: MAX_INDEX_TREE_DEPTH,
..
}
));
}
fn empty_subnode(
data: Vec<u8>,
) -> (
IndexNodeEntryRanges<NtfsFileNameIndex>,
IndexNodeEntryIndex<NtfsFileNameIndex>,
) {
let entry_ranges = IndexNodeEntryRanges::new(data, 0..0, NtfsPosition::none());
let entry_index = entry_ranges.entry_index().unwrap();
(entry_ranges, entry_index)
}
#[test]
fn finder_cache_evicts_lru_node_and_reuses_its_buffer() {
let mut cache = FinderSubnodeCache::new(3);
for vcn in 0..3 {
let (entry_ranges, entry_index) = empty_subnode(Vec::with_capacity(64 + vcn as usize));
let (slot, buffer) = cache.prepare_insert();
assert!(buffer.is_empty());
cache.insert(Vcn::from(vcn), slot, entry_ranges, entry_index);
}
assert!(cache.get(Vcn::from(0)).is_some());
let victim_slot = cache.find_slot(Vcn::from(1)).unwrap();
let victim_pointer = cache.node(victim_slot).entry_ranges.data().as_ptr();
let (slot, buffer) = cache.prepare_insert();
assert_eq!(slot, victim_slot);
assert_eq!(buffer.as_ptr(), victim_pointer);
assert!(cache.find_slot(Vcn::from(0)).is_some());
assert!(cache.find_slot(Vcn::from(1)).is_none());
let (entry_ranges, entry_index) = empty_subnode(buffer);
cache.insert(Vcn::from(3), slot, entry_ranges, entry_index);
assert_eq!(
cache
.node(cache.find_slot(Vcn::from(3)).unwrap())
.entry_ranges
.data()
.as_ptr(),
victim_pointer
);
}
}