Expand description
ioctl
s for Linux APIs.
This library provides a convenient way to bind to Linux ioctl
s.
It is intended to help with writing wrappers around driver functionality, and tries to mirror the syntax you’ll find in C headers closely.
§Example
Let’s wrap V4L2’s QUERYCAP
ioctl.
From linux/videodev2.h
:
struct v4l2_capability {
__u8 driver[16];
__u8 card[32];
__u8 bus_info[32];
__u32 version;
__u32 capabilities;
__u32 device_caps;
__u32 reserved[3];
};
// ...
#define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability)
use std::mem::MaybeUninit;
use linux_ioctl::*;
#[repr(C)]
struct Capability {
driver: [u8; 16],
card: [u8; 32],
bus_info: [u8; 32],
version: u32,
capabilities: u32,
device_caps: u32,
reserved: [u32; 3],
}
const VIDIOC_QUERYCAP: Ioctl<*mut Capability> = _IOR(b'V', 0);
// Use as follows:
let capability = unsafe {
let mut capability = MaybeUninit::uninit();
VIDIOC_QUERYCAP.ioctl(&fd, capability.as_mut_ptr())?;
capability.assume_init()
};
§Portability
Despite being about Linux APIs, and following the Linux convention for declaring ioctl codes,
this library should also work on other operating systems that implement a Linux-comparible
ioctl
-based API.
For example, FreeBSD implements a variety of compatible interfaces like evdev and V4L2.
§Safety
To safely perform an ioctl, the actual behavior of the kernel-side has to match the behavior
expected by userspace (which is encoded in the Ioctl
type).
To accomplish this, it is necessary that the Ioctl
was constructed correctly by the caller:
the direction, type, number, and argument type size are used to build the ioctl request code,
and the Rust type used as the ioctl argument has to match what the kernel expects.
If the argument is a pointer the kernel will read from or write to, the data behind the pointer
also has to be valid, of course (ioctl
s are arbitrary functions, so the same care is needed
as when binding to an arbitrary C function).
However, this is not, strictly speaking, sufficient to ensure safety:
several drivers and subsystems share the same ioctl “type” value, which may lead to an ioctl
request code that is interpreted differently, depending on which driver receives the request.
Since the ioctl request code encodes the size of the argument type, this operation is unlikely
to cause a fault when accessing memory, since both argument types have the same size, so the
ioctl
syscall may complete successfully instead of returning EFAULT
.
The result of this situation is that a type intended for data from one driver now has data from an entirely unrelated driver in it, which will likely cause UB, either because a validity invariant was violated by the data written to the structure, or because userspace will trust the kernel to only write valid data (including pointers) to the structure.
While it may technically be possible to tell which driver owns a given device file descriptor
by crawling /sys
or querying udev
, in practice this situation is deemed “sufficiently
unlikely to cause problems” and programs don’t bother with this.
One way to rule out this issue is to prevent arbitrary file descriptors from making their way
to the ioctl, and to ensure that only files that match the driver’s naming convention are used
for these ioctls.
For example, an evdev wrapper could refuse to operate on files outside of /dev/input
, and a
KVM API could always open /dev/kvm
without offering a safe API to act on a different device
file.
For more information, you can look at the list of ioctl groups here: https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html
TL;DR: don’t worry about it kitten :)
Structs§
Constants§
- _IOC_
NONE - Indicates that an ioctl neither reads nor writes data through its argument.
- _IOC_
READ - Indicates that an
ioctl
reads data from the kernel through its pointer argument. - _IOC_
READ_ WRITE - Indicates that an
ioctl
both reads and writes data through its pointer argument. - _IOC_
WRITE - Indicates that an
ioctl
writes data to the kernel through its pointer argument.
Functions§
- _IO
- Creates an
Ioctl
that doesn’t read or write any userspace data. - _IOC
- Manually constructs an
Ioctl
from its components. - _IOR
- Creates an
Ioctl
that reads data of typeT
from the kernel. - _IOW
- Creates an
Ioctl
that writes data of typeT
to the kernel. - _IOWR
- Creates an
Ioctl
that writes and reads data of typeT
.