1use crate::ffi::{CStr, CString};
2use crate::mem::MaybeUninit;
3use crate::path::Path;
4use crate::slice;
5use crate::{io, ptr};
6
7// Make sure to stay under 4096 so the compiler doesn't insert a probe frame:
8// https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html
9#[cfg(not(target_os = "espidf"))]
10const MAX_STACK_ALLOCATION: usize = 384;
11#[cfg(target_os = "espidf")]
12const MAX_STACK_ALLOCATION: usize = 32;
13
14const NUL_ERR: io::Error =
15 io::const_io_error!(io::ErrorKind::InvalidInput, "file name contained an unexpected NUL byte");
16
17#[inline]
18pub fn run_path_with_cstr<T>(path: &Path, f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
19 run_with_cstr(path.as_os_str().as_encoded_bytes(), f)
20}
21
22#[inline]
23pub fn run_with_cstr<T>(bytes: &[u8], f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
24 // Dispatch and dyn erase the closure type to prevent mono bloat.
25 // See https://github.com/rust-lang/rust/pull/121101.
26 if bytes.len() >= MAX_STACK_ALLOCATION {
27 run_with_cstr_allocating(bytes, f)
28 } else {
29 unsafe { run_with_cstr_stack(bytes, f) }
30 }
31}
32
33/// # Safety
34///
35/// `bytes` must have a length less than `MAX_STACK_ALLOCATION`.
36unsafe fn run_with_cstr_stack<T>(
37 bytes: &[u8],
38 f: &dyn Fn(&CStr) -> io::Result<T>,
39) -> io::Result<T> {
40 let mut buf: MaybeUninit<[u8; 384]> = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
41 let buf_ptr: *mut u8 = buf.as_mut_ptr() as *mut u8;
42
43 unsafe {
44 ptr::copy_nonoverlapping(src:bytes.as_ptr(), dst:buf_ptr, count:bytes.len());
45 buf_ptr.add(bytes.len()).write(val:0);
46 }
47
48 match CStr::from_bytes_with_nul(bytes:unsafe { slice::from_raw_parts(data:buf_ptr, len:bytes.len() + 1) }) {
49 Ok(s: &CStr) => f(s),
50 Err(_) => Err(NUL_ERR),
51 }
52}
53
54#[cold]
55#[inline(never)]
56fn run_with_cstr_allocating<T>(bytes: &[u8], f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
57 match CString::new(bytes) {
58 Ok(s: CString) => f(&s),
59 Err(_) => Err(NUL_ERR),
60 }
61}
62