1#[doc(hidden)]
5#[allow(clippy::panic)]
6pub const fn validate_cstr_contents(bytes: &[u8]) {
7 let str_len = bytes.len() - 1usize;
9 if bytes[str_len] != b'\0' {
10 panic!("cstr must be null terminated");
11 }
12
13 let mut i = 0;
15 while bytes[i] != b'\0' {
16 i += 1;
17 }
18
19 if i != str_len {
21 panic!("cstr string cannot contain null character outside of last element");
22 }
23}
24
25#[macro_export]
26macro_rules! cstr {
27 ($s:expr) => {{
28 let mut bytes = $s.as_bytes();
29 if bytes[bytes.len() - 1usize] != b'\0' {
30 bytes = concat!($s, "\0").as_bytes();
31 }
32
33 $crate::cstr::validate_cstr_contents(bytes);
34 unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(bytes) }
35 }};
36}
37
38#[macro_export]
39macro_rules! cstr_u8 {
40 ($s:literal) => {{
41 $crate::cstr::validate_cstr_contents($s);
42 unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked($s as &[u8]) }
43 }};
44}
45
46#[cfg(test)]
47mod tests {
48 #[test]
49 fn test_cstr() {
50 assert_eq!(b"/dev/null", cstr!("/dev/null").to_bytes());
51 assert_eq!(b"/dev/null", cstr!("/dev/null\0").to_bytes());
52 assert_eq!(b"/dev/null", cstr_u8!(b"/dev/null\0").to_bytes());
53 }
54
55 #[test]
56 #[should_panic]
57 fn test_invalid_cstr_with_extra_null_character() {
58 _ = cstr!("/dev/null\0\0");
59 }
60
61 #[test]
62 #[should_panic]
63 fn test_invalid_cstr_u8_without_terminatid_nul() {
64 _ = cstr_u8!(b"/dev/null");
65 }
66
67 #[test]
68 #[should_panic]
69 fn test_invalid_cstr_with_nul_character() {
70 _ = cstr!("/dev/\0null");
71 }
72}