Skip to content

Commit 6e3c4bb

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 4f5408e commit 6e3c4bb

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

rust/kernel/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub mod user_ptr;
7070
pub use macros;
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

Lines changed: 159 additions & 0 deletions
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 build_assert::build_assert;
8+
use core::ops::Deref;
9+
710
use crate::bindings;
811
use crate::c_types;
912

@@ -123,6 +126,112 @@ 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+
/// Relaxes 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+
build_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+
/// Converts the string to a c_char array with the current bound, fills the
206+
/// remaining bytes with zero.
207+
#[inline]
208+
pub const fn to_char_array(&self) -> [c_types::c_char; N] {
209+
let mut ret: [c_types::c_char; N] = [0; N];
210+
let mut i = 0;
211+
while i < self.0 .0.len() {
212+
ret[i] = self.0 .0[i] as _;
213+
i += 1;
214+
}
215+
ret
216+
}
217+
218+
/// Expands the string a c_char array, fills remaining bytes with zero.
219+
///
220+
/// `M` must be no less than the bound `N`.
221+
#[inline]
222+
pub const fn expand_to_char_array<const M: usize>(&self) -> [c_types::c_char; M] {
223+
self.relax_bound().to_char_array()
224+
}
225+
}
226+
227+
impl<const N: usize> Deref for CBoundedStr<N> {
228+
type Target = CStr;
229+
230+
fn deref(&self) -> &Self::Target {
231+
&self.0
232+
}
233+
}
234+
126235
/// Creates a new `CStr` from a string literal.
127236
///
128237
/// The string literal should not contain any `NUL` bytes.
@@ -142,3 +251,53 @@ macro_rules! c_str {
142251
C
143252
}};
144253
}
254+
255+
/// Creates a new `CBoundedStr` from a string literal.
256+
///
257+
/// The string literal should not contain any `NUL` bytes, and its length with NUL should not
258+
/// exceed the bound supplied.
259+
///
260+
/// # Examples
261+
///
262+
/// ```rust,no_run
263+
/// // If no bound is specified, the tighest bound will be inferred:
264+
/// const MY_CSTR: &'static CBoundedStr<17> = c_bounded_str!("My awesome CStr!");
265+
/// ```
266+
///
267+
/// ```rust,compile_fail
268+
/// // This would not compile as the type is `CBoundedStr<17>`.
269+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!");
270+
/// ```
271+
///
272+
/// ```rust,no_run
273+
/// // You can relax the bound using the `relax_bound` method.
274+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!").relax_bound();
275+
/// ```
276+
///
277+
/// ```rust,no_run
278+
/// // Or alternatively specify a bound when invoking macro.
279+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!(100, "My awesome CStr!");
280+
/// ```
281+
///
282+
/// ```rust,compile_fail
283+
/// // shouldn't compile as the string is longer than the specified bound.
284+
/// const MY_CSTR: &'static CBoundedStr<10> = c_bounded_str!(100, "My awesome CStr!");
285+
/// ```
286+
#[macro_export]
287+
macro_rules! c_bounded_str {
288+
($str:literal) => {{
289+
let s = $crate::macros::append_nul!($str);
290+
unsafe { $crate::CBoundedStr::from_exact_bytes_with_nul_unchecked(s) }
291+
}};
292+
($bound:expr, $str:literal) => {{
293+
const C: &'static $crate::CBoundedStr<$bound> = {
294+
let s = $crate::macros::append_nul!($str);
295+
if s.len() > $bound {
296+
// NOPANIC: This is a const panic.
297+
panic!("bound exceeded");
298+
}
299+
unsafe { $crate::CBoundedStr::<$bound>::from_bytes_with_nul_unchecked(s) }
300+
};
301+
C
302+
}};
303+
}

0 commit comments

Comments
 (0)