// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34//! Macros and wrapper functions for dealing with ioctls.56use std::mem::size_of;7use std::os::raw::c_int;8use std::os::raw::c_ulong;9use std::os::raw::*;10use std::ptr::null_mut;1112use winapi::um::errhandlingapi::GetLastError;13use winapi::um::ioapiset::DeviceIoControl;14pub use winapi::um::winioctl::FILE_ANY_ACCESS;15pub use winapi::um::winioctl::METHOD_BUFFERED;1617use crate::descriptor::AsRawDescriptor;18use crate::errno_result;19use crate::Result;2021/// Raw macro to declare the expression that calculates an ioctl number22#[macro_export]23macro_rules! device_io_control_expr {24// TODO (colindr) b/144440409: right now GVM is our only DeviceIOControl25// target on windows, and it only uses METHOD_BUFFERED for the transfer26// type and FILE_ANY_ACCESS for the required access, so we're going to27// just use that for now. However, we may need to support more28// options later.29($dtype:expr, $code:expr) => {30$crate::windows::ctl_code(31$dtype,32$code,33$crate::windows::METHOD_BUFFERED,34$crate::windows::FILE_ANY_ACCESS,35) as ::std::os::raw::c_ulong36};37}3839/// Raw macro to declare a function that returns an DeviceIOControl code.40#[macro_export]41macro_rules! ioctl_ioc_nr {42($name:ident, $dtype:expr, $code:expr) => {43#[allow(non_snake_case)]44pub const $name: ::std::os::raw::c_ulong = $crate::device_io_control_expr!($dtype, $code);45};46($name:ident, $dtype:expr, $code:expr, $($v:ident),+) => {47#[allow(non_snake_case)]48pub fn $name($($v: ::std::os::raw::c_uint),+) -> ::std::os::raw::c_ulong {49$crate::device_io_control_expr!($dtype, $code)50}51};52}5354/// Declare an ioctl that transfers no data.55#[macro_export]56macro_rules! ioctl_io_nr {57($name:ident, $ty:expr, $nr:expr) => {58$crate::ioctl_ioc_nr!($name, $ty, $nr);59};60($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {61$crate::ioctl_ioc_nr!($name, $ty, $nr, $($v),+);62};63}6465/// Declare an ioctl that reads data.66#[macro_export]67macro_rules! ioctl_ior_nr {68($name:ident, $ty:expr, $nr:expr, $size:ty) => {69$crate::ioctl_ioc_nr!(70$name,71$ty,72$nr73);74};75($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {76$crate::ioctl_ioc_nr!(77$name,78$ty,79$nr,80$($v),+81);82};83}8485/// Declare an ioctl that writes data.86#[macro_export]87macro_rules! ioctl_iow_nr {88($name:ident, $ty:expr, $nr:expr, $size:ty) => {89$crate::ioctl_ioc_nr!(90$name,91$ty,92$nr93);94};95($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {96$crate::ioctl_ioc_nr!(97$name,98$ty,99$nr,100$($v),+101);102};103}104105/// Declare an ioctl that reads and writes data.106#[macro_export]107macro_rules! ioctl_iowr_nr {108($name:ident, $ty:expr, $nr:expr, $size:ty) => {109$crate::ioctl_ioc_nr!(110$name,111$ty,112$nr113);114};115($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {116$crate::ioctl_ioc_nr!(117$name,118$ty,119$nr,120$($v),+121);122};123}124125pub type IoctlNr = c_ulong;126127/// Constructs an I/O control code.128///129/// Shifts control code components into the appropriate bitfield locations:130/// <https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes>131pub const fn ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> IoctlNr {132(device_type << 16) | (access << 14) | (function << 2) | method133}134135/// Run an ioctl with no arguments.136// (colindr) b/144457461 : This will probably not be used on windows.137// It's only used on linux for the ioctls that override the exit code to138// be the return value of the ioctl. As far as I can tell, no DeviceIoControl139// will do this, they will always instead return values in the output140// buffer. So, as a result, we have no tests for this function, and141// we may want to remove it if we never use it on windows, but we can't142// remove it right now until we re-implement all the code that calls143// this funciton for windows.144/// # Safety145/// The caller is responsible for determining the safety of the particular ioctl.146/// This method should be safe as `DeviceIoControl` will handle error cases147/// and it does size checking.148pub unsafe fn ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int {149let mut byte_ret: c_ulong = 0;150let ret = DeviceIoControl(151descriptor.as_raw_descriptor(),152nr,153null_mut(),1540,155null_mut(),1560,157&mut byte_ret,158null_mut(),159);160161if ret == 1 {162return 0;163}164165GetLastError() as i32166}167168/// Run an ioctl with a single value argument.169/// # Safety170/// The caller is responsible for determining the safety of the particular ioctl.171/// This method should be safe as `DeviceIoControl` will handle error cases172/// and it does size checking.173pub unsafe fn ioctl_with_val(174descriptor: &dyn AsRawDescriptor,175nr: IoctlNr,176mut arg: c_ulong,177) -> c_int {178let mut byte_ret: c_ulong = 0;179180let ret = DeviceIoControl(181descriptor.as_raw_descriptor(),182nr,183&mut arg as *mut c_ulong as *mut c_void,184size_of::<c_ulong>() as u32,185null_mut(),1860,187&mut byte_ret,188null_mut(),189);190191if ret == 1 {192return 0;193}194195GetLastError() as i32196}197198/// Run an ioctl with an immutable reference.199/// # Safety200/// The caller is responsible for determining the safety of the particular ioctl.201/// Look at `ioctl_with_ptr` comments.202pub unsafe fn ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int {203ioctl_with_ptr(descriptor, nr, arg)204}205206/// Run an ioctl with a mutable reference.207/// # Safety208/// The caller is responsible for determining the safety of the particular ioctl.209/// Look at `ioctl_with_ptr` comments.210pub unsafe fn ioctl_with_mut_ref<T>(211descriptor: &dyn AsRawDescriptor,212nr: IoctlNr,213arg: &mut T,214) -> c_int {215ioctl_with_mut_ptr(descriptor, nr, arg)216}217218/// Run an ioctl with a raw pointer, specifying the size of the buffer.219/// # Safety220/// This method should be safe as `DeviceIoControl` will handle error cases221/// and it does size checking. Also The caller should make sure `T` is valid.222pub unsafe fn ioctl_with_ptr_sized<T>(223descriptor: &dyn AsRawDescriptor,224nr: IoctlNr,225arg: *const T,226size: usize,227) -> c_int {228let mut byte_ret: c_ulong = 0;229230// We are trusting the DeviceIoControl function to not write anything231// to the input buffer. Just because it's a *const does not prevent232// the unsafe call from writing to it.233let ret = DeviceIoControl(234descriptor.as_raw_descriptor(),235nr,236arg as *mut c_void,237size as u32,238// We pass a null_mut as the output buffer. If you expect239// an output, you should be calling the mut variant of this240// function.241null_mut(),2420,243&mut byte_ret,244null_mut(),245);246247if ret == 1 {248return 0;249}250251GetLastError() as i32252}253254/// Run an ioctl with a raw pointer.255/// # Safety256/// The caller is responsible for determining the safety of the particular ioctl.257/// This method should be safe as `DeviceIoControl` will handle error cases258/// and it does size checking. Also The caller should make sure `T` is valid.259pub unsafe fn ioctl_with_ptr<T>(260descriptor: &dyn AsRawDescriptor,261nr: IoctlNr,262arg: *const T,263) -> c_int {264ioctl_with_ptr_sized(descriptor, nr, arg, size_of::<T>())265}266267/// Run an ioctl with a mutable raw pointer.268/// # Safety269/// The caller is responsible for determining the safety of the particular ioctl.270/// This method should be safe as `DeviceIoControl` will handle error cases271/// and it does size checking. Also The caller should make sure `T` is valid.272pub unsafe fn ioctl_with_mut_ptr<T>(273descriptor: &dyn AsRawDescriptor,274nr: IoctlNr,275arg: *mut T,276) -> c_int {277let mut byte_ret: c_ulong = 0;278279let ret = DeviceIoControl(280descriptor.as_raw_descriptor(),281nr,282arg as *mut c_void,283size_of::<T>() as u32,284arg as *mut c_void,285size_of::<T>() as u32,286&mut byte_ret,287null_mut(),288);289290if ret == 1 {291return 0;292}293294GetLastError() as i32295}296297/// Run a DeviceIoControl, specifying all options, only available on windows298/// # Safety299/// This method should be safe as `DeviceIoControl` will handle error cases300/// for invalid paramters and takes input buffer and output buffer size301/// arguments. Also The caller should make sure `T` is valid.302pub unsafe fn device_io_control<F: AsRawDescriptor, T, T2>(303descriptor: &F,304nr: IoctlNr,305input: *const T,306inputsize: u32,307output: *mut T2,308outputsize: u32,309byte_ret: &mut c_ulong,310) -> Result<()> {311let ret = DeviceIoControl(312descriptor.as_raw_descriptor(),313nr,314input as *mut c_void,315inputsize,316output as *mut c_void,317outputsize,318byte_ret,319null_mut(),320);321322if ret == 1 {323return Ok(());324}325326errno_result()327}328329#[cfg(test)]330mod tests {331332use std::ffi::OsStr;333use std::fs::File;334use std::fs::OpenOptions;335use std::io::prelude::*;336use std::os::raw::*;337use std::os::windows::ffi::OsStrExt;338use std::os::windows::prelude::*;339use std::ptr::null_mut;340341use tempfile::tempdir;342use winapi::um::fileapi::CreateFileW;343use winapi::um::fileapi::OPEN_EXISTING;344use winapi::um::winbase::SECURITY_SQOS_PRESENT;345use winapi::um::winioctl::FSCTL_GET_COMPRESSION;346use winapi::um::winioctl::FSCTL_SET_COMPRESSION;347use winapi::um::winnt::COMPRESSION_FORMAT_LZNT1;348use winapi::um::winnt::COMPRESSION_FORMAT_NONE;349use winapi::um::winnt::FILE_SHARE_READ;350use winapi::um::winnt::FILE_SHARE_WRITE;351use winapi::um::winnt::GENERIC_READ;352use winapi::um::winnt::GENERIC_WRITE;353354// helper func, returns str as Vec<u16>355fn to_u16s<S: AsRef<OsStr>>(s: S) -> std::io::Result<Vec<u16>> {356Ok(s.as_ref().encode_wide().chain(Some(0)).collect())357}358359#[cfg_attr(all(target_os = "windows", target_env = "gnu"), ignore)]360#[test]361fn ioct_get_and_set_compression() {362let dir = tempdir().unwrap();363let file_path = dir.path().join("test.dat");364let file_path = file_path.as_path();365366// compressed = empty short for compressed status to be read into367let mut compressed: c_ushort = 0x0000;368369// open our random file and write "foo" in it370let mut f = OpenOptions::new()371.write(true)372.create_new(true)373.open(file_path)374.unwrap();375f.write_all(b"foo").expect("Failed to write bytes.");376f.sync_all().expect("Failed to sync all.");377378// read the compression status379// SAFETY: safe because return value is checked.380let ecode = unsafe {381super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)382};383384// shouldn't error385assert_eq!(ecode, 0);386// should not be compressed by default (not sure if this will be the case on387// all machines...)388assert_eq!(compressed, COMPRESSION_FORMAT_NONE);389390// Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.391compressed = COMPRESSION_FORMAT_LZNT1;392393// NOTE: Theoretically I should be able to open this file like so:394// let mut f = OpenOptions::new()395// .access_mode(GENERIC_WRITE|GENERIC_WRITE)396// .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)397// .open("test.dat").unwrap();398//399// However, that does not work, and I'm not sure why. Here's where400// the underlying std code is doing a CreateFileW:401// https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260402// For now I'm just going to leave this test as-is.403//404// SAFETY: safe because return value is checked.405let f = unsafe {406File::from_raw_handle(CreateFileW(407to_u16s(file_path).unwrap().as_ptr(),408GENERIC_READ | GENERIC_WRITE,409FILE_SHARE_READ | FILE_SHARE_WRITE,410null_mut(),411OPEN_EXISTING,412// I read there's some security concerns if you don't use this413SECURITY_SQOS_PRESENT,414null_mut(),415))416};417418let ecode =419// SAFETY: safe because return value is checked.420unsafe { super::super::ioctl::ioctl_with_ref(&f, FSCTL_SET_COMPRESSION, &compressed) };421422assert_eq!(ecode, 0);423// set compressed short back to 0 for reading purposes,424// otherwise we can't be sure we're the FSCTL_GET_COMPRESSION425// is writing anything to the compressed pointer.426compressed = 0;427428// SAFETY: safe because return value is checked.429let ecode = unsafe {430super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)431};432433// now should be compressed434assert_eq!(ecode, 0);435assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);436437drop(f);438// clean up439dir.close().expect("Failed to close the temp directory.");440}441442#[cfg_attr(all(target_os = "windows", target_env = "gnu"), ignore)]443#[test]444fn ioctl_with_val() {445let dir = tempdir().unwrap();446let file_path = dir.path().join("test.dat");447let file_path = file_path.as_path();448449// compressed = empty short for compressed status to be read into450// Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.451let mut compressed: c_ushort = COMPRESSION_FORMAT_LZNT1;452453// open our random file and write "foo" in it454let mut f = OpenOptions::new()455.write(true)456.create_new(true)457.open(file_path)458.unwrap();459f.write_all(b"foo").expect("Failed to write bytes.");460f.sync_all().expect("Failed to sync all.");461462// NOTE: Theoretically I should be able to open this file like so:463// let mut f = OpenOptions::new()464// .access_mode(GENERIC_WRITE|GENERIC_WRITE)465// .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)466// .open("test.dat").unwrap();467//468// However, that does not work, and I'm not sure why. Here's where469// the underlying std code is doing a CreateFileW:470// https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260471// For now I'm just going to leave this test as-is.472//473// SAFETY: safe because return value is checked.474let f = unsafe {475File::from_raw_handle(CreateFileW(476to_u16s(file_path).unwrap().as_ptr(),477GENERIC_READ | GENERIC_WRITE,478FILE_SHARE_READ | FILE_SHARE_WRITE,479null_mut(),480OPEN_EXISTING,481// I read there's some security concerns if you don't use this482SECURITY_SQOS_PRESENT,483null_mut(),484))485};486487// now we call ioctl_with_val, which isn't particularly any more helpful than488// ioctl_with_ref except for the cases where the input is only a word long489// SAFETY: safe because return value is checked.490let ecode = unsafe {491super::super::ioctl::ioctl_with_val(&f, FSCTL_SET_COMPRESSION, compressed.into())492};493494assert_eq!(ecode, 0);495// set compressed short back to 0 for reading purposes,496// otherwise we can't be sure we're the FSCTL_GET_COMPRESSION497// is writing anything to the compressed pointer.498compressed = 0;499500// SAFETY: safe because return value is checked.501let ecode = unsafe {502super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)503};504505// now should be compressed506assert_eq!(ecode, 0);507assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);508509drop(f);510// clean up511dir.close().expect("Failed to close the temp directory.");512}513}514515516