• 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 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