ntfs-reader 0.4.5

Read MFT and USN journal
Documentation
// Copyright (c) 2022, Matteo Bernacchia <dev@kikijiki.com>. All rights reserved.
// This project is dual licensed under the Apache License 2.0 and the MIT license.
// See the LICENSE files in the project root for details.

use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use time::OffsetDateTime;

use crate::{
    api::{ntfs_to_unix_time, NtfsAttributeType, ROOT_RECORD},
    file::NtfsFile,
    mft::Mft,
};

const MAX_PATH_DEPTH: usize = 1024;

pub trait FileInfoCache<'a> {
    fn get(&self, number: u64) -> Option<&Path>;
    fn insert(&mut self, number: u64, path: PathBuf);
}

#[derive(Default)]
pub struct HashMapCache(pub HashMap<u64, PathBuf>);
impl FileInfoCache<'_> for HashMapCache {
    fn get(&self, number: u64) -> Option<&Path> {
        if let Some(p) = self.0.get(&number) {
            Some(p)
        } else {
            None
        }
    }

    fn insert(&mut self, number: u64, path: PathBuf) {
        self.0.insert(number, path);
    }
}

#[derive(Default)]
pub struct VecCache(pub Vec<Option<PathBuf>>);
impl FileInfoCache<'_> for VecCache {
    fn get(&self, number: u64) -> Option<&Path> {
        if (self.0.len() as u64) > number {
            if let Some(p) = &self.0[number as usize] {
                return Some(p);
            }
        }
        None
    }

    fn insert(&mut self, number: u64, path: PathBuf) {
        if (self.0.len() as u64) <= number {
            self.0.resize(number as usize + 1, None);
        }
        self.0[number as usize] = Some(path);
    }
}

pub struct FileInfo {
    pub name: String,
    pub path: PathBuf,
    pub is_directory: bool,
    pub size: u64,
    pub created: Option<OffsetDateTime>,
    pub accessed: Option<OffsetDateTime>,
    pub modified: Option<OffsetDateTime>,
}

impl FileInfo {
    pub fn new(mft: &Mft, file: &NtfsFile) -> Self {
        let mut info = Self::_new(file);
        info._compute_path(mft, file);
        info
    }

    pub fn with_cache<C: for<'a> FileInfoCache<'a>>(
        mft: &Mft,
        file: &NtfsFile,
        cache: &mut C,
    ) -> Self {
        let mut info = Self::_new(file);
        info._compute_path_with_cache(mft, file, cache);
        info
    }

    fn _new(file: &NtfsFile) -> Self {
        let mut accessed = None;
        let mut created = None;
        let mut modified = None;
        let mut size = 0u64;

        file.attributes(|att| {
            if att.header.type_id == NtfsAttributeType::StandardInformation as u32 {
                if let Some(stdinfo) = att.as_standard_info() {
                    accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
                    created = Some(ntfs_to_unix_time(stdinfo.creation_time));
                    modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
                }
            }

            if att.header.type_id == NtfsAttributeType::Data as u32 {
                if att.header.is_non_resident == 0 {
                    if let Some(header) = att.resident_header() {
                        size = header.value_length as u64;
                    }
                } else if let Some(header) = att.nonresident_header() {
                    size = header.data_size;
                }
            }
        });

        FileInfo {
            name: String::new(),
            path: PathBuf::new(),
            is_directory: file.is_directory(),
            size,
            created,
            accessed,
            modified,
        }
    }

    fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
        let mut next_parent;

        if let Some(name) = file.get_best_file_name(mft) {
            self.name = name.to_string();
            next_parent = name.parent();
        } else {
            tracing::debug!("No name for file {}", file.number);
            return;
        }

        let mut components = Vec::new();
        for _ in 0..MAX_PATH_DEPTH {
            if next_parent == ROOT_RECORD {
                break;
            }

            let cur_file = match mft.get_record(next_parent) {
                Some(f) => f,
                None => return,
            };

            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
                let cur_name = cur_name_att.to_string();
                components.push((cur_file.number(), PathBuf::from(cur_name)));
                next_parent = cur_name_att.parent();
            } else {
                return;
            }
        }

        let mut path = mft.volume.path.clone();
        for (_, comp) in components.iter().rev() {
            path.push(comp);
        }
        path.push(&self.name);

        self.path = path;
    }

    fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
        &mut self,
        mft: &Mft,
        file: &NtfsFile,
        cache: &mut C,
    ) {
        let mut next_parent;

        if let Some(name) = file.get_best_file_name(mft) {
            self.name = name.to_string();
            next_parent = name.parent();
        } else {
            return;
        }

        let mut components = Vec::new();
        let mut cached_path = None;
        for _ in 0..MAX_PATH_DEPTH {
            if next_parent == ROOT_RECORD {
                break;
            }

            // Cache hit?
            if let Some(cur_path) = cache.get(next_parent) {
                cached_path = Some(cur_path);
                break;
            }

            let cur_file = match mft.get_record(next_parent) {
                Some(f) => f,
                None => return,
            };

            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
                let cur_name = cur_name_att.to_string();
                components.push((cur_file.number(), PathBuf::from(cur_name)));
                next_parent = cur_name_att.parent();
            } else {
                return;
            }
        }

        let mut path = PathBuf::from(cached_path.unwrap_or(&mft.volume.path));

        for (number, comp) in components.iter().rev() {
            path.push(comp);
            cache.insert(*number, path.clone());
        }

        path.push(&self.name);
        cache.insert(file.number, path.clone());

        self.path = path;
    }
}