use crate::win32;
use derive_try_from_primitive::TryFromPrimitive;
use std::{
cmp::min,
convert::TryFrom,
io::{self, Cursor, Read, Seek, SeekFrom},
mem::size_of,
ptr::null_mut,
};
use winapi::{
ctypes::c_void,
shared::ntdef::LARGE_INTEGER,
um::{
fileapi::{self as fs, OPEN_EXISTING},
handleapi::{CloseHandle, INVALID_HANDLE_VALUE},
ioapiset::DeviceIoControl,
winbase::{FILE_BEGIN, FILE_CURRENT, FILE_FLAG_NO_BUFFERING},
winioctl::{
DISK_GEOMETRY, IOCTL_DISK_GET_DRIVE_GEOMETRY, IOCTL_DISK_GET_PARTITION_INFO_EX,
PARTITION_INFORMATION_EX, PARTITION_STYLE_GPT, PARTITION_STYLE_MBR,
PARTITION_STYLE_RAW,
},
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, HANDLE},
},
};
#[derive(Debug)]
pub struct PhysicalDrive {
handle: HANDLE,
pub geometry: DiskGeometry,
}
#[derive(Debug)]
pub struct BufferedPhysicalDrive {
drive: PhysicalDrive,
current_sector: Cursor<Vec<u8>>,
current_sector_num: u64,
pub geometry: DiskGeometry,
}
#[derive(Debug)]
pub struct HarddiskVolume {
handle: HANDLE,
pub geometry: DiskGeometry,
pub partition_info: PartitionInfo,
}
#[derive(Debug)]
pub struct BufferedHarddiskVolume {
volume: HarddiskVolume,
current_sector: Cursor<Vec<u8>>,
current_sector_num: u64,
pub geometry: DiskGeometry,
pub partition_info: PartitionInfo,
}
#[derive(Copy, Clone, Debug)]
pub struct DiskGeometry {
pub cylinders: i64,
pub media_type: u32,
pub tracks_per_cylinder: u32,
pub sectors_per_track: u32,
pub bytes_per_sector: u32,
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, TryFromPrimitive)]
pub enum PartitionStyle {
Gpt = PARTITION_STYLE_GPT,
Mbr = PARTITION_STYLE_MBR,
Raw = PARTITION_STYLE_RAW,
}
#[derive(Copy, Clone, Debug)]
pub struct PartitionInfo {
pub partition_style: PartitionStyle,
pub starting_offset: u64,
pub partition_length: u64,
pub partition_number: u32,
}
impl PhysicalDrive {
pub fn open(drive_num: u8) -> Result<Self, String> {
let path = format!("\\\\.\\PhysicalDrive{}", drive_num);
let handle = open_handle(&path)?;
Ok(PhysicalDrive {
handle,
geometry: geometry(&handle)?,
})
}
pub fn size(&self) -> u64 {
self.geometry.size()
}
}
impl HarddiskVolume {
pub fn open(volume_num: u8) -> Result<Self, String> {
let path = format!("\\\\?\\HarddiskVolume{}", volume_num);
let handle = open_handle(&path)?;
Ok(HarddiskVolume {
handle,
geometry: geometry(&handle)?,
partition_info: partition_info(&handle)?,
})
}
pub fn size(&self) -> u64 {
self.partition_info.partition_length
}
}
impl_physical!(PhysicalDrive, HarddiskVolume);
impl BufferedPhysicalDrive {
pub fn open(drive_num: u8) -> Result<Self, String> {
let mut drive = PhysicalDrive::open(drive_num)?;
let mut sector = vec![0; drive.geometry.bytes_per_sector as usize];
match drive.read(&mut sector) {
Ok(_) => (),
Err(e) => return Err(format!("{}", e)),
}
let geo = drive.geometry;
Ok(BufferedPhysicalDrive {
drive,
current_sector: Cursor::new(sector),
current_sector_num: 0,
geometry: geo,
})
}
pub fn size(&self) -> u64 {
self.geometry.size()
}
}
impl BufferedHarddiskVolume {
pub fn open(volume_num: u8) -> Result<Self, String> {
let mut volume = HarddiskVolume::open(volume_num)?;
let mut sector = vec![0; volume.geometry.bytes_per_sector as usize];
match volume.read(&mut sector) {
Ok(_) => (),
Err(e) => return Err(format!("{}", e)),
}
let geo = volume.geometry;
let info = volume.partition_info;
Ok(BufferedHarddiskVolume {
volume,
current_sector: Cursor::new(sector),
current_sector_num: 0,
geometry: geo,
partition_info: info,
})
}
pub fn size(&self) -> u64 {
self.partition_info.partition_length
}
}
impl_buffered!(
(BufferedPhysicalDrive, drive),
(BufferedHarddiskVolume, volume)
);
impl DiskGeometry {
pub fn size(&self) -> u64 {
self.sectors() * self.bytes_per_sector as u64
}
pub fn sectors(&self) -> u64 {
self.cylinders as u64 * self.tracks_per_cylinder as u64 * self.sectors_per_track as u64
}
}
impl From<DISK_GEOMETRY> for DiskGeometry {
fn from(geo: DISK_GEOMETRY) -> Self {
DiskGeometry {
cylinders: unsafe { *geo.Cylinders.QuadPart() },
media_type: geo.MediaType,
tracks_per_cylinder: geo.TracksPerCylinder,
sectors_per_track: geo.SectorsPerTrack,
bytes_per_sector: geo.BytesPerSector,
}
}
}
impl From<PARTITION_INFORMATION_EX> for PartitionInfo {
fn from(info: PARTITION_INFORMATION_EX) -> Self {
PartitionInfo {
partition_style: PartitionStyle::try_from(info.PartitionStyle).unwrap(),
starting_offset: unsafe { *info.StartingOffset.QuadPart() } as _,
partition_length: unsafe { *info.PartitionLength.QuadPart() } as _,
partition_number: info.PartitionNumber,
}
}
}
fn open_handle(path: &str) -> Result<HANDLE, String> {
let path = win32::win32_string(&path);
let handle = unsafe {
fs::CreateFileW(
path.as_ptr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING,
null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
let err = win32::last_error();
Err(match err {
2 => "could not open handle because the device was not found".to_string(),
5 => "could not open handle because access was denied - do you have administrator privileges?".to_string(),
_ => format!("got invalid handle: error code {:#08x}", err)
})
} else {
Ok(handle)
}
}
fn geometry(drive: &HANDLE) -> Result<DiskGeometry, String> {
let mut geo = Default::default();
let mut bytes_returned = 0u32;
let geo_ptr: *mut DISK_GEOMETRY = &mut geo;
let r = unsafe {
DeviceIoControl(
*drive,
IOCTL_DISK_GET_DRIVE_GEOMETRY,
null_mut(),
0,
geo_ptr as *mut c_void,
size_of::<DISK_GEOMETRY>() as u32,
&mut bytes_returned,
null_mut(),
)
};
if r == 0 {
Err(format!(
"could not get geometry: error code {:#08x}",
win32::last_error()
))
} else {
Ok(DiskGeometry::from(geo))
}
}
fn partition_info(partition: &HANDLE) -> Result<PartitionInfo, String> {
let mut info = Default::default();
let mut bytes_returned = 0u32;
let info_ptr: *mut PARTITION_INFORMATION_EX = &mut info;
let r = unsafe {
DeviceIoControl(
*partition,
IOCTL_DISK_GET_PARTITION_INFO_EX,
null_mut(),
0,
info_ptr as *mut c_void,
size_of::<PARTITION_INFORMATION_EX>() as u32,
&mut bytes_returned,
null_mut(),
)
};
if r == 0 {
Err(format!(
"could not get partition info: error code {:#08x}",
win32::last_error()
))
} else {
Ok(PartitionInfo::from(info))
}
}