Skip to content

Commit

Permalink
rust: add CBoundedStr
Browse files Browse the repository at this point in the history
`CBoundedStr<N>` is a `CStr` with length known to be less than `N`.
It can be used in cases where a known length limit exists.

Signed-off-by: Gary Guo <[email protected]>
  • Loading branch information
nbdd0121 committed May 9, 2021
1 parent 0de10ea commit e818b00
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
2 changes: 1 addition & 1 deletion rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub mod user_ptr;
pub use c_str_check;

pub use crate::error::{Error, KernelResult};
pub use crate::types::{CStr, Mode};
pub use crate::types::{CBoundedStr, CStr, Mode};

/// Page size defined in terms of the `PAGE_SHIFT` macro from C.
///
Expand Down
150 changes: 150 additions & 0 deletions rust/kernel/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
//!
//! C header: [`include/linux/types.h`](../../../../include/linux/types.h)
use core::ops::Deref;
use link_time_panic::link_time_assert;

use crate::bindings;
use crate::c_types;

Expand Down Expand Up @@ -123,6 +126,103 @@ impl CStr {
}
}

/// A `NUL`-terminated string that is guaranteed to be shorter than a given
/// length. This type is useful because C-side usually impose maximum length
/// on types.
///
/// The size parameter `N` represent the maximum number of bytes including NUL.
/// This implies that even though `CBoundedStr<0>` is a well-formed type it cannot
/// be safely created.
#[repr(transparent)]
pub struct CBoundedStr<const N: usize>(CStr);

impl<const N: usize> CBoundedStr<N> {
/// Creates a [`CBoundedStr`] form a [`CStr`].
///
/// The provided `CStr` must be shorten than `N`.
#[inline]
pub fn from_c_str(c_str: &CStr) -> Result<&Self, CStrConvertError> {
if c_str.0.len() > N {
return Err(CStrConvertError::BoundExceeded);
}

// SAFETY: We just checked that all properties hold.
Ok(unsafe { Self::from_c_str_unchecked(c_str) })
}

/// Creates a [`CBoundedStr`] form a [`CStr`] without performing any sanity
/// checks.
///
/// # Safety
///
/// The provided CStr must be shorten than `N`.
#[inline]
pub const unsafe fn from_c_str_unchecked(c_str: &CStr) -> &Self {
&*(c_str as *const CStr as *const Self)
}

/// Creates a [`CBoundedStr`] form a `[u8]`.
///
/// The provided slice must be nul-terminated, does not contain any
/// interior nul bytes and be shorten than `N`.
#[inline]
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, CStrConvertError> {
Self::from_c_str(CStr::from_bytes_with_nul(bytes)?)
}

/// Creates a [`CBoundedStr`] form a `[u8]` without performing any sanity
/// checks.
///
/// # Safety
///
/// The provided slice must be nul-terminated, does not contain any
/// interior nul bytes and be shorten than `N`.
#[inline]
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &Self {
Self::from_c_str_unchecked(CStr::from_bytes_with_nul_unchecked(bytes))
}

/// Creates a [`CBoundedStr`] form a `[u8; N]` without performing any sanity
/// checks.
///
/// # Safety
///
/// The provided slice must be nul-terminated.
#[inline]
pub const unsafe fn from_exact_bytes_with_nul_unchecked(bytes: &[u8; N]) -> &Self {
Self::from_bytes_with_nul_unchecked(bytes)
}

/// Relax the bound from `N` to `M`.
///
/// `M` must be no less than the bound `N`.
#[inline]
pub const fn relax_bound<const M: usize>(&self) -> &CBoundedStr<M> {
link_time_assert!(N <= M, "relaxed bound should be no less than current bound");
unsafe { CBoundedStr::<M>::from_c_str_unchecked(&self.0) }
}

/// Expand the string a c_char array with current bound, filling remaining bytes with zero.
#[inline]
pub const fn into_char_array(&self) -> [c_types::c_char; N] {
let mut ret: [c_types::c_char; N] = [0; N];
let mut i = 0;
while i < self.0 .0.len() {
ret[i] = self.0 .0[i] as _;
i += 1;
}
ret
}
}

impl<const N: usize> Deref for CBoundedStr<N> {
type Target = CStr;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// Creates a new `CStr` from a string literal.
///
/// The string literal should not contain any `NUL` bytes.
Expand All @@ -142,3 +242,53 @@ macro_rules! c_str {
C
}};
}

/// Creates a new `CBoundedStr` from a string literal.
///
/// The string literal should not contain any `NUL` bytes, and its length with NUL should not
/// exceed the bound supplied.
///
/// # Examples
///
/// ```rust,no_run
/// // If no bound is specified, the tighest bound will be inferred:
/// const MY_CSTR: &'static CBoundedStr<17> = c_bounded_str!("My awesome CStr!");
/// ```
///
/// ```rust,compile_fail
/// // This would not compile as the type is `CBoundedStr<17>`.
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!");
/// ```
///
/// ```rust,no_run
/// // You can relax the bound using the `relax_bound` method.
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!").relax_bound();
/// ```
///
/// ```rust,no_run
/// // Or alternatively specify a bound when invoking macro.
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!(100, "My awesome CStr!");
/// ```
///
/// ```rust,compile_fail
/// // shouldn't compile as the string is longer than the specified bound.
/// const MY_CSTR: &'static CBoundedStr<10> = c_bounded_str!(100, "My awesome CStr!");
/// ```
#[macro_export]
macro_rules! c_bounded_str {
($str:literal) => {{
let s = $crate::c_str_check::append_nul!($str);
unsafe { $crate::CBoundedStr::from_exact_bytes_with_nul_unchecked(s) }
}};
($bound:expr, $str:literal) => {{
const C: &'static $crate::CBoundedStr<$bound> = {
let s = $crate::c_str_check::append_nul!($str);
if s.len() > $bound {
// NOPANIC: This is a const panic.
panic!("bound exceeded");
}
unsafe { $crate::CBoundedStr::<$bound>::from_bytes_with_nul_unchecked(s) }
};
C
}};
}

0 comments on commit e818b00

Please sign in to comment.