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