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}