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