xref: /aosp_15_r20/external/crosvm/base/src/sys/windows/ioctl.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Macros and wrapper functions for dealing with ioctls.
6 
7 use std::mem::size_of;
8 use std::os::raw::c_int;
9 use std::os::raw::c_ulong;
10 use std::os::raw::*;
11 use std::ptr::null_mut;
12 
13 use winapi::um::errhandlingapi::GetLastError;
14 use winapi::um::ioapiset::DeviceIoControl;
15 pub use winapi::um::winioctl::FILE_ANY_ACCESS;
16 pub use winapi::um::winioctl::METHOD_BUFFERED;
17 
18 use crate::descriptor::AsRawDescriptor;
19 use crate::errno_result;
20 use crate::Result;
21 
22 /// Raw macro to declare the expression that calculates an ioctl number
23 #[macro_export]
24 macro_rules! device_io_control_expr {
25     // TODO (colindr) b/144440409: right now GVM is our only DeviceIOControl
26     //  target on windows, and it only uses METHOD_BUFFERED for the transfer
27     //  type and FILE_ANY_ACCESS for the required access, so we're going to
28     //  just use that for now. However, we may need to support more
29     //  options later.
30     ($dtype:expr, $code:expr) => {
31         $crate::windows::ctl_code(
32             $dtype,
33             $code,
34             $crate::windows::METHOD_BUFFERED,
35             $crate::windows::FILE_ANY_ACCESS,
36         ) as ::std::os::raw::c_ulong
37     };
38 }
39 
40 /// Raw macro to declare a function that returns an DeviceIOControl code.
41 #[macro_export]
42 macro_rules! ioctl_ioc_nr {
43     ($name:ident, $dtype:expr, $code:expr) => {
44         #[allow(non_snake_case)]
45         pub const $name: ::std::os::raw::c_ulong = $crate::device_io_control_expr!($dtype, $code);
46     };
47     ($name:ident, $dtype:expr, $code:expr, $($v:ident),+) => {
48         #[allow(non_snake_case)]
49         pub fn $name($($v: ::std::os::raw::c_uint),+) -> ::std::os::raw::c_ulong {
50             $crate::device_io_control_expr!($dtype, $code)
51         }
52     };
53 }
54 
55 /// Declare an ioctl that transfers no data.
56 #[macro_export]
57 macro_rules! ioctl_io_nr {
58     ($name:ident, $ty:expr, $nr:expr) => {
59         $crate::ioctl_ioc_nr!($name, $ty, $nr);
60     };
61     ($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {
62         $crate::ioctl_ioc_nr!($name, $ty, $nr, $($v),+);
63     };
64 }
65 
66 /// Declare an ioctl that reads data.
67 #[macro_export]
68 macro_rules! ioctl_ior_nr {
69     ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
70         $crate::ioctl_ioc_nr!(
71             $name,
72             $ty,
73             $nr
74         );
75     };
76     ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
77         $crate::ioctl_ioc_nr!(
78             $name,
79             $ty,
80             $nr,
81             $($v),+
82         );
83     };
84 }
85 
86 /// Declare an ioctl that writes data.
87 #[macro_export]
88 macro_rules! ioctl_iow_nr {
89     ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
90         $crate::ioctl_ioc_nr!(
91             $name,
92             $ty,
93             $nr
94         );
95     };
96     ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
97         $crate::ioctl_ioc_nr!(
98             $name,
99             $ty,
100             $nr,
101             $($v),+
102         );
103     };
104 }
105 
106 /// Declare an ioctl that reads and writes data.
107 #[macro_export]
108 macro_rules! ioctl_iowr_nr {
109     ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
110         $crate::ioctl_ioc_nr!(
111             $name,
112             $ty,
113             $nr
114         );
115     };
116     ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
117         $crate::ioctl_ioc_nr!(
118             $name,
119             $ty,
120             $nr,
121             $($v),+
122         );
123     };
124 }
125 
126 pub type IoctlNr = c_ulong;
127 
128 /// Constructs an I/O control code.
129 ///
130 /// Shifts control code components into the appropriate bitfield locations:
131 /// <https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes>
ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> IoctlNr132 pub const fn ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> IoctlNr {
133     (device_type << 16) | (access << 14) | (function << 2) | method
134 }
135 
136 /// Run an ioctl with no arguments.
137 // (colindr) b/144457461 : This will probably not be used on windows.
138 // It's only used on linux for the ioctls that override the exit code to
139 // be the  return value of the ioctl. As far as I can tell, no DeviceIoControl
140 // will do this, they will always instead return values in the output
141 // buffer. So, as a result, we have no tests for this function, and
142 // we may want to remove it if we never use it on windows, but we can't
143 // remove it right now until we re-implement all the code that calls
144 // this funciton for windows.
145 /// # Safety
146 /// The caller is responsible for determining the safety of the particular ioctl.
147 /// This method should be safe as `DeviceIoControl` will handle error cases
148 /// and it does size checking.
ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int149 pub unsafe fn ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int {
150     let mut byte_ret: c_ulong = 0;
151     let ret = DeviceIoControl(
152         descriptor.as_raw_descriptor(),
153         nr,
154         null_mut(),
155         0,
156         null_mut(),
157         0,
158         &mut byte_ret,
159         null_mut(),
160     );
161 
162     if ret == 1 {
163         return 0;
164     }
165 
166     GetLastError() as i32
167 }
168 
169 /// Run an ioctl with a single value argument.
170 /// # Safety
171 /// The caller is responsible for determining the safety of the particular ioctl.
172 /// This method should be safe as `DeviceIoControl` will handle error cases
173 /// and it does size checking.
ioctl_with_val( descriptor: &dyn AsRawDescriptor, nr: IoctlNr, mut arg: c_ulong, ) -> c_int174 pub unsafe fn ioctl_with_val(
175     descriptor: &dyn AsRawDescriptor,
176     nr: IoctlNr,
177     mut arg: c_ulong,
178 ) -> c_int {
179     let mut byte_ret: c_ulong = 0;
180 
181     let ret = DeviceIoControl(
182         descriptor.as_raw_descriptor(),
183         nr,
184         &mut arg as *mut c_ulong as *mut c_void,
185         size_of::<c_ulong>() as u32,
186         null_mut(),
187         0,
188         &mut byte_ret,
189         null_mut(),
190     );
191 
192     if ret == 1 {
193         return 0;
194     }
195 
196     GetLastError() as i32
197 }
198 
199 /// Run an ioctl with an immutable reference.
200 /// # Safety
201 /// The caller is responsible for determining the safety of the particular ioctl.
202 /// Look at `ioctl_with_ptr` comments.
ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int203 pub unsafe fn ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int {
204     ioctl_with_ptr(descriptor, nr, arg)
205 }
206 
207 /// Run an ioctl with a mutable reference.
208 /// # Safety
209 /// The caller is responsible for determining the safety of the particular ioctl.
210 /// Look at `ioctl_with_ptr` comments.
ioctl_with_mut_ref<T>( descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &mut T, ) -> c_int211 pub unsafe fn ioctl_with_mut_ref<T>(
212     descriptor: &dyn AsRawDescriptor,
213     nr: IoctlNr,
214     arg: &mut T,
215 ) -> c_int {
216     ioctl_with_mut_ptr(descriptor, nr, arg)
217 }
218 
219 /// Run an ioctl with a raw pointer, specifying the size of the buffer.
220 /// # Safety
221 /// This method should be safe as `DeviceIoControl` will handle error cases
222 /// and it does size checking. Also The caller should make sure `T` is valid.
ioctl_with_ptr_sized<T>( descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: *const T, size: usize, ) -> c_int223 pub unsafe fn ioctl_with_ptr_sized<T>(
224     descriptor: &dyn AsRawDescriptor,
225     nr: IoctlNr,
226     arg: *const T,
227     size: usize,
228 ) -> c_int {
229     let mut byte_ret: c_ulong = 0;
230 
231     // We are trusting the DeviceIoControl function to not write anything
232     // to the input buffer. Just because it's a *const does not prevent
233     // the unsafe call from writing to it.
234     let ret = DeviceIoControl(
235         descriptor.as_raw_descriptor(),
236         nr,
237         arg as *mut c_void,
238         size as u32,
239         // We pass a null_mut as the output buffer.  If you expect
240         // an output, you should be calling the mut variant of this
241         // function.
242         null_mut(),
243         0,
244         &mut byte_ret,
245         null_mut(),
246     );
247 
248     if ret == 1 {
249         return 0;
250     }
251 
252     GetLastError() as i32
253 }
254 
255 /// Run an ioctl with a raw pointer.
256 /// # Safety
257 /// The caller is responsible for determining the safety of the particular ioctl.
258 /// This method should be safe as `DeviceIoControl` will handle error cases
259 /// and it does size checking. Also The caller should make sure `T` is valid.
ioctl_with_ptr<T>( descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: *const T, ) -> c_int260 pub unsafe fn ioctl_with_ptr<T>(
261     descriptor: &dyn AsRawDescriptor,
262     nr: IoctlNr,
263     arg: *const T,
264 ) -> c_int {
265     ioctl_with_ptr_sized(descriptor, nr, arg, size_of::<T>())
266 }
267 
268 /// Run an ioctl with a mutable raw pointer.
269 /// # Safety
270 /// The caller is responsible for determining the safety of the particular ioctl.
271 /// This method should be safe as `DeviceIoControl` will handle error cases
272 /// and it does size checking. Also The caller should make sure `T` is valid.
ioctl_with_mut_ptr<T>( descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: *mut T, ) -> c_int273 pub unsafe fn ioctl_with_mut_ptr<T>(
274     descriptor: &dyn AsRawDescriptor,
275     nr: IoctlNr,
276     arg: *mut T,
277 ) -> c_int {
278     let mut byte_ret: c_ulong = 0;
279 
280     let ret = DeviceIoControl(
281         descriptor.as_raw_descriptor(),
282         nr,
283         arg as *mut c_void,
284         size_of::<T>() as u32,
285         arg as *mut c_void,
286         size_of::<T>() as u32,
287         &mut byte_ret,
288         null_mut(),
289     );
290 
291     if ret == 1 {
292         return 0;
293     }
294 
295     GetLastError() as i32
296 }
297 
298 /// Run a DeviceIoControl, specifying all options, only available on windows
299 /// # Safety
300 /// This method should be safe as `DeviceIoControl` will handle error cases
301 /// for invalid paramters and takes input buffer and output buffer size
302 /// arguments. Also The caller should make sure `T` is valid.
device_io_control<F: AsRawDescriptor, T, T2>( descriptor: &F, nr: IoctlNr, input: *const T, inputsize: u32, output: *mut T2, outputsize: u32, byte_ret: &mut c_ulong, ) -> Result<()>303 pub unsafe fn device_io_control<F: AsRawDescriptor, T, T2>(
304     descriptor: &F,
305     nr: IoctlNr,
306     input: *const T,
307     inputsize: u32,
308     output: *mut T2,
309     outputsize: u32,
310     byte_ret: &mut c_ulong,
311 ) -> Result<()> {
312     let ret = DeviceIoControl(
313         descriptor.as_raw_descriptor(),
314         nr,
315         input as *mut c_void,
316         inputsize,
317         output as *mut c_void,
318         outputsize,
319         byte_ret,
320         null_mut(),
321     );
322 
323     if ret == 1 {
324         return Ok(());
325     }
326 
327     errno_result()
328 }
329 
330 #[cfg(test)]
331 mod tests {
332 
333     use std::ffi::OsStr;
334     use std::fs::File;
335     use std::fs::OpenOptions;
336     use std::io::prelude::*;
337     use std::os::raw::*;
338     use std::os::windows::ffi::OsStrExt;
339     use std::os::windows::prelude::*;
340     use std::ptr::null_mut;
341 
342     use tempfile::tempdir;
343     use winapi::um::fileapi::CreateFileW;
344     use winapi::um::fileapi::OPEN_EXISTING;
345     use winapi::um::winbase::SECURITY_SQOS_PRESENT;
346     use winapi::um::winioctl::FSCTL_GET_COMPRESSION;
347     use winapi::um::winioctl::FSCTL_SET_COMPRESSION;
348     use winapi::um::winnt::COMPRESSION_FORMAT_LZNT1;
349     use winapi::um::winnt::COMPRESSION_FORMAT_NONE;
350     use winapi::um::winnt::FILE_SHARE_READ;
351     use winapi::um::winnt::FILE_SHARE_WRITE;
352     use winapi::um::winnt::GENERIC_READ;
353     use winapi::um::winnt::GENERIC_WRITE;
354 
355     // helper func, returns str as Vec<u16>
to_u16s<S: AsRef<OsStr>>(s: S) -> std::io::Result<Vec<u16>>356     fn to_u16s<S: AsRef<OsStr>>(s: S) -> std::io::Result<Vec<u16>> {
357         Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
358     }
359 
360     #[cfg_attr(all(target_os = "windows", target_env = "gnu"), ignore)]
361     #[test]
ioct_get_and_set_compression()362     fn ioct_get_and_set_compression() {
363         let dir = tempdir().unwrap();
364         let file_path = dir.path().join("test.dat");
365         let file_path = file_path.as_path();
366 
367         // compressed = empty short for compressed status to be read into
368         let mut compressed: c_ushort = 0x0000;
369 
370         // open our random file and write "foo" in it
371         let mut f = OpenOptions::new()
372             .write(true)
373             .create_new(true)
374             .open(file_path)
375             .unwrap();
376         f.write_all(b"foo").expect("Failed to write bytes.");
377         f.sync_all().expect("Failed to sync all.");
378 
379         // read the compression status
380         // SAFETY: safe because return value is checked.
381         let ecode = unsafe {
382             super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
383         };
384 
385         // shouldn't error
386         assert_eq!(ecode, 0);
387         // should not be compressed by default (not sure if this will be the case on
388         // all machines...)
389         assert_eq!(compressed, COMPRESSION_FORMAT_NONE);
390 
391         // Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.
392         compressed = COMPRESSION_FORMAT_LZNT1;
393 
394         // NOTE: Theoretically I should be able to open this file like so:
395         // let mut f = OpenOptions::new()
396         //     .access_mode(GENERIC_WRITE|GENERIC_WRITE)
397         //     .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)
398         //     .open("test.dat").unwrap();
399         //
400         //   However, that does not work, and I'm not sure why.  Here's where
401         //   the underlying std code is doing a CreateFileW:
402         //   https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260
403         //   For now I'm just going to leave this test as-is.
404         //
405         // SAFETY: safe because return value is checked.
406         let f = unsafe {
407             File::from_raw_handle(CreateFileW(
408                 to_u16s(file_path).unwrap().as_ptr(),
409                 GENERIC_READ | GENERIC_WRITE,
410                 FILE_SHARE_READ | FILE_SHARE_WRITE,
411                 null_mut(),
412                 OPEN_EXISTING,
413                 // I read there's some security concerns if you don't use this
414                 SECURITY_SQOS_PRESENT,
415                 null_mut(),
416             ))
417         };
418 
419         let ecode =
420             // SAFETY: safe because return value is checked.
421             unsafe { super::super::ioctl::ioctl_with_ref(&f, FSCTL_SET_COMPRESSION, &compressed) };
422 
423         assert_eq!(ecode, 0);
424         // set compressed short back to 0 for reading purposes,
425         // otherwise we can't be sure we're the FSCTL_GET_COMPRESSION
426         // is writing anything to the compressed pointer.
427         compressed = 0;
428 
429         // SAFETY: safe because return value is checked.
430         let ecode = unsafe {
431             super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
432         };
433 
434         // now should be compressed
435         assert_eq!(ecode, 0);
436         assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);
437 
438         drop(f);
439         // clean up
440         dir.close().expect("Failed to close the temp directory.");
441     }
442 
443     #[cfg_attr(all(target_os = "windows", target_env = "gnu"), ignore)]
444     #[test]
ioctl_with_val()445     fn ioctl_with_val() {
446         let dir = tempdir().unwrap();
447         let file_path = dir.path().join("test.dat");
448         let file_path = file_path.as_path();
449 
450         // compressed = empty short for compressed status to be read into
451         // Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.
452         let mut compressed: c_ushort = COMPRESSION_FORMAT_LZNT1;
453 
454         // open our random file and write "foo" in it
455         let mut f = OpenOptions::new()
456             .write(true)
457             .create_new(true)
458             .open(file_path)
459             .unwrap();
460         f.write_all(b"foo").expect("Failed to write bytes.");
461         f.sync_all().expect("Failed to sync all.");
462 
463         // NOTE: Theoretically I should be able to open this file like so:
464         // let mut f = OpenOptions::new()
465         //     .access_mode(GENERIC_WRITE|GENERIC_WRITE)
466         //     .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)
467         //     .open("test.dat").unwrap();
468         //
469         //   However, that does not work, and I'm not sure why.  Here's where
470         //   the underlying std code is doing a CreateFileW:
471         //   https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260
472         //   For now I'm just going to leave this test as-is.
473         //
474         // SAFETY: safe because return value is checked.
475         let f = unsafe {
476             File::from_raw_handle(CreateFileW(
477                 to_u16s(file_path).unwrap().as_ptr(),
478                 GENERIC_READ | GENERIC_WRITE,
479                 FILE_SHARE_READ | FILE_SHARE_WRITE,
480                 null_mut(),
481                 OPEN_EXISTING,
482                 // I read there's some security concerns if you don't use this
483                 SECURITY_SQOS_PRESENT,
484                 null_mut(),
485             ))
486         };
487 
488         // now we call ioctl_with_val, which isn't particularly any more helpful than
489         // ioctl_with_ref except for the cases where the input is only a word long
490         // SAFETY: safe because return value is checked.
491         let ecode = unsafe {
492             super::super::ioctl::ioctl_with_val(&f, FSCTL_SET_COMPRESSION, compressed.into())
493         };
494 
495         assert_eq!(ecode, 0);
496         // set compressed short back to 0 for reading purposes,
497         // otherwise we can't be sure we're the FSCTL_GET_COMPRESSION
498         // is writing anything to the compressed pointer.
499         compressed = 0;
500 
501         // SAFETY: safe because return value is checked.
502         let ecode = unsafe {
503             super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
504         };
505 
506         // now should be compressed
507         assert_eq!(ecode, 0);
508         assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);
509 
510         drop(f);
511         // clean up
512         dir.close().expect("Failed to close the temp directory.");
513     }
514 }
515