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 expects an `int` argument to be
204    /// passed to `ioctl(2)`, 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<T> Ioctl<*const T> {
250    /// Changes the [`Ioctl`] argument type to be passed directly instead of behind a pointer.
251    ///
252    /// Does not change the request code.
253    ///
254    /// Many linux headers define `ioctl`s like `_IOW('U', 100, int)`, but then expect the `int`
255    /// argument to be passed as a direct argument to `ioctl(2)` instead of passing a pointer.
256    /// This method can be used to bind to these `ioctl`s.
257    ///
258    /// # Example
259    ///
260    /// `uinput` defines several `ioctl`s where this method is useful:
261    ///
262    /// ```c
263    /// #define UI_SET_EVBIT		_IOW(UINPUT_IOCTL_BASE, 100, int)
264    /// ```
265    ///
266    /// ```
267    /// use std::ffi::c_int;
268    /// use linux_ioctl::{Ioctl, _IOW};
269    ///
270    /// const UI_SET_EVBIT: Ioctl<c_int> = _IOW(b'U', 100).with_direct_arg();
271    /// ```
272    #[inline]
273    pub const fn with_direct_arg(self) -> Ioctl<T> {
274        self.with_arg()
275    }
276}
277
278impl Ioctl<NoArgs> {
279    /// Performs an *ioctl* that doesn't take an argument.
280    ///
281    /// On success, returns the value returned by the `ioctl` syscall. On error (when `ioctl`
282    /// returns -1), returns the error from *errno*.
283    ///
284    /// Note that the actual `ioctl(2)` call performed will pass 0 as a dummy argument to the
285    /// *ioctl*. This is because some Linux *ioctl*s are declared without an argument, but will fail
286    /// unless they receive 0 as their argument (eg. `KVM_GET_API_VERSION`). There should be no harm
287    /// in passing this argument unconditionally, as the kernel will typically just ignore excess
288    /// arguments.
289    ///
290    /// # Safety
291    ///
292    /// This method performs an arbitrary *ioctl* on an arbitrary file descriptor.
293    /// The caller has to ensure that any safety requirements of the *ioctl* are met, and that `fd`
294    /// belongs to the driver it expects.
295    pub unsafe fn ioctl(self, fd: &impl AsRawFd) -> io::Result<c_int> {
296        let res = unsafe { libc::ioctl(fd.as_raw_fd(), self.request as _, 0) };
297        if res == -1 {
298            Err(io::Error::last_os_error())
299        } else {
300            Ok(res)
301        }
302    }
303}
304
305impl<T> Ioctl<T> {
306    /// Performs an *ioctl* that takes an argument of type `T`.
307    ///
308    /// Returns the value returned by the `ioctl(2)` invocation, or an I/O error if the call failed.
309    ///
310    /// For many *ioctl*s, `T` will be a pointer to the actual argument.
311    /// The caller must ensure that it points to valid data that conforms to the requirements of the
312    /// *ioctl*.
313    ///
314    /// # Safety
315    ///
316    /// This method performs an arbitrary *ioctl* on an arbitrary file descriptor.
317    /// The caller has to ensure that any safety requirements of the *ioctl* are met, and that `fd`
318    /// belongs to the driver it expects.
319    pub unsafe fn ioctl(self, fd: &impl AsRawFd, arg: T) -> io::Result<c_int> {
320        let res = unsafe { libc::ioctl(fd.as_raw_fd(), self.request as _, arg) };
321        if res == -1 {
322            Err(io::Error::last_os_error())
323        } else {
324            Ok(res)
325        }
326    }
327}
328
329/// Indicates that an [`Ioctl`] does not take any arguments.
330///
331/// This is used as the type parameter of [`Ioctl`] by the [`_IO`] and [`_IOC`] functions.
332/// [`Ioctl<NoArgs>`] comes with its own, separate `IOCTL.ioctl(fd)` method that only takes the file
333/// descriptor as an argument.
334///
335/// Since [`NoArgs`] is the default value for [`Ioctl`]'s type parameter, it can typically be
336/// omitted.
337///
338/// # Example
339///
340/// The *uinput* ioctls `UI_DEV_CREATE` and `UI_DEV_DESTROY` do not take any arguments, while
341/// `UI_DEV_SETUP` *does* take an argument.
342///
343/// From `linux/uinput.h`:
344///
345/// ```c
346/// /* ioctl */
347/// #define UINPUT_IOCTL_BASE	'U'
348/// #define UI_DEV_CREATE		_IO(UINPUT_IOCTL_BASE, 1)
349/// #define UI_DEV_DESTROY		_IO(UINPUT_IOCTL_BASE, 2)
350/// ...
351/// #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup)
352/// ```
353///
354/// ```rust
355/// use std::{mem, fs::File, ffi::c_char};
356/// use libc::uinput_setup;
357/// use linux_ioctl::*;
358///
359/// const UINPUT_IOCTL_BASE: u8 = b'U';
360/// const UI_DEV_CREATE: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 1);
361/// const UI_DEV_DESTROY: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 2);
362/// const UI_DEV_SETUP: Ioctl<*const uinput_setup> = _IOW(UINPUT_IOCTL_BASE, 3);
363///
364/// let uinput = File::options().write(true).open("/dev/uinput")?;
365///
366/// let mut setup: libc::uinput_setup = unsafe { mem::zeroed() };
367/// setup.name[0] = b'A' as c_char; // (must not be blank)
368/// unsafe {
369///     UI_DEV_SETUP.ioctl(&uinput, &setup)?;
370///     UI_DEV_CREATE.ioctl(&uinput)?;
371///     // ...use the device...
372///     UI_DEV_DESTROY.ioctl(&uinput)?;
373/// }
374/// # std::io::Result::Ok(())
375/// ```
376pub struct NoArgs {
377    // Unsized type so that the `impl<T> Ioctl<T>` does not conflict.
378    _f: [u8],
379}
380
381/// Direction of an [`Ioctl`].
382///
383/// Used by [`_IOC`]. Constructed by using the constants [`_IOC_NONE`], [`_IOC_READ`], and
384/// [`_IOC_WRITE`].
385#[derive(Clone, Copy, PartialEq, Eq)]
386pub struct Dir(u32);
387
388impl BitOr for Dir {
389    type Output = Dir;
390
391    #[inline]
392    fn bitor(self, rhs: Self) -> Self::Output {
393        // `_IOC_NONE` is 0 on x86, but non-zero on other architectures. It is invalid and
394        // non-portable to combine it with other usages, so we prevent it here.
395        // This check will easily optimize out in almost all cases, since the direction is a
396        // compile-time constant.
397        if (self == _IOC_NONE && rhs != _IOC_NONE) || (self != _IOC_NONE && rhs == _IOC_NONE) {
398            panic!("`_IOC_NONE` cannot be combined with other values");
399        }
400
401        Self(self.0 | rhs.0)
402    }
403}
404
405impl fmt::Debug for Dir {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        if *self == _IOC_READ | _IOC_WRITE {
408            f.write_str("_IOC_READ | _IOC_WRITE")
409        } else if *self == _IOC_READ {
410            f.write_str("_IOC_READ")
411        } else if *self == _IOC_WRITE {
412            f.write_str("_IOC_WRITE")
413        } else if *self == _IOC_NONE {
414            f.write_str("_IOC_NONE")
415        } else {
416            write!(f, "{:#x}", self.0)
417        }
418    }
419}
420
421/// Indicates that an *ioctl* neither reads nor writes data through its argument.
422pub const _IOC_NONE: Dir = Dir(consts::_IOC_NONE);
423
424/// Indicates that an *ioctl* reads data through its pointer argument.
425pub const _IOC_READ: Dir = Dir(consts::_IOC_READ);
426
427/// Indicates that an *ioctl* writes data through its pointer argument.
428pub const _IOC_WRITE: Dir = Dir(consts::_IOC_WRITE);
429
430/// Indicates that an *ioctl* both reads and writes data through its pointer argument.
431///
432/// Equivalent to `_IOC_READ | _IOC_WRITE`, which doesn't work in `const` contexts. Does not have a
433/// C equivalent.
434pub const _IOC_READ_WRITE: Dir = Dir(consts::_IOC_READ | consts::_IOC_WRITE);
435
436// NB: these are bare `u32`s because `const` `BitOr` impls aren't possible on stable
437// (they're only used with `_IOC`, which is a somewhat niche API)
438
439/// Creates an [`Ioctl`] that doesn't read or write any userspace data.
440///
441/// This type of ioctl can return an `int` to userspace via the return value of the `ioctl` syscall.
442/// By default, the returned [`Ioctl`] takes no argument.
443/// [`Ioctl::with_arg`] can be used to pass a direct argument to the *ioctl*.
444///
445/// # Example
446///
447/// `KVM_GET_API_VERSION` is an *ioctl* that does not take any arguments. The API version is
448/// returned as the return value of the `ioctl(2)` function.
449///
450/// From `linux/kvm.h`:
451///
452/// ```c
453/// #define KVMIO 0xAE
454/// ...
455/// #define KVM_GET_API_VERSION       _IO(KVMIO,   0x00)
456/// ```
457///
458/// ```rust
459/// use std::fs::File;
460/// use linux_ioctl::*;
461///
462/// const KVMIO: u8 = 0xAE;
463/// const KVM_GET_API_VERSION: Ioctl<NoArgs> = _IO(KVMIO, 0x00);
464///
465/// let file = File::open("/dev/kvm")?;
466///
467/// let version = unsafe { KVM_GET_API_VERSION.ioctl(&file)? };
468/// println!("KVM API version: {version}");
469/// # std::io::Result::Ok(())
470/// ```
471#[allow(non_snake_case)]
472pub const fn _IO(ty: u8, nr: u8) -> Ioctl<NoArgs> {
473    _IOC(_IOC_NONE, ty, nr, 0)
474}
475
476/// Creates an [`Ioctl`] that reads data of type `T` from the kernel.
477///
478/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will fill the destination
479/// with data.
480///
481/// # Errors
482///
483/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
484/// argument size limit.
485/// This typically means that the wrong type `T` was specified.
486///
487/// # Examples
488///
489/// From `linux/random.h`:
490///
491/// ```c
492/// /* ioctl()'s for the random number generator */
493///
494/// /* Get the entropy count. */
495/// #define RNDGETENTCNT	_IOR( 'R', 0x00, int )
496/// ```
497///
498/// ```
499/// use std::fs::File;
500/// use std::ffi::c_int;
501/// use linux_ioctl::*;
502///
503/// const RNDGETENTCNT: Ioctl<*mut c_int> = _IOR(b'R', 0x00);
504///
505/// let file = File::open("/dev/urandom")?;
506///
507/// let mut entropy = 0;
508/// unsafe { RNDGETENTCNT.ioctl(&file, &mut entropy)? };
509///
510/// println!("{entropy} bits of entropy in /dev/urandom");
511/// # std::io::Result::Ok(())
512/// ```
513#[allow(non_snake_case)]
514pub const fn _IOR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
515    const {
516        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
517    }
518    _IOC(_IOC_READ, ty, nr, size_of::<T>())
519}
520
521/// Creates an [`Ioctl`] that writes data of type `T` to the kernel.
522///
523/// By default, a pointer to the data will be passed to `ioctl(2)`, and the kernel will read the
524/// argument from that location.
525/// This is generally correct if the argument is a `struct`, but if the argument is a primitive type
526/// like `int`, or is already a pointer like `char*`, many drivers expect the argument to be passed
527/// to `ioctl(2)` *without* indirection.
528/// To bind to those `ioctl`s, you can call [`Ioctl::with_direct_arg`] on the [`Ioctl`] returned by
529/// [`_IOW`].
530///
531/// # Errors
532///
533/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
534/// argument size limit.
535/// This typically means that the wrong type `T` was specified.
536///
537/// # Example
538///
539/// Let's create a virtual input device with *uinput*.
540///
541/// From `linux/uinput.h`:
542///
543/// ```c
544/// /* ioctl */
545/// #define UINPUT_IOCTL_BASE	'U'
546/// #define UI_DEV_CREATE		_IO(UINPUT_IOCTL_BASE, 1)
547/// #define UI_DEV_DESTROY		_IO(UINPUT_IOCTL_BASE, 2)
548/// ...
549/// #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup)
550/// ...
551/// #define UI_SET_EVBIT		_IOW(UINPUT_IOCTL_BASE, 100, int)
552/// #define UI_SET_KEYBIT		_IOW(UINPUT_IOCTL_BASE, 101, int)
553/// ```
554///
555/// From `linux/input.h`:
556///
557/// ```c
558/// #define EV_KEY			0x01
559/// ...
560/// #define KEY_A			30
561/// ```
562///
563/// ```rust
564/// use std::{mem, fs::File, ffi::{c_char, c_int}};
565/// use libc::uinput_setup;
566/// use linux_ioctl::*;
567///
568/// const UINPUT_IOCTL_BASE: u8 = b'U';
569/// const UI_DEV_CREATE: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 1);
570/// const UI_DEV_DESTROY: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 2);
571/// const UI_DEV_SETUP: Ioctl<*const uinput_setup> = _IOW(UINPUT_IOCTL_BASE, 3);
572/// // These two expect their argument to be passed directly instead of behind a pointer:
573/// const UI_SET_EVBIT: Ioctl<c_int> = _IOW(UINPUT_IOCTL_BASE, 100).with_direct_arg();
574/// const UI_SET_KEYBIT: Ioctl<c_int> = _IOW(UINPUT_IOCTL_BASE, 101).with_direct_arg();
575///
576/// const EV_KEY: c_int = 0x01;
577/// const KEY_A: c_int = 30;
578///
579/// let uinput = File::options().write(true).open("/dev/uinput")?;
580///
581/// // Enable the "A" key:
582/// unsafe {
583///     UI_SET_EVBIT.ioctl(&uinput, EV_KEY)?;
584///     UI_SET_KEYBIT.ioctl(&uinput, KEY_A)?;
585/// }
586///
587/// let mut setup: uinput_setup = unsafe { mem::zeroed() };
588/// setup.name[0] = b'A' as c_char; // (must not be blank)
589/// unsafe {
590///     UI_DEV_SETUP.ioctl(&uinput, &setup)?;
591///     UI_DEV_CREATE.ioctl(&uinput)?;
592///     // ...use the device...
593///     UI_DEV_DESTROY.ioctl(&uinput)?;
594/// }
595/// # std::io::Result::Ok(())
596/// ```
597#[allow(non_snake_case)]
598pub const fn _IOW<T>(ty: u8, nr: u8) -> Ioctl<*const T> {
599    const {
600        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
601    }
602    _IOC(_IOC_WRITE, ty, nr, size_of::<T>())
603}
604
605/// Creates an [`Ioctl`] that writes and reads data of type `T`.
606///
607/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will read and write to the
608/// data `T`.
609///
610/// # Errors
611///
612/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
613/// argument size limit.
614/// This typically means that the wrong type `T` was specified.
615#[allow(non_snake_case)]
616pub const fn _IOWR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
617    const {
618        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
619    }
620    _IOC(_IOC_READ_WRITE, ty, nr, size_of::<T>())
621}
622
623/// Manually constructs an [`Ioctl`] from its components.
624///
625/// Also see [`Ioctl::from_raw`] for a way to interface with "legacy" ioctls that don't yet follow
626/// this scheme.
627///
628/// Prefer to use [`_IO`], [`_IOR`], [`_IOW`], or [`_IOWR`] where possible.
629///
630/// # Arguments
631///
632/// - **`dir`**: Direction of the ioctl. One of [`_IOC_NONE`], [`_IOC_READ`], [`_IOC_WRITE`], or
633///   `_IOC_READ | _IOC_WRITE` (aka [`_IOC_READ_WRITE`]).
634/// - **`ty`**: the `ioctl` group or type to identify the driver or subsystem. You can find a list
635///   [here].
636/// - **`nr`**: the `ioctl` number within its group.
637/// - **`size`**: the size of the `ioctl`'s indirect argument. `ioctl`s that take an argument
638///   directly (without passing a pointer to it) typically set this to 0.
639///
640/// [here]: https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html
641///
642/// # Panics
643///
644/// This function may panic when `size` exceeds the (platform-specific) maximum parameter size.
645///
646/// # Example
647///
648/// `UI_GET_SYSNAME` is a polymorphic *ioctl* that can be invoked with a variety of buffer lengths.
649/// This function can be used to bind to it.
650///
651/// From `linux/uinput.h`:
652///
653/// ```c
654/// /* ioctl */
655/// #define UINPUT_IOCTL_BASE	'U'
656/// ...
657/// #define UI_GET_SYSNAME(len)	_IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len)
658/// ```
659///
660/// ```no_run
661/// use std::ffi::c_char;
662/// use linux_ioctl::*;
663///
664/// const UINPUT_IOCTL_BASE: u8 = b'U';
665/// const fn UI_GET_SYSNAME(len: usize) -> Ioctl<*mut c_char> {
666///     _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len)
667/// }
668///
669/// // Use it like this:
670/// unsafe {
671/// #   let fd = &123;
672///     let mut buffer = [0 as c_char; 16];
673///     UI_GET_SYSNAME(16).ioctl(fd, buffer.as_mut_ptr())?;
674/// }
675/// # std::io::Result::Ok(())
676/// ```
677#[allow(non_snake_case)]
678#[inline]
679pub const fn _IOC<T: ?Sized>(dir: Dir, ty: u8, nr: u8, size: usize) -> Ioctl<T> {
680    use consts::*;
681
682    assert!(size <= (_IOC_SIZEMASK as usize));
683
684    let request = (dir.0 << _IOC_DIRSHIFT)
685        | ((ty as u32) << _IOC_TYPESHIFT)
686        | ((nr as u32) << _IOC_NRSHIFT)
687        | ((size as u32) << _IOC_SIZESHIFT);
688    Ioctl::from_raw(request)
689}
690
691#[cfg(test)]
692mod tests {
693    use super::*;
694
695    #[test]
696    fn dir_or() {
697        assert_ne!(_IOC_NONE, _IOC_READ);
698        assert_ne!(_IOC_NONE, _IOC_WRITE);
699
700        assert_eq!(_IOC_READ | _IOC_WRITE, _IOC_READ_WRITE);
701        assert_eq!(_IOC_READ | _IOC_READ, _IOC_READ);
702        assert_eq!(_IOC_WRITE | _IOC_WRITE, _IOC_WRITE);
703        assert_eq!(_IOC_NONE | _IOC_NONE, _IOC_NONE);
704    }
705
706    #[test]
707    #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
708    fn dir_none_or_read() {
709        let _ = _IOC_NONE | _IOC_READ;
710    }
711
712    #[test]
713    #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
714    fn dir_none_or_write() {
715        let _ = _IOC_NONE | _IOC_WRITE;
716    }
717
718    #[test]
719    #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
720    fn dir_read_or_none() {
721        let _ = _IOC_READ | _IOC_NONE;
722    }
723
724    #[test]
725    #[should_panic(expected = "`_IOC_NONE` cannot be combined with other values")]
726    fn dir_write_or_none() {
727        let _ = _IOC_WRITE | _IOC_NONE;
728    }
729}