Skip to content

Commit

Permalink
rust/error: add helper function to convert a C error pointer to a `Re…
Browse files Browse the repository at this point in the history
…sult`

Some kernel C API functions return a pointer which embeds an optional
`errno`. Callers are supposed to check the returned pointer with
`IS_ERR()` and if this returns `true`, retrieve the `errno` using
`PTR_ERR()`.

Create a Rust helper function to implement the Rust equivalent:
transform a `*mut T` to `Result<*mut T>`.

Co-Created-By: Boqun Feng <boqun.feng@gmail.com>
Co-Created-By: Miguel Ojeda <ojeda@users.noreply.github.com>
Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
  • Loading branch information
Sven Van Asbroeck committed May 17, 2021
1 parent fc2b177 commit 62d3b58
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 0 deletions.
12 changes: 12 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ size_t rust_helper_copy_to_iter(const void *addr, size_t bytes, struct iov_iter
}
EXPORT_SYMBOL_GPL(rust_helper_copy_to_iter);

bool rust_helper_is_err(__force const void *ptr)
{
return IS_ERR(ptr);
}
EXPORT_SYMBOL_GPL(rust_helper_is_err);

long rust_helper_ptr_err(__force const void *ptr)
{
return PTR_ERR(ptr);
}
EXPORT_SYMBOL_GPL(rust_helper_ptr_err);

#if !defined(CONFIG_ARM)
// See https://github.com/rust-lang/rust-bindgen/issues/1671
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
Expand Down
54 changes: 54 additions & 0 deletions rust/kernel/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,57 @@ impl From<AllocError> for Error {
Error::ENOMEM
}
}

// # Invariant: `-bindings::MAX_ERRNO` fits in an `i16`.
crate::static_assert!(bindings::MAX_ERRNO as i64 <= -(i16::MIN as i64));

/// Transform a kernel "error pointer" to a normal pointer.
///
/// Some kernel C API functions return an "error pointer" which optionally
/// embeds an `errno`. Callers are supposed to check the returned pointer
/// for errors. This function performs the check and converts the "error pointer"
/// to a normal pointer in an idiomatic fashion.
///
/// # Examples
///
/// ```rust,no_run
/// fn devm_platform_ioremap_resource(
/// pdev: &mut PlatformDevice,
/// index: u32,
/// ) -> Result<*mut c_types::c_void> {
/// // SAFETY: FFI call.
/// unsafe {
/// from_kernel_err_ptr(bindings::devm_platform_ioremap_resource(
/// pdev.to_ptr(),
/// index,
/// ))
/// }
/// }
/// ```
// TODO: remove `dead_code` marker once an in-kernel client is available.
#[allow(dead_code)]
pub(crate) fn from_kernel_err_ptr<T>(ptr: *mut T) -> Result<*mut T> {
extern "C" {
#[allow(improper_ctypes)]
fn rust_helper_is_err(ptr: *const c_types::c_void) -> bool;

#[allow(improper_ctypes)]
fn rust_helper_ptr_err(ptr: *const c_types::c_void) -> c_types::c_long;
}

// CAST: casting a pointer to `*const c_types::c_void` is always valid.
let const_ptr: *const c_types::c_void = ptr.cast();
// SAFETY: the FFI function does not deref the pointer.
if unsafe { rust_helper_is_err(const_ptr) } {
// SAFETY: the FFI function does not deref the pointer.
let err = unsafe { rust_helper_ptr_err(const_ptr) };
// CAST: if `rust_helper_is_err()` returns `true`,
// then `rust_helper_ptr_err()` is guaranteed to return a
// negative value greater-or-equal to `-bindings::MAX_ERRNO`,
// which always fits in an `i16`, as per the invariant above.
// And an `i16` always fits in an `i32`. So casting `err` to
// an `i32` can never overflow, and is always valid.
return Err(Error::from_kernel_errno(err as i32));
}
Ok(ptr)
}

0 comments on commit 62d3b58

Please sign in to comment.