// Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::structured_values::{ NtfsFileAttributeFlags, NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; use crate::time::NtfsTime; use binread::io::{Cursor, Read, Seek}; use binread::{BinRead, BinReaderExt}; /// Size of all [`StandardInformationData`] fields plus some reserved bytes. const STANDARD_INFORMATION_SIZE_NTFS1: usize = 48; /// Size of all [`StandardInformationData`] plus [`StandardInformationDataNtfs3`] fields. const STANDARD_INFORMATION_SIZE_NTFS3: usize = 72; #[derive(BinRead, Clone, Debug)] struct StandardInformationDataNtfs1 { creation_time: NtfsTime, modification_time: NtfsTime, mft_record_modification_time: NtfsTime, access_time: NtfsTime, file_attributes: u32, } #[derive(BinRead, Clone, Debug)] struct StandardInformationDataNtfs3 { maximum_versions: u32, version: u32, class_id: u32, owner_id: u32, security_id: u32, quota_charged: u64, usn: u64, } /// Structure of a $STANDARD_INFORMATION attribute. /// /// Among other things, this is the place where the file times and "File Attributes" /// (Read-Only, Hidden, System, Archive, etc.) are stored. /// /// A $STANDARD_INFORMATION attribute is always resident. /// /// Reference: #[derive(Clone, Debug)] pub struct NtfsStandardInformation { ntfs1_data: StandardInformationDataNtfs1, ntfs3_data: Option, } impl NtfsStandardInformation { fn new(r: &mut T, position: u64, value_length: u64) -> Result where T: Read + Seek, { if value_length < STANDARD_INFORMATION_SIZE_NTFS1 as u64 { return Err(NtfsError::InvalidStructuredValueSize { position, ty: NtfsAttributeType::StandardInformation, expected: STANDARD_INFORMATION_SIZE_NTFS1 as u64, actual: value_length, }); } let ntfs1_data = r.read_le::()?; let mut ntfs3_data = None; if value_length >= STANDARD_INFORMATION_SIZE_NTFS3 as u64 { ntfs3_data = Some(r.read_le::()?); } Ok(Self { ntfs1_data, ntfs3_data, }) } /// Returns the time this file was last accessed. pub fn access_time(&self) -> NtfsTime { self.ntfs1_data.access_time } /// Returns the Class ID of the file, if stored via NTFS 3.x file information. pub fn class_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.class_id) } /// Returns the time this file was created. pub fn creation_time(&self) -> NtfsTime { self.ntfs1_data.creation_time } /// Returns flags that a user can set for a file (Read-Only, Hidden, System, Archive, etc.). /// Commonly called "File Attributes" in Windows Explorer. pub fn file_attributes(&self) -> NtfsFileAttributeFlags { NtfsFileAttributeFlags::from_bits_truncate(self.ntfs1_data.file_attributes) } /// Returns the maximum allowed versions for this file, if stored via NTFS 3.x file information. /// /// A value of zero means that versioning is disabled for this file. pub fn maximum_versions(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.maximum_versions) } /// Returns the time the MFT record of this file was last modified. pub fn mft_record_modification_time(&self) -> NtfsTime { self.ntfs1_data.mft_record_modification_time } /// Returns the time this file was last modified. pub fn modification_time(&self) -> NtfsTime { self.ntfs1_data.modification_time } /// Returns the Owner ID of the file, if stored via NTFS 3.x file information. pub fn owner_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.owner_id) } /// Returns the quota charged by this file, if stored via NTFS 3.x file information. pub fn quota_charged(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.quota_charged) } /// Returns the Security ID of the file, if stored via NTFS 3.x file information. pub fn security_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.security_id) } /// Returns the Update Sequence Number (USN) of the file, if stored via NTFS 3.x file information. pub fn usn(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.usn) } /// Returns the version of the file, if stored via NTFS 3.x file information. /// /// This will be zero if versioning is disabled for this file. pub fn version(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.version) } } impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsStandardInformation { const TY: NtfsAttributeType = NtfsAttributeType::StandardInformation; fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { let position = value.data_position().unwrap(); let value_length = value.len(); let mut value_attached = value.attach(fs); Self::new(&mut value_attached, position, value_length) } } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsStandardInformation { fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { let position = value.data_position().unwrap(); let value_length = value.len(); let mut cursor = Cursor::new(value.data()); Self::new(&mut cursor, position, value_length) } } #[cfg(test)] mod tests { use super::*; use crate::file::KnownNtfsFileRecordNumber; use crate::ntfs::Ntfs; #[test] fn test_standard_information() { let mut testfs1 = crate::helpers::tests::testfs1(); let ntfs = Ntfs::new(&mut testfs1).unwrap(); let mft = ntfs .file(&mut testfs1, KnownNtfsFileRecordNumber::MFT as u64) .unwrap(); let mut mft_attributes = mft.attributes_raw(); // Check the StandardInformation attribute of the MFT. let attribute = mft_attributes.nth(0).unwrap(); assert_eq!( attribute.ty().unwrap(), NtfsAttributeType::StandardInformation, ); assert_eq!(attribute.attribute_length(), 96); assert!(attribute.is_resident()); assert_eq!(attribute.name_length(), 0); assert_eq!(attribute.value_length(), 72); // Try to read the actual information. let _standard_info = attribute .resident_structured_value::() .unwrap(); // There are no reliable values to check here, so that's it. } }