linux_io/fd/
ioctl.rs

1//! A safer abstraction for `ioctl`.
2//!
3//! The `ioctl` system call is very open-ended, supporting a variety of
4//! different operations with different argument and result types, with the
5//! valid operations varying drastically based on which underlying driver or
6//! type of device the file represents. Passing the wrong kind of value to the
7//! wrong request can cause memory corruption.
8//!
9//! To make `ioctl` a _little_ safer to use, this module provides the building
10//! blocks for request constants that also include information about the
11//! argument and result types, so that the [`super::File::ioctl`] method can
12//! then provide a type-safe interface as long as these constants are defined
13//! correctly. [`IoctlReq`] implementations can be defined in other crates that
14//! provide support for particular device types or device drivers.
15
16use core::mem::MaybeUninit;
17
18use linux_unsafe::args::AsRawV;
19use linux_unsafe::{int, ulong};
20
21/// Represents a device type that can have `ioctl` requests implemented for it.
22///
23/// Implementations of this type should typically be zero-sized types, because
24/// they are used only as markers for annotating `File` objects using
25/// [`super::File::to_device`].
26///
27/// This is just a marker trait used to check type safety for
28/// [`super::File::ioctl`]; it has no behavior of its own and merely permits
29/// the safe abstraction to block trying to use ioctl requests intended for
30/// another device type which might therefore cause memory corruption if the
31/// argument type is incorrect.
32pub trait IoDevice {}
33
34/// Implemented by [`IoDevice`] implementations that are "sub-devices" of
35/// another device, specified as type parameter `Parent`.
36///
37/// This is just a marker trait used to check type safety for
38/// [`super::File::ioctl`]; it has no behavior of its own and merely permits
39/// the safe abstraction to send requests that were intended for the parent
40/// device type to files representing the implementer (the "child").
41///
42/// Devices don't actually need to form a strict tree. It is fine for a device
43/// type to have multiple parents, or for the structure implied by this trait
44/// to not resemble a tree at all, as long as the safety guarantees are met.
45/// If A is parent of B and B is parent of C then A is not _automatically_
46/// parent of C unless also explicitly implemented.
47///
48/// **Safety:** The implementer must be able to safely accept all of the
49/// `ioctl` requests of `Parent` without compromising memory safety. The
50/// parent requests do not necessarily need to _succeed_; it is sufficient
51/// for them to fail safely without making any use of the given argument.
52pub unsafe trait SubDevice<Parent: IoDevice>: IoDevice {}
53
54/// Any `IoDevice` type can by definition safely accept its own `ioctl` requests,
55/// assuming that those requests are themselves defined per the safety
56/// requirements of [`IoctlReq`].
57unsafe impl<T: IoDevice> SubDevice<T> for T {}
58
59/// Represents a particular request that can be issue with the `ioctl` system call.
60///
61/// Safety: Implementers must ensure that they only generate valid combinations
62/// of `ioctl` request and raw argument.
63pub unsafe trait IoctlReq<'a, Device: IoDevice>: Copy {
64    /// The type that the caller will provide when using this `ioctl` command.
65    ///
66    /// Use `()` for requests that don't need a caller-provided argument, such
67    /// as those which only return some data.
68    type ExtArg
69    where
70        Self: 'a;
71
72    /// The type of some temporary memory that the request needs to do its
73    /// work.
74    ///
75    /// For a request that only returns data, this will typically
76    /// describe the layout of the returned data, which the kernel will
77    /// then populate. For requests that don't need this, use `()`.
78    type TempMem;
79
80    /// The type of argument that will be passed to the raw system call.
81    type RawArg: AsRawV;
82
83    /// The type of the result of the `fcntl` call.
84    type Result
85    where
86        Self: 'a;
87
88    /// Prepare the `cmd` and `arg` values for a `ioctl` system call.
89    ///
90    /// The `arg` parameter is the argument provided by the caller of the
91    /// [`super::File::ioctl`] function. `temp_mem` is a reference to
92    /// uninitialized memory of appropriate size and alignment for
93    /// [`Self::TempMem`], which the implementer can either leave uninitialized
94    /// for the kernel to populate or pre-initialize with data the
95    /// kernel will expect to find there.
96    fn prepare_ioctl_args(
97        &self,
98        arg: &Self::ExtArg,
99        temp_mem: &mut MaybeUninit<Self::TempMem>,
100    ) -> (ulong, Self::RawArg);
101
102    /// Prepare a raw successful result from a `ioctl` call to be returned.
103    fn prepare_ioctl_result(
104        &self,
105        raw: int,
106        arg: &Self::ExtArg,
107        temp_mem: &MaybeUninit<Self::TempMem>,
108    ) -> Self::Result;
109}
110
111/// Constructs a new read-only [`IoctlReq`] with a fixed request code that
112/// passes no payload to `ioctl` and returns its result in the system call
113/// return value.
114///
115/// Safety: Callers must ensure that the given `request` is valid.
116pub const unsafe fn ioctl_no_arg<Device, Result>(request: ulong) -> IoctlReqNoArgs<Device, Result>
117where
118    *mut Result: AsRawV,
119    Device: IoDevice,
120    Result: FromIoctlResult<int>,
121{
122    IoctlReqNoArgs::<Device, Result> {
123        request,
124        _phantom: core::marker::PhantomData,
125    }
126}
127
128/// Constructs a new read-only [`IoctlReq`] with a fixed request code that
129/// passes a constant integer to `ioctl` and returns its result in the system
130/// call return value.
131///
132/// Safety: Callers must ensure that the given `request` is valid.
133pub const unsafe fn ioctl_const_arg<Device, Result, const ARG: int>(
134    request: ulong,
135) -> IoctlReqConstArg<Device, Result, ARG>
136where
137    *mut Result: AsRawV,
138    Device: IoDevice,
139    Result: FromIoctlResult<int>,
140{
141    IoctlReqConstArg::<Device, Result, ARG> {
142        request,
143        _phantom: core::marker::PhantomData,
144    }
145}
146
147/// Constructs a new read-only [`IoctlReq`] with a fixed request code and
148/// a result type that maps directly to the data the kernel will
149/// provide.
150///
151/// Safety: Callers must ensure that the given `request` is valid, that
152/// type `Arg` describes what this request expects to get a pointer to, and
153/// that the kernel will populate the given `Arg` object with data that is
154/// consistent with Rust's expectations for the given type.
155pub const unsafe fn ioctl_read<Device, Result>(request: ulong) -> IoctlReqRead<Device, Result>
156where
157    *mut Result: AsRawV,
158    Device: IoDevice,
159    Result: Copy,
160{
161    IoctlReqRead::<Device, Result> {
162        request,
163        _phantom: core::marker::PhantomData,
164    }
165}
166
167/// Constructs a new write-only [`IoctlReq`] with a fixed request code and
168/// an argument type that the data the kernel expects to recieve directly as
169/// its argument, without any pointer indirection.
170///
171/// Safety: Callers must ensure that the given `request` is valid, that
172/// type `Arg` describes what this request expects to get as its argument.
173pub const unsafe fn ioctl_write_val<Device, Arg, Result>(
174    request: ulong,
175) -> IoctlReqWriteVal<Device, Arg, Result>
176where
177    Arg: AsRawV,
178    Device: IoDevice,
179    Result: FromIoctlResult<int>,
180{
181    IoctlReqWriteVal::<Device, Arg, Result> {
182        request,
183        _phantom: core::marker::PhantomData,
184    }
185}
186
187/// Constructs a new write-only [`IoctlReq`] with a fixed request code and
188/// an argument type that maps directly to the data the kernel expects
189/// to receive a pointer to.
190///
191/// Safety: Callers must ensure that the given `request` is valid, that
192/// type `Arg` describes what this request expects to get a pointer to, and
193/// that it isn't possible for any value of that type to cause the kernel
194/// to violate memory safety. In particular, the kernel must not modify
195/// the given memory, because the safe caller will provide a shared reference.
196pub const unsafe fn ioctl_write<Device, Arg, Result>(
197    request: ulong,
198) -> IoctlReqWrite<Device, Arg, Result>
199where
200    *const Arg: AsRawV,
201    Device: IoDevice,
202    Result: FromIoctlResult<int>,
203{
204    IoctlReqWrite::<Device, Arg, Result> {
205        request,
206        _phantom: core::marker::PhantomData,
207    }
208}
209
210/// Constructs a new write/read [`IoctlReq`] with a fixed request code
211/// and an argument type that maps directly to the data the kernel
212/// expects to recieve a pointer to.
213///
214/// Safety: Callers must ensure that the given `request` is valid, that
215/// type `Arg` describes what this request expects to get a mutable pointer to,
216/// and that it isn't possible for any value of that type to cause the kernel
217/// to violate memory safety. In particular, the kernel must only write
218/// valid bit patterns into the object that the pointer refers to.
219pub const unsafe fn ioctl_writeread<Device, Arg, Result>(
220    request: ulong,
221) -> IoctlReqWriteRead<Device, Arg, Result>
222where
223    *mut Result: AsRawV,
224    Device: IoDevice,
225    Result: Copy,
226{
227    IoctlReqWriteRead::<Device, Arg, Result> {
228        request,
229        _phantom: core::marker::PhantomData,
230    }
231}
232
233/// Implementation of [`IoctlReq`] with a fixed `cmd` and passing no arguments
234/// at all, just returning the kernel's result value.
235///
236/// This is for the less common `ioctl` requests that indicate more than just
237/// success in their result, and so callers need to obtain that result.
238#[repr(transparent)]
239pub struct IoctlReqNoArgs<Device: IoDevice, Result> {
240    request: ulong,
241    _phantom: core::marker::PhantomData<(Device, Result)>,
242}
243
244impl<Device: IoDevice, Result> Clone for IoctlReqNoArgs<Device, Result> {
245    fn clone(&self) -> Self {
246        Self {
247            request: self.request,
248            _phantom: core::marker::PhantomData,
249        }
250    }
251}
252impl<Device: IoDevice, Result> Copy for IoctlReqNoArgs<Device, Result> {}
253
254unsafe impl<'a, Device, Result> IoctlReq<'a, Device> for IoctlReqNoArgs<Device, Result>
255where
256    Result: 'a + FromIoctlResult<int>,
257    Device: 'a + IoDevice,
258{
259    type ExtArg = ();
260    type TempMem = ();
261    type RawArg = ();
262    type Result = Result;
263
264    #[inline(always)]
265    fn prepare_ioctl_args(
266        &self,
267        _: &Self::ExtArg,
268        _: &mut MaybeUninit<Self::TempMem>,
269    ) -> (ulong, Self::RawArg) {
270        (self.request, ())
271    }
272
273    #[inline(always)]
274    fn prepare_ioctl_result(
275        &self,
276        raw: int,
277        _: &Self::ExtArg,
278        _: &MaybeUninit<Self::TempMem>,
279    ) -> Self::Result {
280        Self::Result::from_ioctl_result(&raw)
281    }
282}
283
284/// Implementation of [`IoctlReq`] with a fixed `cmd` and passing a constant
285/// int as the argument, then returning the kernel's result value.
286#[repr(transparent)]
287pub struct IoctlReqConstArg<Device: IoDevice, Result, const ARG: int> {
288    request: ulong,
289    _phantom: core::marker::PhantomData<(Device, Result)>,
290}
291
292impl<Device: IoDevice, Result, const ARG: int> Clone for IoctlReqConstArg<Device, Result, ARG> {
293    fn clone(&self) -> Self {
294        Self {
295            request: self.request,
296            _phantom: core::marker::PhantomData,
297        }
298    }
299}
300impl<Device: IoDevice, Result, const ARG: int> Copy for IoctlReqConstArg<Device, Result, ARG> {}
301
302unsafe impl<'a, Device, Result, const ARG: int> IoctlReq<'a, Device>
303    for IoctlReqConstArg<Device, Result, ARG>
304where
305    Result: 'a + FromIoctlResult<int>,
306    Device: 'a + IoDevice,
307{
308    type ExtArg = ();
309    type TempMem = ();
310    type RawArg = int;
311    type Result = Result;
312
313    #[inline(always)]
314    fn prepare_ioctl_args(
315        &self,
316        _: &Self::ExtArg,
317        _: &mut MaybeUninit<Self::TempMem>,
318    ) -> (ulong, Self::RawArg) {
319        (self.request, ARG)
320    }
321
322    #[inline(always)]
323    fn prepare_ioctl_result(
324        &self,
325        raw: int,
326        _: &Self::ExtArg,
327        _: &MaybeUninit<Self::TempMem>,
328    ) -> Self::Result {
329        Self::Result::from_ioctl_result(&raw)
330    }
331}
332
333/// Implementation of [`IoctlReq`] with a fixed `cmd` value and passing a
334/// pointer to a zeroed memory block of type `Result` directly through to the
335/// underlying system call and then returnin a copy of that memory.
336#[repr(transparent)]
337pub struct IoctlReqRead<Device: IoDevice, Result>
338where
339    *mut Result: AsRawV,
340    Result: Copy,
341{
342    request: ulong,
343    _phantom: core::marker::PhantomData<(Device, Result)>,
344}
345
346impl<Device: IoDevice, Result: Copy> Clone for IoctlReqRead<Device, Result> {
347    fn clone(&self) -> Self {
348        Self {
349            request: self.request,
350            _phantom: core::marker::PhantomData,
351        }
352    }
353}
354impl<Device: IoDevice, Result: Copy> Copy for IoctlReqRead<Device, Result> {}
355
356unsafe impl<'a, Device, Result> IoctlReq<'a, Device> for IoctlReqRead<Device, Result>
357where
358    *mut Result: AsRawV,
359    Result: 'a + Copy,
360    Device: 'a + IoDevice,
361{
362    type ExtArg = ();
363    type TempMem = Result;
364    type RawArg = *mut Result;
365    type Result = Result;
366
367    #[inline(always)]
368    fn prepare_ioctl_args(
369        &self,
370        _: &Self::ExtArg,
371        temp_mem: &mut MaybeUninit<Self::TempMem>,
372    ) -> (ulong, Self::RawArg) {
373        (self.request, temp_mem.as_mut_ptr())
374    }
375
376    #[inline(always)]
377    fn prepare_ioctl_result(
378        &self,
379        _: int,
380        _: &Self::ExtArg,
381        temp_mem: &MaybeUninit<Self::TempMem>,
382    ) -> Self::Result {
383        unsafe { temp_mem.assume_init() }
384    }
385}
386
387/// Implementation of [`IoctlReq`] with a fixed `cmd` value and passing a
388/// direct value from memory, without pointer indirection.
389#[repr(transparent)]
390pub struct IoctlReqWriteVal<Device: IoDevice, Arg, Result = int>
391where
392    Arg: AsRawV,
393{
394    request: ulong,
395    _phantom: core::marker::PhantomData<(Device, Arg, Result)>,
396}
397
398impl<Device: IoDevice, Arg: AsRawV, Result> Clone for IoctlReqWriteVal<Device, Arg, Result> {
399    fn clone(&self) -> Self {
400        Self {
401            request: self.request,
402            _phantom: core::marker::PhantomData,
403        }
404    }
405}
406impl<Device: IoDevice, Arg: AsRawV, Result> Copy for IoctlReqWriteVal<Device, Arg, Result> {}
407
408unsafe impl<'a, Device, Arg, Result> IoctlReq<'a, Device> for IoctlReqWriteVal<Device, Arg, Result>
409where
410    Arg: 'a + AsRawV,
411    Result: 'a + FromIoctlResult<int>,
412    Device: 'a + IoDevice,
413{
414    type ExtArg = Arg;
415    type TempMem = ();
416    type RawArg = Arg;
417    type Result = Result;
418
419    #[inline(always)]
420    fn prepare_ioctl_args(
421        &self,
422        arg: &Self::ExtArg,
423        _: &mut MaybeUninit<Self::TempMem>,
424    ) -> (ulong, Arg) {
425        (self.request, *arg)
426    }
427
428    #[inline(always)]
429    fn prepare_ioctl_result(
430        &self,
431        ret: int,
432        _: &Self::ExtArg,
433        _: &MaybeUninit<Self::TempMem>,
434    ) -> Self::Result {
435        Result::from_ioctl_result(&ret)
436    }
437}
438
439/// Implementation of [`IoctlReq`] with a fixed `cmd` value and passing a
440/// pointer to an argument value in memory.
441#[repr(transparent)]
442pub struct IoctlReqWrite<Device: IoDevice, Arg, Result = int>
443where
444    *const Arg: AsRawV,
445{
446    request: ulong,
447    _phantom: core::marker::PhantomData<(Device, Arg, Result)>,
448}
449
450impl<Device: IoDevice, Arg, Result> Clone for IoctlReqWrite<Device, Arg, Result> {
451    fn clone(&self) -> Self {
452        Self {
453            request: self.request,
454            _phantom: core::marker::PhantomData,
455        }
456    }
457}
458impl<Device: IoDevice, Arg, Result> Copy for IoctlReqWrite<Device, Arg, Result> {}
459
460unsafe impl<'a, Device, Arg, Result> IoctlReq<'a, Device> for IoctlReqWrite<Device, Arg, Result>
461where
462    *const Arg: AsRawV,
463    Arg: 'a,
464    Result: 'a + FromIoctlResult<int>,
465    Device: 'a + IoDevice,
466{
467    type ExtArg = &'a Arg;
468    type TempMem = ();
469    type RawArg = *const Arg;
470    type Result = Result;
471
472    #[inline(always)]
473    fn prepare_ioctl_args(
474        &self,
475        arg: &Self::ExtArg,
476        _: &mut MaybeUninit<Self::TempMem>,
477    ) -> (ulong, *const Arg) {
478        (self.request, (*arg) as *const Arg)
479    }
480
481    #[inline(always)]
482    fn prepare_ioctl_result(
483        &self,
484        ret: int,
485        _: &Self::ExtArg,
486        _: &MaybeUninit<Self::TempMem>,
487    ) -> Self::Result {
488        Result::from_ioctl_result(&ret)
489    }
490}
491
492#[repr(transparent)]
493pub struct IoctlReqWriteRead<Device: IoDevice, Arg, Result = int>
494where
495    *const Arg: AsRawV,
496{
497    request: ulong,
498    _phantom: core::marker::PhantomData<(Device, Arg, Result)>,
499}
500
501impl<Device: IoDevice, Arg, Result> Clone for IoctlReqWriteRead<Device, Arg, Result> {
502    fn clone(&self) -> Self {
503        Self {
504            request: self.request,
505            _phantom: core::marker::PhantomData,
506        }
507    }
508}
509impl<Device: IoDevice, Arg, Result> Copy for IoctlReqWriteRead<Device, Arg, Result> {}
510
511unsafe impl<'a, Device, Arg, Result> IoctlReq<'a, Device> for IoctlReqWriteRead<Device, Arg, Result>
512where
513    Device: IoDevice + 'a,
514    *const Arg: AsRawV,
515    Arg: 'a,
516    Result: 'a + FromIoctlResult<int>,
517{
518    type ExtArg = &'a mut Arg;
519    type TempMem = ();
520    type RawArg = *mut Arg;
521    type Result = Result;
522
523    #[inline(always)]
524    fn prepare_ioctl_args(
525        &self,
526        arg: &Self::ExtArg,
527        _: &mut MaybeUninit<Self::TempMem>,
528    ) -> (ulong, *mut Arg) {
529        (self.request, (*arg) as *const Arg as *mut Arg)
530    }
531
532    #[inline(always)]
533    fn prepare_ioctl_result(
534        &self,
535        ret: int,
536        _: &Self::ExtArg,
537        _: &MaybeUninit<Self::TempMem>,
538    ) -> Self::Result {
539        Result::from_ioctl_result(&ret)
540    }
541}
542
543/// Trait for types that can be constructed automatically from `ioctl` results
544/// from requests with a given argument type and temporary value type.
545pub trait FromIoctlResult<Raw> {
546    fn from_ioctl_result(raw: &Raw) -> Self;
547}
548
549impl FromIoctlResult<int> for int {
550    fn from_ioctl_result(raw: &int) -> Self {
551        *raw
552    }
553}
554
555impl<Device: IoDevice> FromIoctlResult<int> for super::File<Device> {
556    fn from_ioctl_result(raw: &int) -> Self {
557        unsafe { super::File::from_raw_fd(*raw) }
558    }
559}
560
561#[allow(non_snake_case)]
562const fn _IOC(dir: ulong, typ: ulong, nr: ulong, size: ulong) -> ulong {
563    (dir << 30) | (typ << 8) | (nr << 0) | (size << 16)
564}
565
566/// Equivalent to the kernel macro `_IO` for defining ioctl request numbers that
567/// neither read nor write within the standard numbering scheme.
568#[allow(non_snake_case)]
569pub const fn _IO(typ: ulong, nr: ulong) -> ulong {
570    _IOC(0, typ, nr, 0)
571}
572
573/// Equivalent to the kernel macro `_IOR` for defining ioctl request numbers
574/// where userspace reads data from the kernel.
575#[allow(non_snake_case)]
576pub const fn _IOR(typ: ulong, nr: ulong, size: ulong) -> ulong {
577    _IOC(2, typ, nr, size)
578}
579
580/// Equivalent to the kernel macro `_IOW` for defining ioctl request numbers
581/// where userspace writes data to the kernel.
582#[allow(non_snake_case)]
583pub const fn _IOW(typ: ulong, nr: ulong, size: ulong) -> ulong {
584    _IOC(1, typ, nr, size)
585}
586
587/// Equivalent to the kernel macro `_IOWR` for defining ioctl request numbers
588/// where userspace writes data to the kernel _and_ the kernel returns data
589/// back to userspace.
590#[allow(non_snake_case)]
591pub const fn _IOWR(typ: ulong, nr: ulong, size: ulong) -> ulong {
592    _IOC(1 | 2, typ, nr, size)
593}