Skip to main content

windows_drives/
drive.rs

1use crate::win32;
2use derive_try_from_primitive::TryFromPrimitive;
3use std::{
4    cmp::min,
5    convert::TryFrom,
6    io::{self, Cursor, Read, Seek, SeekFrom},
7    mem::size_of,
8    ptr::null_mut,
9};
10use winapi::{
11    ctypes::c_void,
12    shared::ntdef::LARGE_INTEGER,
13    um::{
14        fileapi::{self as fs, OPEN_EXISTING},
15        handleapi::{CloseHandle, INVALID_HANDLE_VALUE},
16        ioapiset::DeviceIoControl,
17        winbase::{FILE_BEGIN, FILE_CURRENT, FILE_FLAG_NO_BUFFERING},
18        winioctl::{
19            DISK_GEOMETRY, IOCTL_DISK_GET_DRIVE_GEOMETRY, IOCTL_DISK_GET_PARTITION_INFO_EX,
20            PARTITION_INFORMATION_EX, PARTITION_STYLE_GPT, PARTITION_STYLE_MBR,
21            PARTITION_STYLE_RAW,
22        },
23        winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, HANDLE},
24    },
25};
26
27/// Gives direct access to a physical drive.
28///
29/// NOTE: because of Win32 restrictions, you must `read()` whole sectors and must only `seek()` to sector
30/// boundaries. If you need random access, use [`BufferedPhysicalDrive`].
31#[derive(Debug)]
32pub struct PhysicalDrive {
33    handle: HANDLE,
34    /// The [`DiskGeometry`] of the drive.
35    pub geometry: DiskGeometry,
36}
37
38/// Gives buffered direct access to a physical drive to enable random access.
39///
40/// Also see [`PhysicalDrive`].
41#[derive(Debug)]
42pub struct BufferedPhysicalDrive {
43    drive: PhysicalDrive,
44    current_sector: Cursor<Vec<u8>>,
45    current_sector_num: u64,
46    /// The [`DiskGeometry`] of the drive.
47    pub geometry: DiskGeometry,
48}
49
50/// Gives direct access to a harddisk volume.
51///
52/// NOTE: because of Win32 restrictions, you must `read()` whole sectors and must only `seek()` to sector
53/// boundaries. If you need random access, use [`BufferedHarddiskVolume`].
54#[derive(Debug)]
55pub struct HarddiskVolume {
56    handle: HANDLE,
57    /// The [`DiskGeometry`] of the drive this volume is located on.
58    pub geometry: DiskGeometry,
59    /// The [`PartitionInfo`] of the volume.
60    pub partition_info: PartitionInfo,
61}
62
63/// Gives buffered direct access to a harddisk volume to enable random access.
64///
65/// Also see [`HarddiskVolume`].
66#[derive(Debug)]
67pub struct BufferedHarddiskVolume {
68    volume: HarddiskVolume,
69    current_sector: Cursor<Vec<u8>>,
70    current_sector_num: u64,
71    /// The [`DiskGeometry`] of the drive this volume is located on.
72    pub geometry: DiskGeometry,
73    /// The [`PartitionInfo`] of the volume.
74    pub partition_info: PartitionInfo,
75}
76
77/// Represents the geometry of a disk or physical drive.
78#[derive(Copy, Clone, Debug)]
79pub struct DiskGeometry {
80    pub cylinders: i64,
81    pub media_type: u32,
82    pub tracks_per_cylinder: u32,
83    pub sectors_per_track: u32,
84    pub bytes_per_sector: u32,
85}
86
87/// The style of a partition.
88#[repr(u32)]
89#[derive(Copy, Clone, Debug, TryFromPrimitive)]
90pub enum PartitionStyle {
91    Gpt = PARTITION_STYLE_GPT,
92    Mbr = PARTITION_STYLE_MBR,
93    Raw = PARTITION_STYLE_RAW,
94}
95
96/// Information about a partition.
97#[derive(Copy, Clone, Debug)]
98pub struct PartitionInfo {
99    pub partition_style: PartitionStyle,
100    pub starting_offset: u64,
101    pub partition_length: u64,
102    pub partition_number: u32,
103}
104
105impl PhysicalDrive {
106    /// Opens the physical drive with the given number.
107    ///
108    /// NOTE: this requires administrator privileges. Drive numbers start at 0.
109    ///
110    /// Fails if an invalid drive number is given, the user has insufficient privileges
111    /// or an error occures while opening the drive.
112    /// If an error occurs, an error message containing the error number is returned, see
113    /// [here](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes) for a list.
114    pub fn open(drive_num: u8) -> Result<Self, String> {
115        let path = format!("\\\\.\\PhysicalDrive{}", drive_num);
116        let handle = open_handle(&path)?;
117        Ok(PhysicalDrive {
118            handle,
119            geometry: geometry(&handle)?,
120        })
121    }
122
123    /// The size of the drive in bytes.
124    pub fn size(&self) -> u64 {
125        self.geometry.size()
126    }
127}
128
129impl HarddiskVolume {
130    /// Opens the harddisk volume with the given number.
131    ///
132    /// NOTE: this requires administrator privileges. Volume numbers start at 1.
133    ///
134    /// Fails if an invalid harddisk volume number is given, the user has insufficient privileges
135    /// or an error occures while opening the volume.
136    /// If an error occurs, an error message containing the error number is returned, see
137    /// [here](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes) for a list.
138    pub fn open(volume_num: u8) -> Result<Self, String> {
139        let path = format!("\\\\?\\HarddiskVolume{}", volume_num);
140        let handle = open_handle(&path)?;
141        Ok(HarddiskVolume {
142            handle,
143            geometry: geometry(&handle)?,
144            partition_info: partition_info(&handle)?,
145        })
146    }
147
148    /// The size of the volume in bytes.
149    pub fn size(&self) -> u64 {
150        self.partition_info.partition_length
151    }
152}
153
154impl_physical!(PhysicalDrive, HarddiskVolume);
155
156impl BufferedPhysicalDrive {
157    /// Opens the physical drive with the given number.
158    ///
159    /// NOTE: this requires administrator privileges. Drive numbers start at 0.
160    ///
161    /// Fails if an invalid drive number is given, the user has insufficient privileges
162    /// or an error occures while opening the drive.
163    /// If an error occurs, an error message containing the error number is returned, see
164    /// [here](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes) for a list.
165    pub fn open(drive_num: u8) -> Result<Self, String> {
166        let mut drive = PhysicalDrive::open(drive_num)?;
167        let mut sector = vec![0; drive.geometry.bytes_per_sector as usize];
168        match drive.read(&mut sector) {
169            Ok(_) => (),
170            Err(e) => return Err(format!("{}", e)),
171        }
172        let geo = drive.geometry;
173        Ok(BufferedPhysicalDrive {
174            drive,
175            current_sector: Cursor::new(sector),
176            current_sector_num: 0,
177            geometry: geo,
178        })
179    }
180
181    /// The size of the drive in bytes.
182    pub fn size(&self) -> u64 {
183        self.geometry.size()
184    }
185}
186
187impl BufferedHarddiskVolume {
188    /// Opens the harddisk volume with the given number.
189    ///
190    /// NOTE: this requires administrator privileges. Volume numbers start at 1.
191    ///
192    /// Fails if an invalid harddisk volume number is given, the user has insufficient privileges
193    /// or an error occures while opening the colume.
194    /// If an error occurs, an error message containing the error number is returned, see
195    /// [here](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes) for a list.
196    pub fn open(volume_num: u8) -> Result<Self, String> {
197        let mut volume = HarddiskVolume::open(volume_num)?;
198        let mut sector = vec![0; volume.geometry.bytes_per_sector as usize];
199        match volume.read(&mut sector) {
200            Ok(_) => (),
201            Err(e) => return Err(format!("{}", e)),
202        }
203        let geo = volume.geometry;
204        let info = volume.partition_info;
205        Ok(BufferedHarddiskVolume {
206            volume,
207            current_sector: Cursor::new(sector),
208            current_sector_num: 0,
209            geometry: geo,
210            partition_info: info,
211        })
212    }
213
214    /// The size of the volume in bytes.
215    pub fn size(&self) -> u64 {
216        self.partition_info.partition_length
217    }
218}
219
220impl_buffered!(
221    (BufferedPhysicalDrive, drive),
222    (BufferedHarddiskVolume, volume)
223);
224
225impl DiskGeometry {
226    /// Returns the size of the disk in bytes.
227    pub fn size(&self) -> u64 {
228        self.sectors() * self.bytes_per_sector as u64
229    }
230
231    /// Returns the number of sectors of the disk.
232    pub fn sectors(&self) -> u64 {
233        self.cylinders as u64 * self.tracks_per_cylinder as u64 * self.sectors_per_track as u64
234    }
235}
236
237impl From<DISK_GEOMETRY> for DiskGeometry {
238    fn from(geo: DISK_GEOMETRY) -> Self {
239        DiskGeometry {
240            cylinders: unsafe { *geo.Cylinders.QuadPart() },
241            media_type: geo.MediaType,
242            tracks_per_cylinder: geo.TracksPerCylinder,
243            sectors_per_track: geo.SectorsPerTrack,
244            bytes_per_sector: geo.BytesPerSector,
245        }
246    }
247}
248
249impl From<PARTITION_INFORMATION_EX> for PartitionInfo {
250    fn from(info: PARTITION_INFORMATION_EX) -> Self {
251        PartitionInfo {
252            partition_style: PartitionStyle::try_from(info.PartitionStyle).unwrap(),
253            starting_offset: unsafe { *info.StartingOffset.QuadPart() } as _,
254            partition_length: unsafe { *info.PartitionLength.QuadPart() } as _,
255            partition_number: info.PartitionNumber,
256        }
257    }
258}
259
260fn open_handle(path: &str) -> Result<HANDLE, String> {
261    let path = win32::win32_string(&path);
262    let handle = unsafe {
263        fs::CreateFileW(
264            path.as_ptr(),
265            GENERIC_READ,
266            FILE_SHARE_READ | FILE_SHARE_WRITE,
267            null_mut(),
268            OPEN_EXISTING,
269            FILE_FLAG_NO_BUFFERING,
270            null_mut(),
271        )
272    };
273    if handle == INVALID_HANDLE_VALUE {
274        let err = win32::last_error();
275        Err(match err {
276            2 => "could not open handle because the device was not found".to_string(),
277            5 => "could not open handle because access was denied - do you have administrator privileges?".to_string(),
278            _ => format!("got invalid handle: error code {:#08x}", err)
279        })
280    } else {
281        Ok(handle)
282    }
283}
284
285// Gets the disk geometry via the Win32 API.
286// If an error occurs, an error message containing the error number is returned.
287fn geometry(drive: &HANDLE) -> Result<DiskGeometry, String> {
288    let mut geo = Default::default();
289    let mut bytes_returned = 0u32;
290    let geo_ptr: *mut DISK_GEOMETRY = &mut geo;
291    let r = unsafe {
292        DeviceIoControl(
293            *drive,
294            IOCTL_DISK_GET_DRIVE_GEOMETRY,
295            null_mut(),
296            0,
297            geo_ptr as *mut c_void,
298            size_of::<DISK_GEOMETRY>() as u32,
299            &mut bytes_returned,
300            null_mut(),
301        )
302    };
303    if r == 0 {
304        Err(format!(
305            "could not get geometry: error code {:#08x}",
306            win32::last_error()
307        ))
308    } else {
309        Ok(DiskGeometry::from(geo))
310    }
311}
312
313// Gets the disk geometry via the Win32 API.
314// If an error occurs, an error message containing the error number is returned.
315fn partition_info(partition: &HANDLE) -> Result<PartitionInfo, String> {
316    let mut info = Default::default();
317    let mut bytes_returned = 0u32;
318    let info_ptr: *mut PARTITION_INFORMATION_EX = &mut info;
319    let r = unsafe {
320        DeviceIoControl(
321            *partition,
322            IOCTL_DISK_GET_PARTITION_INFO_EX,
323            null_mut(),
324            0,
325            info_ptr as *mut c_void,
326            size_of::<PARTITION_INFORMATION_EX>() as u32,
327            &mut bytes_returned,
328            null_mut(),
329        )
330    };
331    if r == 0 {
332        Err(format!(
333            "could not get partition info: error code {:#08x}",
334            win32::last_error()
335        ))
336    } else {
337        Ok(PartitionInfo::from(info))
338    }
339}