# iocuddle
`iocuddle` is a library for building runtime-safe `ioctl()` interfaces.
Existing approaches to interfacing with `ioctl`s from Rust rely on casting
and/or unsafe code declarations at the call site. This moves the burden of
safety to the consumer of the `ioctl` interface, which is less than ideal.
In contrast, `iocuddle` attempts to move the unsafe code burden to `ioctl`
definition. Once an `ioctl` is defined, all executions of that `ioctl` can
be done within safe code.
## Interfaces
`iocuddle` aims to handle >=99% of the kernel's `ioctl` interfaces.
However, we do not aim to handle all possible `ioctl` interfaces. We will
outline the different `ioctl` interfaces below.
### Classic Interfaces
Classic `ioctl` interfaces are those `ioctl`s which were created before
the modern interfaces we will see below. They basically allowed the full
usage of the `ioctl` libc function which is defined as this:
```rust
use std::os::raw::{c_int, c_ulong};
extern "C" { fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int; }
```
This interface can take any number of any type of arguments and can return
any positive integer (with `-1` reserved for indicating an error in
combination with `errno`).
One major drawback of this interface is that it entirely punts on compiler
checking of type safety. A particular `request` is implicitly associated
with one or more types that are usually listed in the relevant `ioctl` man
page. If the programmer gets any of the types wrong, you end up with
corrupted memory.
The problems with this interface were recognized early on. Therefore,
most `ioctl`s support only a single argument to reduce complexity. But
this does not solve the problem of the lack of compiler-enforced type
safety.
`iocuddle` does not currently support `ioctl`s with multiple arguments.
Otherwise, classic `ioctl` interfaces can be defined and used via the
`Ioctl::classic()` constructor as follows:
```rust
use std::os::raw::{c_void, c_int, c_uint};
use iocuddle::*;
// Ioctl::classic() constructor. This ioctl reads a C integer from the
// kernel by internally passing a reference to a c_int as the argument to
// the ioctl. This c_int is returned in the Ok status of the ioctl Result.
//
// Notice that since the state of the file descriptor is not modified via
// this ioctl, we define it using the Read parameter.
const TIOCINQ: Ioctl<Read, &c_int> = unsafe { Ioctl::classic(0x541B) };
assert_eq!(TIOCINQ.ioctl(&file).unwrap(), (0 as c_uint, 0 as c_int));
// This ioctl is similar to the previous one. It differs in two important
// respects. First, this raw ioctl takes an input argument rather than an
// output argument. This raw argument is a C integer *NOT* a reference to
// a C integer. Second, since this ioctl modifies the state of the file
// descriptor we use Write instead of Read.
//
// Notice that the return value of the TCSBRK.ioctl() call is the positive
// integer returned from the raw ioctl(), unlike the previous example. It
// is not the input argument type.
const TCSBRK: Ioctl<Write, c_int> = unsafe { Ioctl::classic(0x5409) };
assert_eq!(TCSBRK.ioctl(&mut file, 0).unwrap(), 0 as c_uint);
// `iocuddle` can also support classic ioctls with no argument. These
// always modify the file descriptor state, so the Write parameter is
// used.
const TIOCSBRK: Ioctl<Write, c_void> = unsafe { Ioctl::classic(0x5427) };
const TIOCCBRK: Ioctl<Write, c_void> = unsafe { Ioctl::classic(0x5428) };
assert_eq!(TIOCSBRK.ioctl(&mut file).unwrap(), 0);
assert_eq!(TIOCCBRK.ioctl(&mut file).unwrap(), 0);
```
### Modern Interfaces
In order to alleviate the type-safety problem with the classic interfaces,
the Linux kernel developed a new set of conventions for developing
`ioctl`s. We call these conventions the modern interface.
Modern `ioctl` interfaces always take a single reference to a struct or
integer and return `-1` on failure and `0` (or occasionally another
positive integer) on success. The `ioctl` request number is constructed
from four parameters:
* a `group` (confusingly called `type` in the kernel macros)
* a `nr` (number)
* a `direction`
* (the size of) a `type`
The `group` parameter is used as a namespace to group related `ioctl`s.
It is an integer value.
The `nr` parameter is an integer discriminator to uniquely identify the
`ioctl` within the `group`.
The `direction` parameter identifies which direction the data flows. If the
data flows from userspace to the kernel, this is the `write` `direction`.
If data flows from the kernel to userspace, this is the `read` `direction`.
Data which flows both ways is tagged with the `write/read` `direction`.
The `type` parameter identifies the type that should be used with this
`ioctl`. In the kernel C code this type is only directly used to perturb
the `ioctl` request number with the size of the type. `iocuddle`
additionally uses this parameter to provide type safety.
Defining modern `ioctl`s using `iocuddle` looks like this:
```rust
use iocuddle::*;
// Define the Group of KVM ioctls.
const KVM: Group = Group::new(0xAE);
// Define ioctls within the KVM group.
//
// The nr is passed to the direction-specific constructor.
const KVM_PPC_ALLOCATE_HTAB: Ioctl<WriteRead, &u32> = unsafe { KVM.write_read(0xa7) };
const KVM_X86_GET_MCE_CAP_SUPPORTED: Ioctl<Read, &u64> = unsafe { KVM.read(0x9d) };
const KVM_X86_SETUP_MCE: Ioctl<Write, &u64> = unsafe { KVM.write(0x9c) };
```
## Kernel Documentation
For the kernel documentation of the ioctl process, see the following file
in the kernel source tree: `Documentation/userspace-api/ioctl/ioctl-number.rst`
License: Apache-2.0