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, io, marker::PhantomData, 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/// Indicates that an *ioctl* neither reads nor writes data through its argument.
353pub const _IOC_NONE: u32 = consts::_IOC_NONE;
354
355/// Indicates that an *ioctl* reads data through its pointer argument.
356pub const _IOC_READ: u32 = consts::_IOC_READ;
357
358/// Indicates that an *ioctl* writes data through its pointer argument.
359pub const _IOC_WRITE: u32 = consts::_IOC_WRITE;
360
361// NB: these are bare `u32`s because `const` `BitOr` impls aren't possible on stable
362// (they're only used with `_IOC`, which is a somewhat niche API)
363
364/// Creates an [`Ioctl`] that doesn't read or write any userspace data.
365///
366/// This type of ioctl can return an `int` to userspace via the return value of the `ioctl` syscall.
367/// By default, the returned [`Ioctl`] takes no argument.
368/// [`Ioctl::with_arg`] can be used to pass a direct argument to the *ioctl*.
369///
370/// # Example
371///
372/// `KVM_GET_API_VERSION` is an *ioctl* that does not take any arguments. The API version is
373/// returned as the return value of the `ioctl(2)` function.
374///
375/// From `linux/kvm.h`:
376///
377/// ```c
378/// #define KVMIO 0xAE
379/// ...
380/// #define KVM_GET_API_VERSION       _IO(KVMIO,   0x00)
381/// ```
382///
383/// ```rust
384/// use std::fs::File;
385/// use linux_ioctl::*;
386///
387/// const KVMIO: u8 = 0xAE;
388/// const KVM_GET_API_VERSION: Ioctl<NoArgs> = _IO(KVMIO, 0x00);
389///
390/// let file = File::open("/dev/kvm")?;
391///
392/// let version = unsafe { KVM_GET_API_VERSION.ioctl(&file)? };
393/// println!("KVM API version: {version}");
394/// # std::io::Result::Ok(())
395/// ```
396#[allow(non_snake_case)]
397pub const fn _IO(ty: u8, nr: u8) -> Ioctl<NoArgs> {
398    _IOC(_IOC_NONE, ty, nr, 0)
399}
400
401/// Creates an [`Ioctl`] that reads data of type `T` from the kernel.
402///
403/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will fill the destination
404/// with data.
405///
406/// # Errors
407///
408/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
409/// argument size limit.
410/// This typically means that the wrong type `T` was specified.
411///
412/// # Examples
413///
414/// From `linux/random.h`:
415///
416/// ```c
417/// /* ioctl()'s for the random number generator */
418///
419/// /* Get the entropy count. */
420/// #define RNDGETENTCNT	_IOR( 'R', 0x00, int )
421/// ```
422///
423/// ```
424/// use std::fs::File;
425/// use std::ffi::c_int;
426/// use linux_ioctl::*;
427///
428/// const RNDGETENTCNT: Ioctl<*mut c_int> = _IOR(b'R', 0x00);
429///
430/// let file = File::open("/dev/urandom")?;
431///
432/// let mut entropy = 0;
433/// unsafe { RNDGETENTCNT.ioctl(&file, &mut entropy)? };
434///
435/// println!("{entropy} bits of entropy in /dev/urandom");
436/// # std::io::Result::Ok(())
437/// ```
438#[allow(non_snake_case)]
439pub const fn _IOR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
440    const {
441        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
442    }
443    _IOC(_IOC_READ, ty, nr, size_of::<T>()).with_arg()
444}
445
446/// Creates an [`Ioctl`] that writes data of type `T` to the kernel.
447///
448/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will read the argument from
449/// that location.
450///
451/// # Errors
452///
453/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
454/// argument size limit.
455/// This typically means that the wrong type `T` was specified.
456///
457/// # Example
458///
459/// The *uinput* `ioctl` `UI_DEV_SETUP` can be invoked using [`_IOW`].
460///
461/// From `linux/uinput.h`:
462///
463/// ```c
464/// /* ioctl */
465/// #define UINPUT_IOCTL_BASE	'U'
466/// #define UI_DEV_CREATE		_IO(UINPUT_IOCTL_BASE, 1)
467/// #define UI_DEV_DESTROY		_IO(UINPUT_IOCTL_BASE, 2)
468/// ...
469/// #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup)
470/// ```
471///
472/// ```rust
473/// use std::{mem, fs::File, ffi::c_char};
474/// use libc::uinput_setup;
475/// use linux_ioctl::*;
476///
477/// const UINPUT_IOCTL_BASE: u8 = b'U';
478/// const UI_DEV_CREATE: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 1);
479/// const UI_DEV_DESTROY: Ioctl<NoArgs> = _IO(UINPUT_IOCTL_BASE, 2);
480/// const UI_DEV_SETUP: Ioctl<*const uinput_setup> = _IOW(UINPUT_IOCTL_BASE, 3);
481///
482/// let uinput = File::options().write(true).open("/dev/uinput")?;
483///
484/// let mut setup: libc::uinput_setup = unsafe { mem::zeroed() };
485/// setup.name[0] = b'A' as c_char; // (must not be blank)
486/// unsafe {
487///     UI_DEV_SETUP.ioctl(&uinput, &setup)?;
488///     UI_DEV_CREATE.ioctl(&uinput)?;
489///     // ...use the device...
490///     UI_DEV_DESTROY.ioctl(&uinput)?;
491/// }
492/// # std::io::Result::Ok(())
493/// ```
494#[allow(non_snake_case)]
495pub const fn _IOW<T>(ty: u8, nr: u8) -> Ioctl<*const T> {
496    const {
497        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
498    }
499    _IOC(_IOC_WRITE, ty, nr, size_of::<T>()).with_arg()
500}
501
502/// Creates an [`Ioctl`] that writes and reads data of type `T`.
503///
504/// A pointer to the data will be passed to `ioctl(2)`, and the kernel will read and write to the
505/// data `T`.
506///
507/// # Errors
508///
509/// This method will cause a compile-time assertion failure if the size of `T` exceeds the *ioctl*
510/// argument size limit.
511/// This typically means that the wrong type `T` was specified.
512#[allow(non_snake_case)]
513pub const fn _IOWR<T>(ty: u8, nr: u8) -> Ioctl<*mut T> {
514    const {
515        assert!(size_of::<T>() <= (_IOC_SIZEMASK as usize));
516    }
517    _IOC(_IOC_READ | _IOC_WRITE, ty, nr, size_of::<T>()).with_arg()
518}
519
520/// Manually constructs an [`Ioctl`] from its components.
521///
522/// Also see [`Ioctl::from_raw`] for a way to interface with "legacy" ioctls that don't yet follow
523/// this scheme.
524///
525/// Prefer to use [`_IO`], [`_IOR`], [`_IOW`], or [`_IOWR`] where possible.
526///
527/// # Arguments
528///
529/// - **`dir`**: *must* be one of [`_IOC_NONE`], [`_IOC_READ`], [`_IOC_WRITE`], or an ORed-together
530///   combination. 0 is **not** valid on all architectures.
531/// - **`ty`**: the `ioctl` group or type to identify the driver or subsystem. You can find a list
532///   [here].
533/// - **`nr`**: the `ioctl` number within its group.
534/// - **`size`**: the size of the `ioctl`'s indirect argument. `ioctl`s that take an argument
535///   directly (without passing a pointer to it) typically set this to 0.
536///
537/// [here]: https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html
538///
539/// # Panics
540///
541/// This function may panic when `dir` is not one of [`_IOC_NONE`], [`_IOC_READ`], [`_IOC_WRITE`],
542/// or an ORed-together combination of those constants.
543/// It may also panic when `size` exceeds the maximum parameter size.
544///
545/// # Example
546///
547/// `UI_GET_SYSNAME` is a polymorphic *ioctl* that can be invoked with a variety of buffer lengths.
548/// This function can be used to bind to it.
549///
550/// From `linux/uinput.h`:
551///
552/// ```c
553/// /* ioctl */
554/// #define UINPUT_IOCTL_BASE	'U'
555/// ...
556/// #define UI_GET_SYSNAME(len)	_IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len)
557/// ```
558///
559/// ```no_run
560/// use std::ffi::c_char;
561/// use linux_ioctl::*;
562///
563/// const UINPUT_IOCTL_BASE: u8 = b'U';
564/// const fn UI_GET_SYSNAME(len: usize) -> Ioctl<*mut c_char> {
565///     _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len).with_arg()
566/// }
567///
568/// // Use it like this:
569/// unsafe {
570/// #   let fd = &123;
571///     let mut buffer = [0 as c_char; 16];
572///     UI_GET_SYSNAME(16).ioctl(fd, buffer.as_mut_ptr())?;
573/// }
574/// # std::io::Result::Ok(())
575/// ```
576#[allow(non_snake_case)]
577#[inline]
578pub const fn _IOC(dir: u32, ty: u8, nr: u8, size: usize) -> Ioctl<NoArgs> {
579    use consts::*;
580
581    assert!(
582        dir & !(_IOC_NONE | _IOC_WRITE | _IOC_READ) == 0,
583        "`dir` must be a combination of `_IOC_NONE`, `_IOC_READ`, and `_IOC_WRITE`"
584    );
585    assert!(size <= (_IOC_SIZEMASK as usize));
586
587    let request = (dir << _IOC_DIRSHIFT)
588        | ((ty as u32) << _IOC_TYPESHIFT)
589        | ((nr as u32) << _IOC_NRSHIFT)
590        | ((size as u32) << _IOC_SIZESHIFT);
591    Ioctl::from_raw(request)
592}