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