Skip to content

Commit e818b00

Browse files
committed
rust: add CBoundedStr
`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]>
1 parent 0de10ea commit e818b00

File tree

2 files changed

+151
-1
lines changed

2 files changed

+151
-1
lines changed

rust/kernel/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub mod user_ptr;
7070
pub use c_str_check;
7171

7272
pub use crate::error::{Error, KernelResult};
73-
pub use crate::types::{CStr, Mode};
73+
pub use crate::types::{CBoundedStr, CStr, Mode};
7474

7575
/// Page size defined in terms of the `PAGE_SHIFT` macro from C.
7676
///

rust/kernel/types.rs

+150
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
//!
55
//! C header: [`include/linux/types.h`](../../../../include/linux/types.h)
66
7+
use core::ops::Deref;
8+
use link_time_panic::link_time_assert;
9+
710
use crate::bindings;
811
use crate::c_types;
912

@@ -123,6 +126,103 @@ impl CStr {
123126
}
124127
}
125128

129+
/// A `NUL`-terminated string that is guaranteed to be shorter than a given
130+
/// length. This type is useful because C-side usually impose maximum length
131+
/// on types.
132+
///
133+
/// The size parameter `N` represent the maximum number of bytes including NUL.
134+
/// This implies that even though `CBoundedStr<0>` is a well-formed type it cannot
135+
/// be safely created.
136+
#[repr(transparent)]
137+
pub struct CBoundedStr<const N: usize>(CStr);
138+
139+
impl<const N: usize> CBoundedStr<N> {
140+
/// Creates a [`CBoundedStr`] form a [`CStr`].
141+
///
142+
/// The provided `CStr` must be shorten than `N`.
143+
#[inline]
144+
pub fn from_c_str(c_str: &CStr) -> Result<&Self, CStrConvertError> {
145+
if c_str.0.len() > N {
146+
return Err(CStrConvertError::BoundExceeded);
147+
}
148+
149+
// SAFETY: We just checked that all properties hold.
150+
Ok(unsafe { Self::from_c_str_unchecked(c_str) })
151+
}
152+
153+
/// Creates a [`CBoundedStr`] form a [`CStr`] without performing any sanity
154+
/// checks.
155+
///
156+
/// # Safety
157+
///
158+
/// The provided CStr must be shorten than `N`.
159+
#[inline]
160+
pub const unsafe fn from_c_str_unchecked(c_str: &CStr) -> &Self {
161+
&*(c_str as *const CStr as *const Self)
162+
}
163+
164+
/// Creates a [`CBoundedStr`] form a `[u8]`.
165+
///
166+
/// The provided slice must be nul-terminated, does not contain any
167+
/// interior nul bytes and be shorten than `N`.
168+
#[inline]
169+
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, CStrConvertError> {
170+
Self::from_c_str(CStr::from_bytes_with_nul(bytes)?)
171+
}
172+
173+
/// Creates a [`CBoundedStr`] form a `[u8]` without performing any sanity
174+
/// checks.
175+
///
176+
/// # Safety
177+
///
178+
/// The provided slice must be nul-terminated, does not contain any
179+
/// interior nul bytes and be shorten than `N`.
180+
#[inline]
181+
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &Self {
182+
Self::from_c_str_unchecked(CStr::from_bytes_with_nul_unchecked(bytes))
183+
}
184+
185+
/// Creates a [`CBoundedStr`] form a `[u8; N]` without performing any sanity
186+
/// checks.
187+
///
188+
/// # Safety
189+
///
190+
/// The provided slice must be nul-terminated.
191+
#[inline]
192+
pub const unsafe fn from_exact_bytes_with_nul_unchecked(bytes: &[u8; N]) -> &Self {
193+
Self::from_bytes_with_nul_unchecked(bytes)
194+
}
195+
196+
/// Relax the bound from `N` to `M`.
197+
///
198+
/// `M` must be no less than the bound `N`.
199+
#[inline]
200+
pub const fn relax_bound<const M: usize>(&self) -> &CBoundedStr<M> {
201+
link_time_assert!(N <= M, "relaxed bound should be no less than current bound");
202+
unsafe { CBoundedStr::<M>::from_c_str_unchecked(&self.0) }
203+
}
204+
205+
/// Expand the string a c_char array with current bound, filling remaining bytes with zero.
206+
#[inline]
207+
pub const fn into_char_array(&self) -> [c_types::c_char; N] {
208+
let mut ret: [c_types::c_char; N] = [0; N];
209+
let mut i = 0;
210+
while i < self.0 .0.len() {
211+
ret[i] = self.0 .0[i] as _;
212+
i += 1;
213+
}
214+
ret
215+
}
216+
}
217+
218+
impl<const N: usize> Deref for CBoundedStr<N> {
219+
type Target = CStr;
220+
221+
fn deref(&self) -> &Self::Target {
222+
&self.0
223+
}
224+
}
225+
126226
/// Creates a new `CStr` from a string literal.
127227
///
128228
/// The string literal should not contain any `NUL` bytes.
@@ -142,3 +242,53 @@ macro_rules! c_str {
142242
C
143243
}};
144244
}
245+
246+
/// Creates a new `CBoundedStr` from a string literal.
247+
///
248+
/// The string literal should not contain any `NUL` bytes, and its length with NUL should not
249+
/// exceed the bound supplied.
250+
///
251+
/// # Examples
252+
///
253+
/// ```rust,no_run
254+
/// // If no bound is specified, the tighest bound will be inferred:
255+
/// const MY_CSTR: &'static CBoundedStr<17> = c_bounded_str!("My awesome CStr!");
256+
/// ```
257+
///
258+
/// ```rust,compile_fail
259+
/// // This would not compile as the type is `CBoundedStr<17>`.
260+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!");
261+
/// ```
262+
///
263+
/// ```rust,no_run
264+
/// // You can relax the bound using the `relax_bound` method.
265+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!").relax_bound();
266+
/// ```
267+
///
268+
/// ```rust,no_run
269+
/// // Or alternatively specify a bound when invoking macro.
270+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!(100, "My awesome CStr!");
271+
/// ```
272+
///
273+
/// ```rust,compile_fail
274+
/// // shouldn't compile as the string is longer than the specified bound.
275+
/// const MY_CSTR: &'static CBoundedStr<10> = c_bounded_str!(100, "My awesome CStr!");
276+
/// ```
277+
#[macro_export]
278+
macro_rules! c_bounded_str {
279+
($str:literal) => {{
280+
let s = $crate::c_str_check::append_nul!($str);
281+
unsafe { $crate::CBoundedStr::from_exact_bytes_with_nul_unchecked(s) }
282+
}};
283+
($bound:expr, $str:literal) => {{
284+
const C: &'static $crate::CBoundedStr<$bound> = {
285+
let s = $crate::c_str_check::append_nul!($str);
286+
if s.len() > $bound {
287+
// NOPANIC: This is a const panic.
288+
panic!("bound exceeded");
289+
}
290+
unsafe { $crate::CBoundedStr::<$bound>::from_bytes_with_nul_unchecked(s) }
291+
};
292+
C
293+
}};
294+
}

0 commit comments

Comments
 (0)