ntfs/
ntfs.rs

1// Copyright 2021-2023 Colin Finck <colin@reactos.org>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use binrw::io::{Read, Seek, SeekFrom};
5use binrw::BinReaderExt;
6
7use crate::attribute::NtfsAttributeType;
8use crate::boot_sector::BootSector;
9use crate::error::{NtfsError, Result};
10use crate::file::{KnownNtfsFileRecordNumber, NtfsFile};
11use crate::structured_values::{NtfsVolumeInformation, NtfsVolumeName};
12use crate::traits::NtfsReadSeek;
13use crate::types::NtfsPosition;
14use crate::upcase_table::UpcaseTable;
15
16/// Root structure describing an NTFS filesystem.
17#[derive(Debug)]
18pub struct Ntfs {
19    /// The size of a single cluster, in bytes. This is usually 4096.
20    cluster_size: u32,
21    /// The size of a single sector, in bytes. This is usually 512.
22    sector_size: u16,
23    /// Size of the filesystem, in bytes.
24    size: u64,
25    /// Absolute position of the Master File Table (MFT), in bytes.
26    mft_position: NtfsPosition,
27    /// Size of a single File Record, in bytes.
28    file_record_size: u32,
29    /// Serial number of the NTFS volume.
30    serial_number: u64,
31    /// Table of Unicode uppercase characters (only required for case-insensitive comparisons).
32    upcase_table: Option<UpcaseTable>,
33}
34
35impl Ntfs {
36    /// Creates a new [`Ntfs`] object from a reader and validates its boot sector information.
37    ///
38    /// The reader must cover the entire NTFS partition, not more and not less.
39    /// It will be rewinded to the beginning before reading anything.
40    #[allow(clippy::seek_to_start_instead_of_rewind)]
41    pub fn new<T>(fs: &mut T) -> Result<Self>
42    where
43        T: Read + Seek,
44    {
45        // Read and validate the boot sector.
46        fs.seek(SeekFrom::Start(0))?;
47        let boot_sector = fs.read_le::<BootSector>()?;
48        boot_sector.validate()?;
49
50        let bpb = boot_sector.bpb();
51        let cluster_size = bpb.cluster_size()?;
52        let sector_size = bpb.sector_size()?;
53        let total_sectors = bpb.total_sectors();
54        let size = total_sectors
55            .checked_mul(sector_size as u64)
56            .ok_or(NtfsError::TotalSectorsTooBig { total_sectors })?;
57        let mft_position = NtfsPosition::none();
58        let file_record_size = bpb.file_record_size()?;
59        let serial_number = bpb.serial_number();
60        let upcase_table = None;
61
62        let mut ntfs = Self {
63            cluster_size,
64            sector_size,
65            size,
66            mft_position,
67            file_record_size,
68            serial_number,
69            upcase_table,
70        };
71        ntfs.mft_position = bpb.mft_lcn()?.position(&ntfs)?;
72
73        Ok(ntfs)
74    }
75
76    /// Returns the size of a single cluster, in bytes.
77    pub fn cluster_size(&self) -> u32 {
78        self.cluster_size
79    }
80
81    /// Returns the [`NtfsFile`] for the given NTFS File Record Number.
82    ///
83    /// The first few NTFS files have fixed indexes and contain filesystem
84    /// management information (see the [`KnownNtfsFileRecordNumber`] enum).
85    pub fn file<'n, T>(&'n self, fs: &mut T, file_record_number: u64) -> Result<NtfsFile<'n>>
86    where
87        T: Read + Seek,
88    {
89        let offset = file_record_number
90            .checked_mul(self.file_record_size as u64)
91            .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?;
92
93        // The MFT may be split into multiple data runs, referenced by its $DATA attribute.
94        // We therefore read it just like any other non-resident attribute value.
95        // However, this code assumes that the MFT does not have an Attribute List!
96        //
97        // This unwrap is safe, because `self.mft_position` has been checked in `Ntfs::new`.
98        let mft = NtfsFile::new(self, fs, self.mft_position.value().unwrap(), 0)?;
99        let mft_data_attribute =
100            mft.find_resident_attribute(NtfsAttributeType::Data, None, None)?;
101        let mut mft_data_value = mft_data_attribute.value(fs)?;
102
103        mft_data_value.seek(fs, SeekFrom::Start(offset))?;
104        let position = mft_data_value
105            .data_position()
106            .value()
107            .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?;
108
109        NtfsFile::new(self, fs, position, file_record_number)
110    }
111
112    /// Returns the size of a File Record of this NTFS filesystem, in bytes.
113    pub fn file_record_size(&self) -> u32 {
114        self.file_record_size
115    }
116
117    /// Returns the absolute byte position of the Master File Table (MFT).
118    ///
119    /// This [`NtfsPosition`] is guaranteed to be nonzero.
120    pub fn mft_position(&self) -> NtfsPosition {
121        self.mft_position
122    }
123
124    /// Reads the $UpCase file from the filesystem and stores it in this [`Ntfs`] object.
125    ///
126    /// This function only needs to be called if case-insensitive comparisons are later performed
127    /// (i.e. finding files).
128    pub fn read_upcase_table<T>(&mut self, fs: &mut T) -> Result<()>
129    where
130        T: Read + Seek,
131    {
132        let upcase_table = UpcaseTable::read(self, fs)?;
133        self.upcase_table = Some(upcase_table);
134        Ok(())
135    }
136
137    /// Returns the root directory of this NTFS volume as an [`NtfsFile`].
138    pub fn root_directory<'n, T>(&'n self, fs: &mut T) -> Result<NtfsFile<'n>>
139    where
140        T: Read + Seek,
141    {
142        self.file(fs, KnownNtfsFileRecordNumber::RootDirectory as u64)
143    }
144
145    /// Returns the size of a single sector in bytes.
146    pub fn sector_size(&self) -> u16 {
147        self.sector_size
148    }
149
150    /// Returns the 64-bit serial number of this NTFS volume.
151    pub fn serial_number(&self) -> u64 {
152        self.serial_number
153    }
154
155    /// Returns the partition size in bytes.
156    pub fn size(&self) -> u64 {
157        self.size
158    }
159
160    /// Returns the stored [`UpcaseTable`].
161    ///
162    /// # Panics
163    ///
164    /// Panics if [`read_upcase_table`][Ntfs::read_upcase_table] had not been called.
165    pub(crate) fn upcase_table(&self) -> &UpcaseTable {
166        self.upcase_table
167            .as_ref()
168            .expect("You need to call read_upcase_table first")
169    }
170
171    /// Returns an [`NtfsVolumeInformation`] containing general information about
172    /// the volume, like the NTFS version.
173    pub fn volume_info<T>(&self, fs: &mut T) -> Result<NtfsVolumeInformation>
174    where
175        T: Read + Seek,
176    {
177        let volume_file = self.file(fs, KnownNtfsFileRecordNumber::Volume as u64)?;
178        volume_file.find_resident_attribute_structured_value::<NtfsVolumeInformation>(None)
179    }
180
181    /// Returns an [`NtfsVolumeName`] to read the volume name (also called volume label)
182    /// of this NTFS volume.
183    ///
184    /// Note that a volume may also have no label, which is why the return value is further
185    /// encapsulated in an `Option`.
186    pub fn volume_name<T>(&self, fs: &mut T) -> Option<Result<NtfsVolumeName>>
187    where
188        T: Read + Seek,
189    {
190        let volume_file = iter_try!(self.file(fs, KnownNtfsFileRecordNumber::Volume as u64));
191
192        match volume_file.find_resident_attribute_structured_value::<NtfsVolumeName>(None) {
193            Ok(volume_name) => Some(Ok(volume_name)),
194            Err(NtfsError::AttributeNotFound { .. }) => None,
195            Err(e) => Some(Err(e)),
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_basics() {
206        let mut testfs1 = crate::helpers::tests::testfs1();
207        let ntfs = Ntfs::new(&mut testfs1).unwrap();
208        assert_eq!(ntfs.cluster_size(), 512);
209        assert_eq!(ntfs.sector_size(), 512);
210        assert_eq!(ntfs.size(), 2096640);
211    }
212
213    #[test]
214    fn test_volume_info() {
215        let mut testfs1 = crate::helpers::tests::testfs1();
216        let ntfs = Ntfs::new(&mut testfs1).unwrap();
217        let volume_info = ntfs.volume_info(&mut testfs1).unwrap();
218        assert_eq!(volume_info.major_version(), 3);
219        assert_eq!(volume_info.minor_version(), 1);
220    }
221
222    #[test]
223    fn test_volume_name() {
224        let mut testfs1 = crate::helpers::tests::testfs1();
225        let ntfs = Ntfs::new(&mut testfs1).unwrap();
226        let volume_name = ntfs.volume_name(&mut testfs1).unwrap().unwrap();
227        assert_eq!(volume_name.name_length(), 14);
228        assert_eq!(volume_name.name(), "mylabel");
229    }
230}