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