• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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