@@ -16,19 +16,36 @@ use crate::error::{Error, KernelResult};
16
16
use crate :: user_ptr:: { UserSlicePtr , UserSlicePtrReader , UserSlicePtrWriter } ;
17
17
18
18
/// Wraps the kernel's `struct file`.
19
+ ///
20
+ /// # Invariants
21
+ ///
22
+ /// The pointer [`File::ptr`] is non-null and valid.
19
23
pub struct File {
20
24
ptr : * const bindings:: file ,
21
25
}
22
26
23
27
impl File {
28
+ /// Constructs a new [`struct file`] wrapper.
29
+ ///
30
+ /// # Safety
31
+ ///
32
+ /// The pointer `ptr` must be non-null and valid for the lifetime of the object.
24
33
unsafe fn from_ptr ( ptr : * const bindings:: file ) -> File {
34
+ // INVARIANTS: the safety contract ensures the type invariant will hold.
25
35
File { ptr }
26
36
}
27
37
28
38
/// Returns the current seek/cursor/pointer position (`struct file::f_pos`).
29
39
pub fn pos ( & self ) -> u64 {
40
+ // SAFETY: `File::ptr` is guaranteed to be valid by the type invariants.
30
41
unsafe { ( * self . ptr ) . f_pos as u64 }
31
42
}
43
+
44
+ /// Returns whether the file is in blocking mode.
45
+ pub fn is_blocking ( & self ) -> bool {
46
+ // SAFETY: `File::ptr` is guaranteed to be valid by the type invariants.
47
+ unsafe { ( * self . ptr ) . f_flags & bindings:: O_NONBLOCK == 0 }
48
+ }
32
49
}
33
50
34
51
/// Equivalent to [`std::io::SeekFrom`].
@@ -138,6 +155,34 @@ unsafe extern "C" fn llseek_callback<T: FileOperations>(
138
155
}
139
156
}
140
157
158
+ unsafe extern "C" fn unlocked_ioctl_callback < T : FileOperations > (
159
+ file : * mut bindings:: file ,
160
+ cmd : c_types:: c_uint ,
161
+ arg : c_types:: c_ulong ,
162
+ ) -> c_types:: c_long {
163
+ from_kernel_result ! {
164
+ let f = & * ( ( * file) . private_data as * const T ) ;
165
+ // SAFETY: This function is called by the kernel, so it must set `fs` appropriately.
166
+ let mut cmd = IoctlCommand :: new( cmd as _, arg as _) ;
167
+ let ret = T :: ioctl( f, & File :: from_ptr( file) , & mut cmd) ?;
168
+ Ok ( ret as _)
169
+ }
170
+ }
171
+
172
+ unsafe extern "C" fn compat_ioctl_callback < T : FileOperations > (
173
+ file : * mut bindings:: file ,
174
+ cmd : c_types:: c_uint ,
175
+ arg : c_types:: c_ulong ,
176
+ ) -> c_types:: c_long {
177
+ from_kernel_result ! {
178
+ let f = & * ( ( * file) . private_data as * const T ) ;
179
+ // SAFETY: This function is called by the kernel, so it must set `fs` appropriately.
180
+ let mut cmd = IoctlCommand :: new( cmd as _, arg as _) ;
181
+ let ret = T :: compat_ioctl( f, & File :: from_ptr( file) , & mut cmd) ?;
182
+ Ok ( ret as _)
183
+ }
184
+ }
185
+
141
186
unsafe extern "C" fn fsync_callback < T : FileOperations > (
142
187
file : * mut bindings:: file ,
143
188
start : bindings:: loff_t ,
@@ -177,7 +222,11 @@ impl<T: FileOperations> FileOperationsVtable<T> {
177
222
} ,
178
223
179
224
check_flags : None ,
180
- compat_ioctl : None ,
225
+ compat_ioctl : if T :: TO_USE . compat_ioctl {
226
+ Some ( compat_ioctl_callback :: < T > )
227
+ } else {
228
+ None
229
+ } ,
181
230
copy_file_range : None ,
182
231
fallocate : None ,
183
232
fadvise : None ,
@@ -205,7 +254,11 @@ impl<T: FileOperations> FileOperationsVtable<T> {
205
254
show_fdinfo : None ,
206
255
splice_read : None ,
207
256
splice_write : None ,
208
- unlocked_ioctl : None ,
257
+ unlocked_ioctl : if T :: TO_USE . ioctl {
258
+ Some ( unlocked_ioctl_callback :: < T > )
259
+ } else {
260
+ None
261
+ } ,
209
262
write_iter : None ,
210
263
} ;
211
264
}
@@ -221,6 +274,12 @@ pub struct ToUse {
221
274
/// The `llseek` field of [`struct file_operations`].
222
275
pub seek : bool ,
223
276
277
+ /// The `unlocked_ioctl` field of [`struct file_operations`].
278
+ pub ioctl : bool ,
279
+
280
+ /// The `compat_ioctl` field of [`struct file_operations`].
281
+ pub compat_ioctl : bool ,
282
+
224
283
/// The `fsync` field of [`struct file_operations`].
225
284
pub fsync : bool ,
226
285
}
@@ -231,6 +290,8 @@ pub const USE_NONE: ToUse = ToUse {
231
290
read : false ,
232
291
write : false ,
233
292
seek : false ,
293
+ ioctl : false ,
294
+ compat_ioctl : false ,
234
295
fsync : false ,
235
296
} ;
236
297
@@ -249,6 +310,106 @@ macro_rules! declare_file_operations {
249
310
} ;
250
311
}
251
312
313
+ /// Allows the handling of ioctls defined with the `_IO`, `_IOR`, `_IOW`, and `_IOWR` macros.
314
+ ///
315
+ /// For each macro, there is a handler function that takes the appropriate types as arguments.
316
+ pub trait IoctlHandler : Sync {
317
+ /// Handles ioctls defined with the `_IO` macro, that is, with no buffer as argument.
318
+ fn pure ( & self , _file : & File , _cmd : u32 , _arg : usize ) -> KernelResult < i32 > {
319
+ Err ( Error :: EINVAL )
320
+ }
321
+
322
+ /// Handles ioctls defined with the `_IOR` macro, that is, with an output buffer provided as
323
+ /// argument.
324
+ fn read ( & self , _file : & File , _cmd : u32 , _writer : & mut UserSlicePtrWriter ) -> KernelResult < i32 > {
325
+ Err ( Error :: EINVAL )
326
+ }
327
+
328
+ /// Handles ioctls defined with the `_IOW` macro, that is, with an input buffer provided as
329
+ /// argument.
330
+ fn write (
331
+ & self ,
332
+ _file : & File ,
333
+ _cmd : u32 ,
334
+ _reader : & mut UserSlicePtrReader ,
335
+ ) -> KernelResult < i32 > {
336
+ Err ( Error :: EINVAL )
337
+ }
338
+
339
+ /// Handles ioctls defined with the `_IOWR` macro, that is, with a buffer for both input and
340
+ /// output provided as argument.
341
+ fn read_write ( & self , _file : & File , _cmd : u32 , _data : UserSlicePtr ) -> KernelResult < i32 > {
342
+ Err ( Error :: EINVAL )
343
+ }
344
+ }
345
+
346
+ /// Represents an ioctl command.
347
+ ///
348
+ /// It can use the components of an ioctl command to dispatch ioctls using
349
+ /// [`IoctlCommand::dispatch`].
350
+ pub struct IoctlCommand {
351
+ cmd : u32 ,
352
+ arg : usize ,
353
+ user_slice : Option < UserSlicePtr > ,
354
+ }
355
+
356
+ impl IoctlCommand {
357
+ /// Constructs a new [`IoctlCommand`].
358
+ ///
359
+ /// # Safety
360
+ ///
361
+ /// The caller must ensure that `fs` is compatible with `arg` and the original caller's
362
+ /// context. For example, if the original caller is from userland (e.g., through the ioctl
363
+ /// syscall), then `arg` is untrusted and `fs` should therefore be `USER_DS`.
364
+ unsafe fn new ( cmd : u32 , arg : usize ) -> Self {
365
+ let user_slice = {
366
+ let dir = ( cmd >> bindings:: _IOC_DIRSHIFT) & bindings:: _IOC_DIRMASK;
367
+ if dir == bindings:: _IOC_NONE {
368
+ None
369
+ } else {
370
+ let size = ( cmd >> bindings:: _IOC_SIZESHIFT) & bindings:: _IOC_SIZEMASK;
371
+
372
+ // SAFETY: We only create one instance of the user slice, so TOCTOU issues are not
373
+ // possible. The `set_fs` requirements are imposed on the caller.
374
+ UserSlicePtr :: new ( arg as _ , size as _ ) . ok ( )
375
+ }
376
+ } ;
377
+
378
+ Self {
379
+ cmd,
380
+ arg,
381
+ user_slice,
382
+ }
383
+ }
384
+
385
+ /// Dispatches the given ioctl to the appropriate handler based on the value of the command. It
386
+ /// also creates a [`UserSlicePtr`], [`UserSlicePtrReader`], or [`UserSlicePtrWriter`]
387
+ /// depending on the direction of the buffer of the command.
388
+ ///
389
+ /// It is meant to be used in implementations of [`FileOperations::ioctl`] and
390
+ /// [`FileOperations::compat_ioctl`].
391
+ pub fn dispatch < T : IoctlHandler > ( & mut self , handler : & T , file : & File ) -> KernelResult < i32 > {
392
+ let dir = ( self . cmd >> bindings:: _IOC_DIRSHIFT) & bindings:: _IOC_DIRMASK;
393
+ if dir == bindings:: _IOC_NONE {
394
+ return T :: pure ( handler, file, self . cmd , self . arg ) ;
395
+ }
396
+
397
+ let data = self . user_slice . take ( ) . ok_or ( Error :: EFAULT ) ?;
398
+ const READ_WRITE : u32 = bindings:: _IOC_READ | bindings:: _IOC_WRITE;
399
+ match dir {
400
+ bindings:: _IOC_WRITE => T :: write ( handler, file, self . cmd , & mut data. reader ( ) ) ,
401
+ bindings:: _IOC_READ => T :: read ( handler, file, self . cmd , & mut data. writer ( ) ) ,
402
+ READ_WRITE => T :: read_write ( handler, file, self . cmd , data) ,
403
+ _ => Err ( Error :: EINVAL ) ,
404
+ }
405
+ }
406
+
407
+ /// Returns the raw 32-bit value of the command and the ptr-sized argument.
408
+ pub fn raw ( & self ) -> ( u32 , usize ) {
409
+ ( self . cmd , self . arg )
410
+ }
411
+ }
412
+
252
413
/// Corresponds to the kernel's `struct file_operations`.
253
414
///
254
415
/// You implement this trait whenever you would create a `struct file_operations`.
@@ -296,6 +457,20 @@ pub trait FileOperations: Sync + Sized {
296
457
Err ( Error :: EINVAL )
297
458
}
298
459
460
+ /// Performs IO control operations that are specific to the file.
461
+ ///
462
+ /// Corresponds to the `unlocked_ioctl` function pointer in `struct file_operations`.
463
+ fn ioctl ( & self , _file : & File , _cmd : & mut IoctlCommand ) -> KernelResult < i32 > {
464
+ Err ( Error :: EINVAL )
465
+ }
466
+
467
+ /// Performs 32-bit IO control operations on that are specific to the file on 64-bit kernels.
468
+ ///
469
+ /// Corresponds to the `compat_ioctl` function pointer in `struct file_operations`.
470
+ fn compat_ioctl ( & self , _file : & File , _cmd : & mut IoctlCommand ) -> KernelResult < i32 > {
471
+ Err ( Error :: EINVAL )
472
+ }
473
+
299
474
/// Syncs pending changes to this file.
300
475
///
301
476
/// Corresponds to the `fsync` function pointer in `struct file_operations`.
0 commit comments