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