Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ntfs.rs « src - github.com/windirstat/ntfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: ea56716ee974f86bfa303611fba9a4d073f26d94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::attribute::NtfsAttributeType;
use crate::boot_sector::BootSector;
use crate::error::{NtfsError, Result};
use crate::file::{KnownNtfsFileRecordNumber, NtfsFile};
use crate::structured_values::{NtfsVolumeInformation, NtfsVolumeName};
use crate::traits::NtfsReadSeek;
use crate::upcase_table::UpcaseTable;
use binread::io::{Read, Seek, SeekFrom};
use binread::BinReaderExt;

/// Root structure describing an NTFS filesystem.
#[derive(Debug)]
pub struct Ntfs {
    /// The size of a single cluster, in bytes. This is usually 4096.
    cluster_size: u32,
    /// The size of a single sector, in bytes. This is usually 512.
    sector_size: u16,
    /// Size of the filesystem, in bytes.
    size: u64,
    /// Absolute position of the Master File Table (MFT), in bytes.
    mft_position: u64,
    /// Size of a single File Record, in bytes.
    file_record_size: u32,
    /// Serial number of the NTFS volume.
    serial_number: u64,
    /// Table of Unicode uppercase characters (only required for case-insensitive comparisons).
    upcase_table: Option<UpcaseTable>,
}

impl Ntfs {
    /// Creates a new [`Ntfs`] object from a reader and validates its boot sector information.
    ///
    /// The reader must cover the entire NTFS partition, not more and not less.
    /// It will be rewinded to the beginning before reading anything.
    pub fn new<T>(fs: &mut T) -> Result<Self>
    where
        T: Read + Seek,
    {
        // Read and validate the boot sector.
        fs.seek(SeekFrom::Start(0))?;
        let boot_sector = fs.read_le::<BootSector>()?;
        boot_sector.validate()?;

        let bpb = boot_sector.bpb();
        let cluster_size = bpb.cluster_size()?;
        let sector_size = bpb.sector_size();
        let size = bpb.total_sectors() * sector_size as u64;
        let mft_position = 0;
        let file_record_size = bpb.file_record_size()?;
        let serial_number = bpb.serial_number();
        let upcase_table = None;

        let mut ntfs = Self {
            cluster_size,
            sector_size,
            size,
            mft_position,
            file_record_size,
            serial_number,
            upcase_table,
        };
        ntfs.mft_position = bpb.mft_lcn().position(&ntfs)?;

        Ok(ntfs)
    }

    /// Returns the size of a single cluster, in bytes.
    pub fn cluster_size(&self) -> u32 {
        self.cluster_size
    }

    /// Returns the [`NtfsFile`] for the given NTFS File Record Number.
    ///
    /// The first few NTFS files have fixed indexes and contain filesystem
    /// management information (see the [`KnownNtfsFileRecordNumber`] enum).
    pub fn file<'n, T>(&'n self, fs: &mut T, file_record_number: u64) -> Result<NtfsFile<'n>>
    where
        T: Read + Seek,
    {
        let offset = file_record_number
            .checked_mul(self.file_record_size as u64)
            .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?;

        // The MFT may be split into multiple data runs, referenced by its $DATA attribute.
        // We therefore read it just like any other non-resident attribute value.
        // However, this code assumes that the MFT does not have an Attribute List!
        let mft = NtfsFile::new(&self, fs, self.mft_position, 0)?;
        let mft_data_attribute = mft
            .attributes_raw()
            .find(|attribute| {
                attribute
                    .ty()
                    .map(|ty| ty == NtfsAttributeType::Data)
                    .unwrap_or(false)
            })
            .ok_or(NtfsError::AttributeNotFound {
                position: self.mft_position,
                ty: NtfsAttributeType::Data,
            })?;
        let mut mft_data_value = mft_data_attribute.value()?;

        mft_data_value.seek(fs, SeekFrom::Start(offset))?;
        let position = mft_data_value
            .data_position()
            .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?;

        NtfsFile::new(&self, fs, position, file_record_number)
    }

    /// Returns the size of a File Record of this NTFS filesystem, in bytes.
    pub fn file_record_size(&self) -> u32 {
        self.file_record_size
    }

    /// Returns the absolute byte position of the Master File Table (MFT).
    pub fn mft_position(&self) -> u64 {
        self.mft_position
    }

    /// Reads the $UpCase file from the filesystem and stores it in this [`Ntfs`] object.
    ///
    /// This function only needs to be called if case-insensitive comparisons are later performed
    /// (i.e. finding files).
    pub fn read_upcase_table<T>(&mut self, fs: &mut T) -> Result<()>
    where
        T: Read + Seek,
    {
        let upcase_table = UpcaseTable::read(self, fs)?;
        self.upcase_table = Some(upcase_table);
        Ok(())
    }

    /// Returns the root directory of this NTFS volume as an [`NtfsFile`].
    pub fn root_directory<'n, T>(&'n self, fs: &mut T) -> Result<NtfsFile<'n>>
    where
        T: Read + Seek,
    {
        self.file(fs, KnownNtfsFileRecordNumber::RootDirectory as u64)
    }

    /// Returns the size of a single sector in bytes.
    pub fn sector_size(&self) -> u16 {
        self.sector_size
    }

    /// Returns the 64-bit serial number of this NTFS volume.
    pub fn serial_number(&self) -> u64 {
        self.serial_number
    }

    /// Returns the partition size in bytes.
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Returns the stored [`UpcaseTable`].
    ///
    /// # Panics
    ///
    /// Panics if [`read_upcase_table`][Ntfs::read_upcase_table] had not been called.
    pub(crate) fn upcase_table(&self) -> &UpcaseTable {
        self.upcase_table
            .as_ref()
            .expect("You need to call read_upcase_table first")
    }

    /// Returns an [`NtfsVolumeInformation`] containing general information about
    /// the volume, like the NTFS version.
    pub fn volume_info<T>(&self, fs: &mut T) -> Result<NtfsVolumeInformation>
    where
        T: Read + Seek,
    {
        let volume_file = self.file(fs, KnownNtfsFileRecordNumber::Volume as u64)?;
        volume_file.find_resident_attribute_structured_value::<NtfsVolumeInformation>(None)
    }

    /// Returns an [`NtfsVolumeName`] to read the volume name (also called volume label)
    /// of this NTFS volume.
    ///
    /// Note that a volume may also have no label, which is why the return value is further
    /// encapsulated in an `Option`.
    pub fn volume_name<T>(&self, fs: &mut T) -> Option<Result<NtfsVolumeName>>
    where
        T: Read + Seek,
    {
        let volume_file = iter_try!(self.file(fs, KnownNtfsFileRecordNumber::Volume as u64));

        match volume_file.find_resident_attribute_structured_value::<NtfsVolumeName>(None) {
            Ok(volume_name) => Some(Ok(volume_name)),
            Err(NtfsError::AttributeNotFound { .. }) => None,
            Err(e) => Some(Err(e)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basics() {
        let mut testfs1 = crate::helpers::tests::testfs1();
        let ntfs = Ntfs::new(&mut testfs1).unwrap();
        assert_eq!(ntfs.cluster_size(), 512);
        assert_eq!(ntfs.sector_size(), 512);
        assert_eq!(ntfs.size(), 2096640);
    }

    #[test]
    fn test_volume_info() {
        let mut testfs1 = crate::helpers::tests::testfs1();
        let ntfs = Ntfs::new(&mut testfs1).unwrap();
        let volume_info = ntfs.volume_info(&mut testfs1).unwrap();
        assert_eq!(volume_info.major_version(), 3);
        assert_eq!(volume_info.minor_version(), 1);
    }

    #[test]
    fn test_volume_name() {
        let mut testfs1 = crate::helpers::tests::testfs1();
        let ntfs = Ntfs::new(&mut testfs1).unwrap();
        let volume_name = ntfs.volume_name(&mut testfs1).unwrap().unwrap();
        assert_eq!(volume_name.name_length(), 14);
        assert_eq!(volume_name.name(), "mylabel");
    }
}