Skip to content

Commit b02eaaf

Browse files
wedsonaffbq
authored andcommitted
rust: file: add Rust abstraction for struct file
This abstraction makes it possible to manipulate the open files for a process. The new `File` struct wraps the C `struct file`. When accessing it using the smart pointer `ARef<File>`, the pointer will own a reference count to the file. When accessing it as `&File`, then the reference does not own a refcount, but the borrow checker will ensure that the reference count does not hit zero while the `&File` is live. Since this is intended to manipulate the open files of a process, we introduce an `fget` constructor that corresponds to the C `fget` method. In future patches, it will become possible to create a new fd in a process and bind it to a `File`. Rust Binder will use these to send fds from one process to another. We also provide a method for accessing the file's flags. Rust Binder will use this to access the flags of the Binder fd to check whether the non-blocking flag is set, which affects what the Binder ioctl does. This introduces a struct for the EBADF error type, rather than just using the Error type directly. This has two advantages: * `File::fget` returns a `Result<ARef<File>, BadFdError>`, which the compiler will represent as a single pointer, with null being an error. This is possible because the compiler understands that `BadFdError` has only one possible value, and it also understands that the `ARef<File>` smart pointer is guaranteed non-null. * Additionally, we promise to users of the method that the method can only fail with EBADF, which means that they can rely on this promise without having to inspect its implementation. That said, there are also two disadvantages: * Defining additional error types involves boilerplate. * The question mark operator will only utilize the `From` trait once, which prevents you from using the question mark operator on `BadFdError` in methods that return some third error type that the kernel `Error` is convertible into. (However, it works fine in methods that return `Error`.) Signed-off-by: Wedson Almeida Filho <[email protected]> Co-developed-by: Daniel Xu <[email protected]> Signed-off-by: Daniel Xu <[email protected]> Co-developed-by: Alice Ryhl <[email protected]> Reviewed-by: Martin Rodriguez Reboredo <[email protected]> Reviewed-by: Trevor Gross <[email protected]> Reviewed-by: Benno Lossin <[email protected]> Signed-off-by: Alice Ryhl <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 3809d92 commit b02eaaf

File tree

6 files changed

+355
-0
lines changed

6 files changed

+355
-0
lines changed

fs/file.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,13 @@ EXPORT_SYMBOL(task_lookup_next_fdget_rcu);
11271127
*
11281128
* The fput_needed flag returned by fget_light should be passed to the
11291129
* corresponding fput_light.
1130+
*
1131+
* (As an exception to rule 2, you can call filp_close between fget_light and
1132+
* fput_light provided that you capture a real refcount with get_file before
1133+
* the call to filp_close, and ensure that this real refcount is fput *after*
1134+
* the fput_light call.)
1135+
*
1136+
* See also the documentation in rust/kernel/file.rs.
11301137
*/
11311138
static unsigned long __fget_light(unsigned int fd, fmode_t mask)
11321139
{

rust/bindings/bindings_helper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <linux/blk-mq.h>
1212
#include <linux/errname.h>
1313
#include <linux/ethtool.h>
14+
#include <linux/file.h>
15+
#include <linux/fs.h>
1416
#include <linux/jiffies.h>
1517
#include <linux/mdio.h>
1618
#include <linux/phy.h>

rust/helpers.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <linux/build_bug.h>
2626
#include <linux/err.h>
2727
#include <linux/errname.h>
28+
#include <linux/fs.h>
2829
#include <linux/gfp.h>
2930
#include <linux/highmem.h>
3031
#include <linux/mutex.h>
@@ -199,6 +200,12 @@ rust_helper_krealloc(const void *objp, size_t new_size, gfp_t flags)
199200
}
200201
EXPORT_SYMBOL_GPL(rust_helper_krealloc);
201202

203+
struct file *rust_helper_get_file(struct file *f)
204+
{
205+
return get_file(f);
206+
}
207+
EXPORT_SYMBOL_GPL(rust_helper_get_file);
208+
202209
/*
203210
* `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can
204211
* use it in contexts where Rust expects a `usize` like slice (array) indices.

rust/kernel/file.rs

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Files and file descriptors.
4+
//!
5+
//! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h) and
6+
//! [`include/linux/file.h`](srctree/include/linux/file.h)
7+
8+
use crate::{
9+
bindings,
10+
error::{code::*, Error, Result},
11+
types::{ARef, AlwaysRefCounted, Opaque},
12+
};
13+
use core::{marker::PhantomData, ptr};
14+
15+
/// Flags associated with a [`File`].
16+
pub mod flags {
17+
/// File is opened in append mode.
18+
pub const O_APPEND: u32 = bindings::O_APPEND;
19+
20+
/// Signal-driven I/O is enabled.
21+
pub const O_ASYNC: u32 = bindings::FASYNC;
22+
23+
/// Close-on-exec flag is set.
24+
pub const O_CLOEXEC: u32 = bindings::O_CLOEXEC;
25+
26+
/// File was created if it didn't already exist.
27+
pub const O_CREAT: u32 = bindings::O_CREAT;
28+
29+
/// Direct I/O is enabled for this file.
30+
pub const O_DIRECT: u32 = bindings::O_DIRECT;
31+
32+
/// File must be a directory.
33+
pub const O_DIRECTORY: u32 = bindings::O_DIRECTORY;
34+
35+
/// Like [`O_SYNC`] except metadata is not synced.
36+
pub const O_DSYNC: u32 = bindings::O_DSYNC;
37+
38+
/// Ensure that this file is created with the `open(2)` call.
39+
pub const O_EXCL: u32 = bindings::O_EXCL;
40+
41+
/// Large file size enabled (`off64_t` over `off_t`).
42+
pub const O_LARGEFILE: u32 = bindings::O_LARGEFILE;
43+
44+
/// Do not update the file last access time.
45+
pub const O_NOATIME: u32 = bindings::O_NOATIME;
46+
47+
/// File should not be used as process's controlling terminal.
48+
pub const O_NOCTTY: u32 = bindings::O_NOCTTY;
49+
50+
/// If basename of path is a symbolic link, fail open.
51+
pub const O_NOFOLLOW: u32 = bindings::O_NOFOLLOW;
52+
53+
/// File is using nonblocking I/O.
54+
pub const O_NONBLOCK: u32 = bindings::O_NONBLOCK;
55+
56+
/// File is using nonblocking I/O.
57+
///
58+
/// This is effectively the same flag as [`O_NONBLOCK`] on all architectures
59+
/// except SPARC64.
60+
pub const O_NDELAY: u32 = bindings::O_NDELAY;
61+
62+
/// Used to obtain a path file descriptor.
63+
pub const O_PATH: u32 = bindings::O_PATH;
64+
65+
/// Write operations on this file will flush data and metadata.
66+
pub const O_SYNC: u32 = bindings::O_SYNC;
67+
68+
/// This file is an unnamed temporary regular file.
69+
pub const O_TMPFILE: u32 = bindings::O_TMPFILE;
70+
71+
/// File should be truncated to length 0.
72+
pub const O_TRUNC: u32 = bindings::O_TRUNC;
73+
74+
/// Bitmask for access mode flags.
75+
///
76+
/// # Examples
77+
///
78+
/// ```
79+
/// use kernel::file;
80+
/// # fn do_something() {}
81+
/// # let flags = 0;
82+
/// if (flags & file::flags::O_ACCMODE) == file::flags::O_RDONLY {
83+
/// do_something();
84+
/// }
85+
/// ```
86+
pub const O_ACCMODE: u32 = bindings::O_ACCMODE;
87+
88+
/// File is read only.
89+
pub const O_RDONLY: u32 = bindings::O_RDONLY;
90+
91+
/// File is write only.
92+
pub const O_WRONLY: u32 = bindings::O_WRONLY;
93+
94+
/// File can be both read and written.
95+
pub const O_RDWR: u32 = bindings::O_RDWR;
96+
}
97+
98+
/// Compile-time information for keeping track of how a [`File`] is shared.
99+
///
100+
/// The `fdget_pos` method can be used to access the file's position without taking `f_pos_lock`,
101+
/// as long as the file is not shared with any other threads. During such calls to `fdget_pos`, the
102+
/// file must remain non-shared, so it must not be possible to move the file to another thread. For
103+
/// example, if the file is moved to another thread, then it could be passed to `fd_install`, at
104+
/// which point the remote process could touch the file position.
105+
///
106+
/// The share mode only keeps track of whether there are active `fdget_pos` calls that did not take
107+
/// the `f_pos_lock`, and does not keep track of `fdget` calls. This is okay because `fdget` does
108+
/// not care about the refcount of the underlying `struct file`; as long as the entry in the
109+
/// current thread's fd table does not get removed, it's okay to share the file. For example,
110+
/// `fd_install`ing the `struct file` into another process is okay during an `fdget` call, because
111+
/// the other process can't touch the fd table of the original process.
112+
mod share_mode {
113+
/// Trait implemented by the two sharing modes that a file might have.
114+
pub trait FileShareMode {}
115+
116+
/// Represents a file for which there might be an active call to `fdget_pos` that did not take
117+
/// the `f_pos_lock` lock.
118+
pub enum MaybeFdgetPos {}
119+
120+
/// Represents a file for which it is known that all active calls to `fdget_pos` (if any) took
121+
/// the `f_pos_lock` lock.
122+
pub enum NoFdgetPos {}
123+
124+
impl FileShareMode for MaybeFdgetPos {}
125+
impl FileShareMode for NoFdgetPos {}
126+
}
127+
pub use self::share_mode::{FileShareMode, MaybeFdgetPos, NoFdgetPos};
128+
129+
/// Wraps the kernel's `struct file`.
130+
///
131+
/// This represents an open file rather than a file on a filesystem. Processes generally reference
132+
/// open files using file descriptors. However, file descriptors are not the same as files. A file
133+
/// descriptor is just an integer that corresponds to a file, and a single file may be referenced
134+
/// by multiple file descriptors.
135+
///
136+
/// # Refcounting
137+
///
138+
/// Instances of this type are reference-counted. The reference count is incremented by the
139+
/// `fget`/`get_file` functions and decremented by `fput`. The Rust type `ARef<File>` represents a
140+
/// pointer that owns a reference count on the file.
141+
///
142+
/// Whenever a process opens a file descriptor (fd), it stores a pointer to the file in its fd
143+
/// table (`struct files_struct`). This pointer owns a reference count to the file, ensuring the
144+
/// file isn't prematurely deleted while the file descriptor is open. In Rust terminology, the
145+
/// pointers in `struct files_struct` are `ARef<File>` pointers.
146+
///
147+
/// ## Light refcounts
148+
///
149+
/// Whenever a process has an fd to a file, it may use something called a "light refcount" as a
150+
/// performance optimization. Light refcounts are acquired by calling `fdget` and released with
151+
/// `fdput`. The idea behind light refcounts is that if the fd is not closed between the calls to
152+
/// `fdget` and `fdput`, then the refcount cannot hit zero during that time, as the `struct
153+
/// files_struct` holds a reference until the fd is closed. This means that it's safe to access the
154+
/// file even if `fdget` does not increment the refcount.
155+
///
156+
/// The requirement that the fd is not closed during a light refcount applies globally across all
157+
/// threads - not just on the thread using the light refcount. For this reason, light refcounts are
158+
/// only used when the `struct files_struct` is not shared with other threads, since this ensures
159+
/// that other unrelated threads cannot suddenly start using the fd and close it. Therefore,
160+
/// calling `fdget` on a shared `struct files_struct` creates a normal refcount instead of a light
161+
/// refcount.
162+
///
163+
/// Light reference counts must be released with `fdput` before the system call returns to
164+
/// userspace. This means that if you wait until the current system call returns to userspace, then
165+
/// all light refcounts that existed at the time have gone away.
166+
///
167+
/// ### The file position
168+
///
169+
/// Each `struct file` has a position integer, which is protected by the `f_pos_lock` mutex.
170+
/// However, if the `struct file` is not shared, then the kernel may avoid taking the lock as a
171+
/// performance optimization.
172+
///
173+
/// The condition for avoiding the `f_pos_lock` mutex is different from the condition for using
174+
/// `fdget`. With `fdget`, you may avoid incrementing the refcount as long as the current fd table
175+
/// is not shared; it is okay if there are other fd tables that also reference the same `struct
176+
/// file`. However, `fdget_pos` can only avoid taking the `f_pos_lock` if the entire `struct file`
177+
/// is not shared, as different processes with an fd to the same `struct file` share the same
178+
/// position.
179+
///
180+
/// ## Rust references
181+
///
182+
/// The reference type `&File` is similar to light refcounts:
183+
///
184+
/// * `&File` references don't own a reference count. They can only exist as long as the reference
185+
/// count stays positive, and can only be created when there is some mechanism in place to ensure
186+
/// this.
187+
///
188+
/// * The Rust borrow-checker normally ensures this by enforcing that the `ARef<File>` from which
189+
/// a `&File` is created outlives the `&File`.
190+
///
191+
/// * Using the unsafe [`File::from_ptr`] means that it is up to the caller to ensure that the
192+
/// `&File` only exists while the reference count is positive.
193+
///
194+
/// * You can think of `fdget` as using an fd to look up an `ARef<File>` in the `struct
195+
/// files_struct` and create an `&File` from it. The "fd cannot be closed" rule is like the Rust
196+
/// rule "the `ARef<File>` must outlive the `&File`".
197+
///
198+
/// # Invariants
199+
///
200+
/// * All instances of this type are refcounted using the `f_count` field.
201+
/// * If the file sharing mode is `MaybeFdgetPos`, then all active calls to `fdget_pos` that did
202+
/// not take the `f_pos_lock` mutex must be on the same thread as this `File`.
203+
/// * If the file sharing mode is `NoFdgetPos`, then there must not be active calls to `fdget_pos`
204+
/// that did not take the `f_pos_lock` mutex.
205+
#[repr(transparent)]
206+
pub struct File<S: FileShareMode> {
207+
inner: Opaque<bindings::file>,
208+
_share_mode: PhantomData<S>,
209+
}
210+
211+
// SAFETY: This file is known to not have any local active `fdget_pos` calls, so it is safe to
212+
// transfer it between threads.
213+
unsafe impl Send for File<NoFdgetPos> {}
214+
215+
// SAFETY: This file is known to not have any local active `fdget_pos` calls, so it is safe to
216+
// access its methods from several threads in parallel.
217+
unsafe impl Sync for File<NoFdgetPos> {}
218+
219+
/// File methods that only exist under the [`MaybeFdgetPos`] sharing mode.
220+
impl File<MaybeFdgetPos> {
221+
/// Constructs a new `struct file` wrapper from a file descriptor.
222+
///
223+
/// The file descriptor belongs to the current process, and there might be active local calls
224+
/// to `fdget_pos`.
225+
pub fn fget(fd: u32) -> Result<ARef<File<MaybeFdgetPos>>, BadFdError> {
226+
// SAFETY: FFI call, there are no requirements on `fd`.
227+
let ptr = ptr::NonNull::new(unsafe { bindings::fget(fd) }).ok_or(BadFdError)?;
228+
229+
// SAFETY: `bindings::fget` created a refcount, and we pass ownership of it to the `ARef`.
230+
//
231+
// INVARIANT: This file is in the fd table on this thread, so either all `fdget_pos` calls
232+
// are on this thread, or the file is shared, in which case `fdget_pos` calls took the
233+
// `f_pos_lock` mutex.
234+
Ok(unsafe { ARef::from_raw(ptr.cast()) })
235+
}
236+
237+
/// Assume that there are no active `fdget_pos` calls that prevent us from sharing this file.
238+
///
239+
/// This makes it safe to transfer this file to other threads. No checks are performed, and
240+
/// using it incorrectly may lead to a data race on the file position if the file is shared
241+
/// with another thread.
242+
///
243+
/// This method is intended to be used together with [`File::fget`] when the caller knows
244+
/// statically that there are no `fdget_pos` calls on the current thread. For example, you
245+
/// might use it when calling `fget` from an ioctl, since ioctls usually do not touch the file
246+
/// position.
247+
///
248+
/// # Safety
249+
///
250+
/// There must not be any active `fdget_pos` calls on the current thread.
251+
pub unsafe fn assume_no_fdget_pos(me: ARef<Self>) -> ARef<File<NoFdgetPos>> {
252+
// INVARIANT: There are no `fdget_pos` calls on the current thread, and by the type
253+
// invariants, if there is a `fdget_pos` call on another thread, then it took the
254+
// `f_pos_lock` mutex.
255+
//
256+
// SAFETY: `File<MaybeFdgetPos>` and `File<NoFdgetPos>` have the same layout.
257+
unsafe { ARef::from_raw(ARef::into_raw(me).cast()) }
258+
}
259+
}
260+
261+
/// File methods that exist under all sharing modes.
262+
impl<S: FileShareMode> File<S> {
263+
/// Creates a reference to a [`File`] from a valid pointer.
264+
///
265+
/// # Safety
266+
///
267+
/// * The caller must ensure that `ptr` points at a valid file and that the file's refcount is
268+
/// positive for the duration of 'a.
269+
/// * The caller must ensure that the requirements for using the chosen file sharing mode are
270+
/// upheld.
271+
pub unsafe fn from_ptr<'a>(ptr: *const bindings::file) -> &'a File<S> {
272+
// SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the
273+
// duration of 'a. The cast is okay because `File` is `repr(transparent)`.
274+
//
275+
// INVARIANT: The safety requirements guarantee that the refcount does not hit zero during
276+
// 'a. The caller guarantees to uphold the requirements for the chosen sharing mode.
277+
unsafe { &*ptr.cast() }
278+
}
279+
280+
/// Returns a raw pointer to the inner C struct.
281+
#[inline]
282+
pub fn as_ptr(&self) -> *mut bindings::file {
283+
self.inner.get()
284+
}
285+
286+
/// Returns the flags associated with the file.
287+
///
288+
/// The flags are a combination of the constants in [`flags`].
289+
pub fn flags(&self) -> u32 {
290+
// This `read_volatile` is intended to correspond to a READ_ONCE call.
291+
//
292+
// SAFETY: The file is valid because the shared reference guarantees a nonzero refcount.
293+
//
294+
// FIXME(read_once): Replace with `read_once` when available on the Rust side.
295+
unsafe { core::ptr::addr_of!((*self.as_ptr()).f_flags).read_volatile() }
296+
}
297+
}
298+
299+
// SAFETY: The type invariants guarantee that `File` is always ref-counted. This implementation
300+
// makes `ARef<File>` own a normal refcount.
301+
unsafe impl<S: FileShareMode> AlwaysRefCounted for File<S> {
302+
fn inc_ref(&self) {
303+
// SAFETY: The existence of a shared reference means that the refcount is nonzero.
304+
unsafe { bindings::get_file(self.as_ptr()) };
305+
}
306+
307+
unsafe fn dec_ref(obj: ptr::NonNull<File<S>>) {
308+
// SAFETY: To call this method, the caller passes us ownership of a normal refcount, so we
309+
// may drop it. The cast is okay since `File` has the same representation as `struct file`.
310+
unsafe { bindings::fput(obj.cast().as_ptr()) }
311+
}
312+
}
313+
314+
/// Represents the `EBADF` error code.
315+
///
316+
/// Used for methods that can only fail with `EBADF`.
317+
#[derive(Copy, Clone, Eq, PartialEq)]
318+
pub struct BadFdError;
319+
320+
impl From<BadFdError> for Error {
321+
fn from(_: BadFdError) -> Error {
322+
EBADF
323+
}
324+
}
325+
326+
impl core::fmt::Debug for BadFdError {
327+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328+
f.pad("EBADF")
329+
}
330+
}

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod alloc;
3131
pub mod block;
3232
mod build_assert;
3333
pub mod error;
34+
pub mod file;
3435
pub mod init;
3536
pub mod ioctl;
3637
#[cfg(CONFIG_KUNIT)]

rust/kernel/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,14 @@ impl<T: AlwaysRefCounted> ARef<T> {
411411
_p: PhantomData,
412412
}
413413
}
414+
415+
/// Convert this [`ARef`] into a raw pointer.
416+
///
417+
/// The caller retains ownership of the refcount that this `ARef` used to own.
418+
pub fn into_raw(me: Self) -> NonNull<T> {
419+
let me = core::mem::ManuallyDrop::new(me);
420+
me.ptr
421+
}
414422
}
415423

416424
impl<T: AlwaysRefCounted> Clone for ARef<T> {

0 commit comments

Comments
 (0)