use core::ffi::c_void;
use core::mem::size_of;
use core::ptr::null_mut;
use vck_common::{SectorIo, VckError, VckResult, VolumeId};
use wdk_sys::{
ntddk::{
ObOpenObjectByPointer, ZwClose, ZwCreateFile, ZwDeviceIoControlFile, ZwFsControlFile,
ZwReadFile, ZwWriteFile,
},
FILE_READ_DATA, FILE_WRITE_DATA, HANDLE, IO_STATUS_BLOCK, LARGE_INTEGER, OBJECT_ATTRIBUTES,
PDEVICE_OBJECT,
};
use crate::nt::{nt_success, UnicodeString};
const IOCTL_DISK_GET_DRIVE_GEOMETRY: u32 = 0x0007_0000;
const IOCTL_DISK_GET_LENGTH_INFO: u32 = 0x0007_405C;
const IOCTL_DISK_GET_PARTITION_INFO_EX: u32 = 0x0007_0048;
const FSCTL_ALLOW_EXTENDED_DASD_IO: u32 = 0x0009_0083;
const DISK_GEOMETRY_BYTES_PER_SECTOR_OFFSET: usize = 20;
const SYNCHRONIZE: u32 = 0x0010_0000;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x0000_0080;
const FILE_SHARE_READ: u32 = 0x0000_0001;
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
const FILE_OPEN: u32 = 0x0000_0001;
const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x0000_0020;
const FILE_NON_DIRECTORY_FILE: u32 = 0x0000_0040;
const FILE_NO_INTERMEDIATE_BUFFERING: u32 = 0x0000_0008;
const OBJ_CASE_INSENSITIVE: u32 = 0x0000_0040;
const OBJ_KERNEL_HANDLE: u32 = 0x0000_0200;
pub struct KernelVolumeIo {
handle: HANDLE,
sector_size: u32,
total_sectors: u64,
}
unsafe impl Send for KernelVolumeIo {}
unsafe impl Sync for KernelVolumeIo {}
impl KernelVolumeIo {
pub fn open(volume_id: &VolumeId, sector_size: u32, total_sectors: u64) -> VckResult<Self> {
let name = UnicodeString::new(&volume_id.device_path);
let mut oa: OBJECT_ATTRIBUTES = unsafe { core::mem::zeroed() };
oa.Length = size_of::<OBJECT_ATTRIBUTES>() as u32;
oa.ObjectName = name.as_ptr();
oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
let mut handle: HANDLE = null_mut();
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let status = unsafe {
ZwCreateFile(
&mut handle,
FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
&mut oa,
&mut iosb,
null_mut(),
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
| FILE_NO_INTERMEDIATE_BUFFERING,
null_mut(),
0,
)
};
crate::vck_log!(
"KVIO: ZwCreateFile status=0x{:08x} stack={}",
status,
crate::debug::remaining_stack()
);
if !nt_success(status) {
return Err(VckError::Io("ZwCreateFile failed".into()));
}
let me = Self {
handle,
sector_size,
total_sectors,
};
me.allow_extended_dasd_io();
Ok(me)
}
pub fn open_query(volume_id: &VolumeId) -> VckResult<Self> {
let mut me = Self::open(volume_id, 0, 0)?;
me.query_geometry()?;
Ok(me)
}
pub fn from_handle_query(handle: wdk_sys::HANDLE) -> VckResult<Self> {
let mut me = Self {
handle,
sector_size: 0,
total_sectors: 0,
};
me.allow_extended_dasd_io();
me.query_geometry()?;
Ok(me)
}
pub fn from_device_object(
device_object: PDEVICE_OBJECT,
sector_size: u32,
total_sectors: u64,
) -> VckResult<Self> {
let mut handle: HANDLE = null_mut();
let status = unsafe {
ObOpenObjectByPointer(
device_object.cast::<c_void>(),
OBJ_KERNEL_HANDLE,
null_mut(), FILE_READ_DATA | FILE_WRITE_DATA,
null_mut(), 0, &mut handle,
)
};
crate::vck_log!("KVIO: from_device_object status=0x{:08x}", status);
if !nt_success(status) {
return Err(VckError::Io("ObOpenObjectByPointer(lower) failed".into()));
}
Ok(Self {
handle,
sector_size,
total_sectors,
})
}
pub fn from_device_object_query(device_object: PDEVICE_OBJECT) -> VckResult<Self> {
let mut me = Self::from_device_object(device_object, 0, 0)?;
me.query_geometry()?;
Ok(me)
}
pub fn read_gpt_partition_id(&self) -> VckResult<vck_common::types::Guid> {
const PARTITION_STYLE_GPT: u32 = 1;
const GPT_PARTITION_ID_OFFSET: usize = 48;
let mut buf = [0u8; 144];
self.device_ioctl(IOCTL_DISK_GET_PARTITION_INFO_EX, &mut buf)?;
let style = u32::from_le_bytes(buf[0..4].try_into().unwrap());
if style != PARTITION_STYLE_GPT {
return Err(VckError::NotFound("device is not GPT-partitioned"));
}
let mut id = [0u8; 16];
id.copy_from_slice(&buf[GPT_PARTITION_ID_OFFSET..GPT_PARTITION_ID_OFFSET + 16]);
Ok(vck_common::types::guid_from_windows_bytes(id))
}
fn allow_extended_dasd_io(&self) {
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let status = unsafe {
ZwFsControlFile(
self.handle,
null_mut(),
None,
null_mut(),
&mut iosb,
FSCTL_ALLOW_EXTENDED_DASD_IO,
null_mut(),
0,
null_mut(),
0,
)
};
crate::vck_log!("KVIO: allow_extended_dasd_io status=0x{:08x}", status);
}
fn query_geometry(&mut self) -> VckResult<()> {
let mut geom = [0u8; 32];
crate::vck_log!("KVIO: ioctl DRIVE_GEOMETRY");
self.device_ioctl(IOCTL_DISK_GET_DRIVE_GEOMETRY, &mut geom)?;
let bps_off = DISK_GEOMETRY_BYTES_PER_SECTOR_OFFSET;
let bytes_per_sector = u32::from_le_bytes(geom[bps_off..bps_off + 4].try_into().unwrap());
if bytes_per_sector == 0 {
return Err(VckError::Io("volume reported zero sector size".into()));
}
let mut len = [0u8; 8];
crate::vck_log!("KVIO: ioctl LENGTH_INFO (bps={})", bytes_per_sector);
self.device_ioctl(IOCTL_DISK_GET_LENGTH_INFO, &mut len)?;
let total_bytes = u64::from_le_bytes(len);
self.sector_size = bytes_per_sector;
self.total_sectors = total_bytes / bytes_per_sector as u64;
crate::vck_log!(
"KVIO: geometry bps={} total_sectors={}",
bytes_per_sector,
self.total_sectors
);
Ok(())
}
fn device_ioctl(&self, code: u32, output: &mut [u8]) -> VckResult<()> {
crate::vck_log!(
"KVIO: device_ioctl code=0x{:08x} outlen={} stack={}",
code,
output.len(),
crate::debug::remaining_stack()
);
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let status = unsafe {
ZwDeviceIoControlFile(
self.handle,
null_mut(),
None,
null_mut(),
&mut iosb,
code,
null_mut(),
0,
output.as_mut_ptr().cast::<c_void>(),
output.len() as u32,
)
};
crate::vck_log!("KVIO: device_ioctl status=0x{:08x}", status);
if !nt_success(status) {
return Err(VckError::Io("volume geometry IOCTL failed".into()));
}
Ok(())
}
fn run_sync(&self, is_write: bool, lba: u64, buf: *mut u8, len: usize) -> VckResult<()> {
if len == 0 {
return Ok(());
}
crate::vck_log!(
"KVIO: run_sync write={} lba={} len={} stack={}",
is_write as u32,
lba,
len,
crate::debug::remaining_stack()
);
let byte_offset = lba
.checked_mul(self.sector_size as u64)
.ok_or_else(|| VckError::Io("sector offset overflow".into()))?;
let length = u32::try_from(len).map_err(|_| VckError::Io("I/O length too large".into()))?;
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let mut offset = LARGE_INTEGER {
QuadPart: byte_offset as i64,
};
let status = unsafe {
if is_write {
ZwWriteFile(
self.handle,
null_mut(),
None,
null_mut(),
&mut iosb,
buf.cast::<c_void>(),
length,
&mut offset,
null_mut(),
)
} else {
ZwReadFile(
self.handle,
null_mut(),
None,
null_mut(),
&mut iosb,
buf.cast::<c_void>(),
length,
&mut offset,
null_mut(),
)
}
};
crate::vck_log!("KVIO: run_sync done status=0x{:08x}", status);
if !nt_success(status) {
return Err(VckError::Io("volume I/O failed".into()));
}
Ok(())
}
}
impl Drop for KernelVolumeIo {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe {
let _ = ZwClose(self.handle);
}
}
}
}
impl SectorIo for KernelVolumeIo {
fn sector_size(&self) -> u32 {
self.sector_size
}
fn total_sectors(&self) -> u64 {
self.total_sectors
}
fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> VckResult<()> {
self.run_sync(false, lba, buf.as_mut_ptr(), buf.len())
}
fn write_sectors(&self, lba: u64, buf: &[u8]) -> VckResult<()> {
self.run_sync(true, lba, buf.as_ptr() as *mut u8, buf.len())
}
}
use crate::nt::STATUS_PENDING;
use wdk_sys::{
ntddk::{
IoBuildDeviceIoControlRequest, IoBuildSynchronousFsdRequest, IofCallDriver,
KeInitializeEvent, KeWaitForSingleObject,
},
_EVENT_TYPE::NotificationEvent,
_KWAIT_REASON::Executive,
_MODE::KernelMode,
IRP_MJ_READ, IRP_MJ_WRITE, KEVENT,
};
pub struct LowerDeviceIo {
device_object: PDEVICE_OBJECT,
sector_size: u32,
total_sectors: u64,
}
unsafe impl Send for LowerDeviceIo {}
unsafe impl Sync for LowerDeviceIo {}
impl LowerDeviceIo {
pub fn new(device_object: PDEVICE_OBJECT, sector_size: u32, total_sectors: u64) -> Self {
Self {
device_object,
sector_size,
total_sectors,
}
}
fn device_ioctl(&self, code: u32, out: &mut [u8]) -> VckResult<()> {
let mut event: KEVENT = unsafe { core::mem::zeroed() };
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
unsafe { KeInitializeEvent(&mut event, NotificationEvent, 0) };
let irp = unsafe {
IoBuildDeviceIoControlRequest(
code,
self.device_object,
null_mut(),
0,
out.as_mut_ptr().cast::<c_void>(),
out.len() as u32,
0, &mut event,
&mut iosb,
)
};
if irp.is_null() {
return Err(VckError::Io(
"IoBuildDeviceIoControlRequest(lower) failed".into(),
));
}
let mut status = unsafe { IofCallDriver(self.device_object, irp) };
if status == STATUS_PENDING {
unsafe {
let _ = KeWaitForSingleObject(
(&mut event as *mut KEVENT).cast::<c_void>(),
Executive,
KernelMode as i8,
0,
null_mut(),
);
status = iosb.__bindgen_anon_1.Status;
}
}
if !nt_success(status) {
return Err(VckError::Io("lower device IOCTL failed".into()));
}
Ok(())
}
pub fn query_geometry(&mut self) -> VckResult<()> {
let mut geom = [0u8; 32];
self.device_ioctl(IOCTL_DISK_GET_DRIVE_GEOMETRY, &mut geom)?;
let off = DISK_GEOMETRY_BYTES_PER_SECTOR_OFFSET;
let bps = u32::from_le_bytes(geom[off..off + 4].try_into().unwrap());
if bps == 0 {
return Err(VckError::Io(
"lower device reported zero sector size".into(),
));
}
let mut len = [0u8; 8];
self.device_ioctl(IOCTL_DISK_GET_LENGTH_INFO, &mut len)?;
let total_bytes = u64::from_le_bytes(len);
self.sector_size = bps;
self.total_sectors = total_bytes / bps as u64;
Ok(())
}
pub fn read_gpt_partition_id(&self) -> VckResult<vck_common::types::Guid> {
const PARTITION_STYLE_GPT: u32 = 1;
const GPT_PARTITION_ID_OFFSET: usize = 48;
let mut buf = [0u8; 144];
self.device_ioctl(IOCTL_DISK_GET_PARTITION_INFO_EX, &mut buf)?;
let style = u32::from_le_bytes(buf[0..4].try_into().unwrap());
if style != PARTITION_STYLE_GPT {
return Err(VckError::NotFound("device is not GPT-partitioned"));
}
let mut id = [0u8; 16];
id.copy_from_slice(&buf[GPT_PARTITION_ID_OFFSET..GPT_PARTITION_ID_OFFSET + 16]);
Ok(vck_common::types::guid_from_windows_bytes(id))
}
fn run_sync(&self, major: u32, lba: u64, buf: *mut u8, len: usize) -> VckResult<()> {
if len == 0 {
return Ok(());
}
let byte_offset = lba
.checked_mul(self.sector_size as u64)
.ok_or_else(|| VckError::Io("sector offset overflow".into()))?;
let length = u32::try_from(len).map_err(|_| VckError::Io("I/O length too large".into()))?;
let mut event: KEVENT = unsafe { core::mem::zeroed() };
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let mut offset = LARGE_INTEGER {
QuadPart: byte_offset as i64,
};
unsafe { KeInitializeEvent(&mut event, NotificationEvent, 0) };
let irp = unsafe {
IoBuildSynchronousFsdRequest(
major,
self.device_object,
buf.cast::<c_void>(),
length,
&mut offset,
&mut event,
&mut iosb,
)
};
if irp.is_null() {
return Err(VckError::Io(
"IoBuildSynchronousFsdRequest(lower) failed".into(),
));
}
let mut status = unsafe { IofCallDriver(self.device_object, irp) };
if status == STATUS_PENDING {
unsafe {
let _ = KeWaitForSingleObject(
(&mut event as *mut KEVENT).cast::<c_void>(),
Executive,
KernelMode as i8,
0,
null_mut(),
);
status = iosb.__bindgen_anon_1.Status;
}
}
if !nt_success(status) {
return Err(VckError::Io("lower device I/O failed".into()));
}
Ok(())
}
}
impl SectorIo for LowerDeviceIo {
fn sector_size(&self) -> u32 {
self.sector_size
}
fn total_sectors(&self) -> u64 {
self.total_sectors
}
fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> VckResult<()> {
self.run_sync(IRP_MJ_READ, lba, buf.as_mut_ptr(), buf.len())
}
fn write_sectors(&self, lba: u64, buf: &[u8]) -> VckResult<()> {
self.run_sync(IRP_MJ_WRITE, lba, buf.as_ptr() as *mut u8, buf.len())
}
}
pub fn open_volume_handle_raw(nt_path: &str) -> Option<wdk_sys::HANDLE> {
let name = UnicodeString::new(nt_path);
let mut oa: OBJECT_ATTRIBUTES = unsafe { core::mem::zeroed() };
oa.Length = size_of::<OBJECT_ATTRIBUTES>() as u32;
oa.ObjectName = name.as_ptr();
oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
let mut handle: wdk_sys::HANDLE = null_mut();
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
let status = unsafe {
ZwCreateFile(
&mut handle,
FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
&mut oa,
&mut iosb,
null_mut(),
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
null_mut(),
0,
)
};
if nt_success(status) {
Some(handle)
} else {
None
}
}
pub struct OffsetSectorIo {
inner: alloc::sync::Arc<dyn vck_common::SectorIo>,
partition_start_lba: u64,
}
impl OffsetSectorIo {
pub fn new(
inner: alloc::sync::Arc<dyn vck_common::SectorIo>,
partition_start_lba: u64,
) -> Self {
Self {
inner,
partition_start_lba,
}
}
}
impl vck_common::SectorIo for OffsetSectorIo {
fn sector_size(&self) -> u32 {
self.inner.sector_size()
}
fn total_sectors(&self) -> u64 {
self.inner.total_sectors()
}
fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> VckResult<()> {
self.inner.read_sectors(self.partition_start_lba + lba, buf)
}
fn write_sectors(&self, lba: u64, buf: &[u8]) -> VckResult<()> {
self.inner
.write_sectors(self.partition_start_lba + lba, buf)
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn send_fsctl(handle: wdk_sys::HANDLE, code: u32) -> i32 {
let mut iosb: IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
unsafe {
ZwFsControlFile(
handle,
null_mut(),
None,
null_mut(),
&mut iosb,
code,
null_mut(),
0,
null_mut(),
0,
)
}
}