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::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;
#[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();
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>>>,
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 buffer = None;
Self {
index,
inner_iterators,
following_entries,
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);
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);
} 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
{
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;
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) => {
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);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexes::NtfsFileNameIndex;
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
);
}
}