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