linux_ioctl/lib.rs
1//! `ioctl`s for Linux APIs.
2//!
3//! This library provides a convenient way to bind to Linux `ioctl`s.
4//!
5//! It is intended to help with writing wrappers around driver functionality, and tries to mirror
6//! the syntax you'll find in C headers closely.
7//!
8//! # Example
9//!
10//! Let's wrap V4L2's `QUERYCAP` ioctl.
11//!
12//! From `linux/videodev2.h`:
13//!
14//! ```c
15//! struct v4l2_capability {
16//! __u8 driver[16];
17//! __u8 card[32];
18//! __u8 bus_info[32];
19//! __u32 version;
20//! __u32 capabilities;
21//! __u32 device_caps;
22//! __u32 reserved[3];
23//! };
24//! // ...
25//! #define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability)
26//! ```
27//!
28//! ```no_run
29//! use std::mem::MaybeUninit;
30//! use linux_ioctl::*;
31//!
32//! #[repr(C)]
33//! struct Capability {
34//! driver: [u8; 16],
35//! card: [u8; 32],
36//! bus_info: [u8; 32],
37//! version: u32,
38//! capabilities: u32,
39//! device_caps: u32,
40//! reserved: [u32; 3],
41//! }
42//!
43//! const VIDIOC_QUERYCAP: Ioctl<*mut Capability> = _IOR(b'V', 0);
44//!
45//! // Use as follows:
46//!
47//! # let fd = 123;
48//! let capability = unsafe {
49//! let mut capability = MaybeUninit::uninit();
50//! VIDIOC_QUERYCAP.ioctl(&fd, capability.as_mut_ptr())?;
51//! capability.assume_init()
52//! };
53//! # std::io::Result::Ok(())
54//! ```
55//!
56//! # Portability
57//!
58//! Despite being about Linux APIs, and following the Linux convention for declaring *ioctl* codes,
59//! this library should also work on other operating systems that implement a Linux-comparible
60//! `ioctl`-based API.
61//!
62//! For example, FreeBSD implements a variety of compatible interfaces like *evdev* and *V4L2*.
63//!
64//! # Safety
65//!
66//! To safely perform an *ioctl*, the actual behavior of the kernel-side has to match the behavior
67//! expected by userspace (which is encoded in the [`Ioctl`] type).
68//!
69//! To accomplish this, it is necessary that the [`Ioctl`] was constructed correctly by the caller:
70//! the direction, type, number, and argument type size are used to build the ioctl request code,
71//! and the Rust type used as the ioctl argument has to match what the kernel expects.
72//! If the argument is a pointer the kernel will read from or write to, the data behind the pointer
73//! also has to be valid, of course (`ioctl`s are arbitrary functions, so the same care is needed
74//! as when binding to an arbitrary C function).
75//!
76//! However, this is not, strictly speaking, *sufficient* to ensure safety:
77//! several drivers and subsystems share the same *ioctl* "type" value, which may lead to an ioctl
78//! request code that is interpreted differently, depending on which driver receives the request.
79//! Since the *ioctl* request code encodes the size of the argument type, this operation is unlikely
80//! to cause a fault when accessing memory, since both argument types have the same size, so the
81//! `ioctl` syscall may complete successfully instead of returning `EFAULT`.
82//!
83//! The result of this situation is that a type intended for data from one driver now has data from
84//! an entirely unrelated driver in it, which will likely cause UB, either because a *validity
85//! invariant* was violated by the data written to the structure, or because userspace will trust
86//! the kernel to only write valid data (including pointers) to the structure.
87//!
88//! While it may technically be possible to tell which driver owns a given device file descriptor
89//! by crawling `/sys` or querying `udev`, in practice this situation is deemed "sufficiently
90//! unlikely to cause problems" and programs don't bother with this.
91//!
92//! One way to rule out this issue is to prevent arbitrary file descriptors from making their way
93//! to the ioctl, and to ensure that only files that match the driver's naming convention are used
94//! for these ioctls.
95//! For example, an *evdev* wrapper could refuse to operate on files outside of `/dev/input`, and a
96//! KVM API could always open `/dev/kvm` without offering a safe API to act on a different device
97//! file.
98//!
99//! For more information, you can look at the list of ioctl groups here:
100//! <https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html>
101//!
102//! ***TL;DR**: don't worry about it kitten :)*
103
104#[doc = include_str!("../README.md")]
105mod readme {}
106
107mod consts;
108
109use std::{ffi::c_int, fmt, io, marker::PhantomData, ops::BitOr, os::fd::AsRawFd};
110
111use consts::_IOC_SIZEMASK;
112
113/// An *ioctl*.
114///
115/// [`Ioctl`] can represent *ioctl*s that take either no arguments or a single argument.
116/// If `T` is [`NoArgs`], the *ioctl* takes no arguments.
117/// For other values of `T`, the *ioctl* takes `T` as its only argument.
118/// Often, the argument `T` is a pointer or reference to a struct that contains the actual
119/// arguments.
120///
121/// While [`Ioctl`] cannot handle *ioctl*s that require passing more than one argument to the
122/// `ioctl(2)` function, Linux doesn't have any *ioctl*s that take more than one argument, and is
123/// unlikely to gain any in the future.
124///
125/// The [`Ioctl`] type is constructed with the free functions [`_IO`], [`_IOR`], [`_IOW`],
126/// [`_IOWR`], and [`_IOC`].
127/// For legacy *ioctl*s, it can also be created via [`Ioctl::from_raw`].
128pub struct Ioctl<T: ?Sized = NoArgs> {
129 request: u32,
130 _p: PhantomData<T>,
131}
132
133impl<T: ?Sized> Copy for Ioctl<T> {}
134impl<T: ?Sized> Clone for Ioctl<T> {
135 fn clone(&self) -> Self {
136 *self
137 }
138}
139
140impl<T: ?Sized> Ioctl<T> {
141 /// Creates an [`Ioctl`] object from a raw request code and an arbitrary argument type.
142 ///
143 /// This can be used for legacy *ioctl*s that were defined before the `_IOx` macros were
144 /// introduced.
145 ///
146 /// # Examples
147 ///
148 /// From `asm-generic/ioctls.h`:
149 ///
150 /// ```c
151 /// #define FIONREAD 0x541B
152 /// ```
153 ///
154 /// From `man 2const FIONREAD`:
155 ///
156 /// ```text
157 /// DESCRIPTION
158 /// FIONREAD
159 /// Get the number of bytes in the input buffer.
160 /// ...
161 /// SYNOPSIS
162 /// ...
163 /// int ioctl(int fd, FIONREAD, int *argp);
164 /// ...
165 /// ```
166 ///
167 /// ```
168 /// use std::io;
169 /// use std::fs::File;
170 /// use std::ffi::c_int;
171 /// use linux_ioctl::*;
172 ///
173 /// const FIONREAD: Ioctl<*mut c_int> = Ioctl::from_raw(0x541B);
174 ///
175 /// let file = File::open("/dev/ptmx")
176 /// .map_err(|e| io::Error::new(e.kind(), format!("failed to open `/dev/ptmx`: {e}")))?;
177 ///
178 /// let mut bytes = c_int::MAX;
179 /// unsafe { FIONREAD.ioctl(&file, &mut bytes)? };
180 /// assert_ne!(bytes, c_int::MAX);
181 ///
182 /// println!("{} bytes in input buffer", bytes);
183 /// # std::io::Result::Ok(())
184 /// ```
185 pub const fn from_raw(request: u32) -> Self {
186 Self {
187 request,
188 _p: PhantomData,
189 }
190 }
191
192 /// Changes the *ioctl* argument type to `T2`.
193 ///
194 /// This can be used for *ioctl*s that incorrectly declare their type, or for *ioctl*s that take
195 /// a by-value argument, rather than [`_IOW`]-type *ioctl*s that take their argument indirectly
196 /// through a pointer.
197 ///
198 /// Returns an [`Ioctl`] that passes an argument of type `T2` to the kernel, while using the
199 /// *ioctl* request code from `self`.
200 ///
201 /// # Examples
202 ///
203 /// The `KVM_CREATE_VM` *ioctl* is declared with [`_IO`], but takes an `int` as its argument,
204 /// specifying the VM type (`KVM_VM_*`).
205 ///
206 /// From `linux/kvm.h`:
207 ///
208 /// ```c
209 /// #define KVMIO 0xAE
210 /// ...
211 /// #define KVM_CREATE_VM _IO(KVMIO, 0x01) /* returns a VM fd */
212 /// ```
213 ///
214 /// ```no_run
215 /// use std::fs::File;
216 /// use std::ffi::c_int;
217 /// use linux_ioctl::*;
218 ///
219 /// const KVMIO: u8 = 0xAE;
220 /// const KVM_CREATE_VM: Ioctl<c_int> = _IO(KVMIO, 0x01).with_arg::<c_int>();
221 ///
222 /// // The `KVM_CREATE_VM` ioctl takes the VM type as an argument. 0 is a reasonable default on
223 /// // most architectures.
224 /// let vm_type: c_int = 0;
225 ///
226 /// let file = File::open("/dev/kvm")?;
227 ///
228 /// let vm_fd = unsafe { KVM_CREATE_VM.ioctl(&file, vm_type)? };
229 /// println!("created new VM with file descriptor {vm_fd}");
230 ///
231 /// unsafe { libc::close(vm_fd) };
232 /// # std::io::Result::Ok(())
233 /// ```
234 pub const fn with_arg<T2>(self) -> Ioctl<T2> {
235 Ioctl {
236 request: self.request,
237 _p: PhantomData,
238 }
239 }
240
241 /// Returns the *ioctl* request code.
242 ///
243 /// This is passed to `ioctl(2)` as its second argument.
244 pub fn request(self) -> u32 {
245 self.request
246 }
247}
248
249impl Ioctl<NoArgs> {
250 /// Performs an *ioctl* that doesn't take an argument.
251 ///
252 /// On success, returns the value returned by the `ioctl` syscall. On error (when `ioctl`
253 /// returns -1), returns the error from *errno*.
254 ///
255 /// Note that the actual `ioctl(2)` call performed will pass 0 as a dummy argument to the
256 /// *ioctl*. This is because some Linux *ioctl*s are declared without an argument, but will fail
257 /// unless they receive 0 as their argument (eg. `KVM_GET_API_VERSION`). There should be no harm
258 /// in passing this argument unconditionally, as the kernel will typically just ignore excess
259 /// arguments.
260 ///
261 /// # Safety
262 ///
263 /// This method performs an arbitrary *ioctl* on an arbitrary file descriptor.
264 /// The caller has to ensure that any safety requirements of the *ioctl* are met, and that `fd`
265 /// belongs to the driver it expects.
266 pub unsafe fn ioctl(self, fd: &impl AsRawFd) -> io::Result<c_int> {
267 let res = unsafe { libc::ioctl(fd.as_raw_fd(), self.request as _, 0) };
268 if res == -1 {
269 Err(io::Error::last_os_error())
270 } else {
271 Ok(res)
272 }
273 }
274}
275
276impl<T> Ioctl<T> {
277 /// Performs an *ioctl* that takes an argument of type `T`.
278 ///
279 /// Returns the value returned by the `ioctl(2)` invocation, or an I/O error if the call failed.
280 ///
281 /// For many *ioctl*s, `T` will be a pointer to the actual argument.
282 /// The caller must ensure that it points to valid data that conforms to the requirements of the
283 /// *ioctl*.
284 ///
285 /// # Safety
286 ///
287 /// This method performs an arbitrary *ioctl* on an arbitrary file descriptor.
288 /// The caller has to ensure that any safety requirements of the *ioctl* are met, and that `fd`
289 /// belongs to the driver it expects.
290 pub unsafe fn ioctl(self, fd: &impl AsRawFd, arg: T) -> io::Result<c_int> {
291 let res = unsafe { libc::ioctl(fd.as_raw_fd(), self.request as _, arg) };
292 if res == -1 {
293 Err(io::Error::last_os_error())
294 } else {
295 Ok(res)
296 }
297 }
298}
299
300/// Indicates that an [`Ioctl`] does not take any arguments.
301///
302/// This is used as the type parameter of [`Ioctl`] by the [`_IO`] and [`_IOC`] functions.
303/// [`Ioctl<NoArgs>`] comes with its own, separate `IOCTL.ioctl(fd)` method that only takes the file
304/// descriptor as an argument.
305///
306/// Since [`NoArgs`] is the default value for [`Ioctl`]'s type parameter, it can typically be
307/// omitted.
308///
309/// # Example
310///
311/// The *uinput* ioctls `UI_DEV_CREATE` and `UI_DEV_DESTROY` do not take any arguments, while
312/// `UI_DEV_SETUP` *does* take an argument.
313///
314/// From `linux/uinput.h`:
315///
316/// ```c
317/// /* ioctl */
318/// #define UINPUT_IOCTL_BASE 'U'
319/// #define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1)
320/// #define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2)
321/// ...
322/// #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup)
323/// ```
324///
325/// ```rust
326/// use std::{mem, fs::File, ffi::c_char};
327/// use libc::uinput_setup;
328/// use linux_ioctl::*;
329///
330/// const UINPUT_IOCTL_BASE: u8 = b'U';
331/// const UI_DEV_CREATE: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 1);
332/// const UI_DEV_DESTROY: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 2);
333/// const UI_DEV_SETUP: Ioctl<*const uinput_setup> = _IOW(UINPUT_IOCTL_BASE, 3);
334///
335/// let uinput = File::options().write(true).open("/dev/uinput")?;
336///
337/// let mut setup: libc::uinput_setup = unsafe { mem::zeroed() };
338/// setup.name[0] = b'A' as c_char; // (must not be blank)
339/// unsafe {
340/// UI_DEV_SETUP.ioctl(&uinput, &setup)?;
341/// UI_DEV_CREATE.ioctl(&uinput)?;
342/// // ...use the device...
343/// UI_DEV_DESTROY.ioctl(&uinput)?;
344/// }
345/// # std::io::Result::Ok(())
346/// ```
347pub struct NoArgs {
348 // Unsized type so that the `impl<T> Ioctl<T>` does not conflict.
349 _f: [u8],
350}
351
352/// Direction of an [`Ioctl`].
353///
354/// Used by [`_IOC`]. Constructed by using the constants [`_IOC_NONE`], [`_IOC_READ`], and
355/// [`_IOC_WRITE`].
356#[derive(Clone, Copy, PartialEq, Eq)]
357pub struct Dir(u32);
358
359impl BitOr for Dir {
360 type Output = Dir;
361
362 #[inline]
363 fn bitor(self, rhs: Self) -> Self::Output {
364 // `_IOC_NONE` is 0 on x86, but non-zero on other architectures. It is invalid and
365 // non-portable to combine it with other usages, so we check for that here.
366 if (self == _IOC_NONE && rhs != _IOC_NONE) || (self != _IOC_NONE && rhs == _IOC_NONE) {
367 panic!("`_IOC_NONE` cannot be combined with other values");
368 }
369 Self(self.0 | rhs.0)
370 }
371}
372
373impl fmt::Debug for Dir {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 if *self == _IOC_READ | _IOC_WRITE {
376 f.write_str("_IOC_READ | _IOC_WRITE")
377 } else if *self == _IOC_READ {
378 f.write_str("_IOC_READ")
379 } else if *self == _IOC_WRITE {
380 f.write_str("_IOC_WRITE")
381 } else if *self == _IOC_NONE {
382 f.write_str("_IOC_NONE")
383 } else {
384 write!(f, "{:#x}", self.0)
385 }
386 }
387}
388
389/// Indicates that an *ioctl* neither reads nor writes data through its argument.
390pub const _IOC_NONE: Dir = Dir(consts::_IOC_NONE);
391
392/// Indicates that an *ioctl* reads data through its pointer argument.
393pub const _IOC_READ: Dir = Dir(consts::_IOC_READ);
394
395/// Indicates that an *ioctl* writes data through its pointer argument.
396pub const _IOC_WRITE: Dir = Dir(consts::_IOC_WRITE);
397
398/// Indicates that an *ioctl* both reads and writes data through its pointer argument.
399///
400/// Equivalent to `_IOC_READ | _IOC_WRITE`, which doesn't work in `const` contexts. Does not have a
401/// C equivalent.
402pub const _IOC_READ_WRITE: Dir = Dir(consts::_IOC_READ | consts::_IOC_WRITE);
403
404// NB: these are bare `u32`s because `const` `BitOr` impls aren't possible on stable
405// (they're only used with `_IOC`, which is a somewhat niche API)
406
407/// Creates an [`Ioctl`] that doesn't read or write any userspace data.
408///
409/// This type of ioctl can return an `int` to userspace via the return value of the `ioctl` syscall.
410/// By default, the returned [`Ioctl`] takes no argument.
411/// [`Ioctl::with_arg`] can be used to pass a direct argument to the *ioctl*.
412///
413/// # Example
414///
415/// `KVM_GET_API_VERSION` is an *ioctl* that does not take any arguments. The API version is
416/// returned as the return value of the `ioctl(2)` function.
417///
418/// From `linux/kvm.h`:
419///
420/// ```c
421/// #define KVMIO 0xAE
422/// ...
423/// #define KVM_GET_API_VERSION _IO(KVMIO, 0x00)
424/// ```
425///
426/// ```rust
427/// use std::fs::File;
428/// use linux_ioctl::*;
429///
430/// const KVMIO: u8 = 0xAE;
431/// const KVM_GET_API_VERSION: Ioctl<NoArgs> = _IO(KVMIO, 0x00);
432///
433/// let file = File::open("/dev/kvm")?;
434///
435/// let version = unsafe { KVM_GET_API_VERSION.ioctl(&file)? };
436/// println!("KVM API version: {version}");
437/// # std::io::Result::Ok(())
438/// ```
439#[allow(non_snake_case)]
440pub const fn _IO(ty: u8, nr: u8) -> Ioctl<NoArgs> {
441 _IOC(_IOC_NONE, ty, nr, 0)
442}
443
444/// Creates an [`Ioctl`] that reads data of type `T` from the kernel.
445///
446/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will fill the destination
447/// with data.
448///
449/// # Errors
450///
451/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
452/// argument size limit.
453/// This typically means that the wrong type `T` was specified.
454///
455/// # Examples
456///
457/// From `linux/random.h`:
458///
459/// ```c
460/// /* ioctl()'s for the random number generator */
461///
462/// /* Get the entropy count. */
463/// #define RNDGETENTCNT _IOR( 'R', 0x00, int )
464/// ```
465///
466/// ```
467/// use std::fs::File;
468/// use std::ffi::c_int;
469/// use linux_ioctl::*;
470///
471/// const RNDGETENTCNT: Ioctl<*mut c_int> = _IOR(b'R', 0x00);
472///
473/// let file = File::open("/dev/urandom")?;
474///
475/// let mut entropy = 0;
476/// unsafe { RNDGETENTCNT.ioctl(&file, &mut entropy)? };
477///
478/// println!("{entropy} bits of entropy in /dev/urandom");
479/// # std::io::Result::Ok(())
480/// ```
481#[allow(non_snake_case)]
482pub const fn _IOR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
483 const {
484 assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
485 }
486 _IOC(_IOC_READ, ty, nr, size_of::<T>())
487}
488
489/// Creates an [`Ioctl`] that writes data of type `T` to the kernel.
490///
491/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will read the argument from
492/// that location.
493///
494/// # Errors
495///
496/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
497/// argument size limit.
498/// This typically means that the wrong type `T` was specified.
499///
500/// # Example
501///
502/// The *uinput* `ioctl` `UI_DEV_SETUP` can be invoked using [`_IOW`].
503///
504/// From `linux/uinput.h`:
505///
506/// ```c
507/// /* ioctl */
508/// #define UINPUT_IOCTL_BASE 'U'
509/// #define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1)
510/// #define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2)
511/// ...
512/// #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup)
513/// ```
514///
515/// ```rust
516/// use std::{mem, fs::File, ffi::c_char};
517/// use libc::uinput_setup;
518/// use linux_ioctl::*;
519///
520/// const UINPUT_IOCTL_BASE: u8 = b'U';
521/// const UI_DEV_CREATE: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 1);
522/// const UI_DEV_DESTROY: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 2);
523/// const UI_DEV_SETUP: Ioctl<*const uinput_setup> = _IOW(UINPUT_IOCTL_BASE, 3);
524///
525/// let uinput = File::options().write(true).open("/dev/uinput")?;
526///
527/// let mut setup: libc::uinput_setup = unsafe { mem::zeroed() };
528/// setup.name[0] = b'A' as c_char; // (must not be blank)
529/// unsafe {
530/// UI_DEV_SETUP.ioctl(&uinput, &setup)?;
531/// UI_DEV_CREATE.ioctl(&uinput)?;
532/// // ...use the device...
533/// UI_DEV_DESTROY.ioctl(&uinput)?;
534/// }
535/// # std::io::Result::Ok(())
536/// ```
537#[allow(non_snake_case)]
538pub const fn _IOW<T>(ty: u8, nr: u8) -> Ioctl<*const T> {
539 const {
540 assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
541 }
542 _IOC(_IOC_WRITE, ty, nr, size_of::<T>())
543}
544
545/// Creates an [`Ioctl`] that writes and reads data of type `T`.
546///
547/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will read and write to the
548/// data `T`.
549///
550/// # Errors
551///
552/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
553/// argument size limit.
554/// This typically means that the wrong type `T` was specified.
555#[allow(non_snake_case)]
556pub const fn _IOWR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
557 const {
558 assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
559 }
560 _IOC(_IOC_READ_WRITE, ty, nr, size_of::<T>())
561}
562
563/// Manually constructs an [`Ioctl`] from its components.
564///
565/// Also see [`Ioctl::from_raw`] for a way to interface with "legacy" ioctls that don't yet follow
566/// this scheme.
567///
568/// Prefer to use [`_IO`], [`_IOR`], [`_IOW`], or [`_IOWR`] where possible.
569///
570/// # Arguments
571///
572/// - **`dir`**: Direction of the ioctl. One of [`_IOC_NONE`], [`_IOC_READ`], [`_IOC_WRITE`], or
573/// `_IOC_READ | _IOC_WRITE` (aka [`_IOC_READ_WRITE`]).
574/// - **`ty`**: the `ioctl` group or type to identify the driver or subsystem. You can find a list
575/// [here].
576/// - **`nr`**: the `ioctl` number within its group.
577/// - **`size`**: the size of the `ioctl`'s indirect argument. `ioctl`s that take an argument
578/// directly (without passing a pointer to it) typically set this to 0.
579///
580/// [here]: https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html
581///
582/// # Panics
583///
584/// This function may panic when `dir` is not one of [`_IOC_NONE`], [`_IOC_READ`], [`_IOC_WRITE`],
585/// or an ORed-together combination of those constants.
586/// It may also panic when `size` exceeds the maximum parameter size.
587///
588/// # Example
589///
590/// `UI_GET_SYSNAME` is a polymorphic *ioctl* that can be invoked with a variety of buffer lengths.
591/// This function can be used to bind to it.
592///
593/// From `linux/uinput.h`:
594///
595/// ```c
596/// /* ioctl */
597/// #define UINPUT_IOCTL_BASE 'U'
598/// ...
599/// #define UI_GET_SYSNAME(len) _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len)
600/// ```
601///
602/// ```no_run
603/// use std::ffi::c_char;
604/// use linux_ioctl::*;
605///
606/// const UINPUT_IOCTL_BASE: u8 = b'U';
607/// const fn UI_GET_SYSNAME(len: usize) -> Ioctl<*mut c_char> {
608/// _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len)
609/// }
610///
611/// // Use it like this:
612/// unsafe {
613/// # let fd = &123;
614/// let mut buffer = [0 as c_char; 16];
615/// UI_GET_SYSNAME(16).ioctl(fd, buffer.as_mut_ptr())?;
616/// }
617/// # std::io::Result::Ok(())
618/// ```
619#[allow(non_snake_case)]
620#[inline]
621pub const fn _IOC<T: ?Sized>(dir: Dir, ty: u8, nr: u8, size: usize) -> Ioctl<T> {
622 use consts::*;
623
624 assert!(size <= (_IOC_SIZEMASK as usize));
625
626 let request = (dir.0 << _IOC_DIRSHIFT)
627 | ((ty as u32) << _IOC_TYPESHIFT)
628 | ((nr as u32) << _IOC_NRSHIFT)
629 | ((size as u32) << _IOC_SIZESHIFT);
630 Ioctl::from_raw(request)
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn dir_or() {
639 assert_ne!(_IOC_NONE, _IOC_READ);
640 assert_ne!(_IOC_NONE, _IOC_WRITE);
641
642 assert_eq!(_IOC_READ | _IOC_WRITE, _IOC_READ_WRITE);
643 assert_eq!(_IOC_READ | _IOC_READ, _IOC_READ);
644 assert_eq!(_IOC_WRITE | _IOC_WRITE, _IOC_WRITE);
645 assert_eq!(_IOC_NONE | _IOC_NONE, _IOC_NONE);
646 }
647
648 #[test]
649 #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
650 fn dir_none_or_read() {
651 let _ = _IOC_NONE | _IOC_READ;
652 }
653
654 #[test]
655 #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
656 fn dir_none_or_write() {
657 let _ = _IOC_NONE | _IOC_WRITE;
658 }
659
660 #[test]
661 #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
662 fn dir_read_or_none() {
663 let _ = _IOC_READ | _IOC_NONE;
664 }
665
666 #[test]
667 #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
668 fn dir_write_or_none() {
669 let _ = _IOC_WRITE | _IOC_NONE;
670 }
671}