jump_dir/
c_string.rs

1/*
2 * Description: Methods to interop with null-terminated C strings.
3 * This code is largely copied from the rust compiler's small_c_string.rs: https://github.com/rust-lang/rust/blob/52daa7d835e7ff51cb387340082bf9a59b949738/library/std/src/sys/pal/common/small_c_string.rs.
4 * The rust compiler is distributed under both the MIT and Apache v2 licenses.
5 *
6 * Copyright (C) 2025 d@nny mc² <dmc2@hypnicjerk.ai>
7 * SPDX-License-Identifier: LGPL-3.0-or-later
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published
11 * by the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21 */
22
23//! Methods to interop with null-terminated C strings.
24//!
25//! This code is largely copied from the rust compiler's [`small_c_string.rs`](https://github.com/rust-lang/rust/blob/52daa7d835e7ff51cb387340082bf9a59b949738/library/std/src/sys/pal/common/small_c_string.rs).
26
27use std::{
28  ffi::{CStr, CString},
29  io,
30  mem::MaybeUninit,
31  path::Path,
32  ptr, slice,
33};
34
35
36// Make sure to stay under 4096 so the compiler doesn't insert a probe frame:
37// https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html
38cfg_if::cfg_if! {
39  if #[cfg(target_os = "espidf")] {
40    pub const MAX_STACK_ALLOCATION: usize = 32;
41  } else {
42    pub const MAX_STACK_ALLOCATION: usize = 384;
43  }
44}
45
46
47cfg_if::cfg_if! {
48  if #[cfg(feature = "nightly")] {
49    const fn null_err() -> io::Error {
50      io::const_error!(
51        io::ErrorKind::InvalidInput,
52        "file name contained an unexpected NUL byte"
53      )
54    }
55  } else {
56    fn null_err() -> io::Error {
57      io::Error::new(
58          io::ErrorKind::InvalidInput,
59          "file name contained an unexpected NUL byte"
60        )
61    }
62  }
63}
64
65
66// rustc dyn erases the closure type to avoid bloat in codegen
67// (https://github.com/rust-lang/rust/pull/121101), but we're not doing the whole stdlib and care
68// less about code bloat for the small number of call sites in this library.
69#[inline]
70pub fn run_path_with_cstr<T>(path: &Path, f: impl FnOnce(&CStr) -> io::Result<T>) -> io::Result<T> {
71  run_with_cstr(path.as_os_str().as_encoded_bytes(), f)
72}
73
74
75#[inline]
76pub fn run_with_cstr<T>(bytes: &[u8], f: impl FnOnce(&CStr) -> io::Result<T>) -> io::Result<T> {
77  if bytes.len() >= MAX_STACK_ALLOCATION {
78    run_with_cstr_allocating(bytes, f)
79  } else {
80    unsafe { run_with_cstr_stack(bytes, f) }
81  }
82}
83
84/// # Safety
85///
86/// `bytes` must have a length less than [`MAX_STACK_ALLOCATION`].
87unsafe fn run_with_cstr_stack<T>(
88  bytes: &[u8],
89  f: impl FnOnce(&CStr) -> io::Result<T>,
90) -> io::Result<T> {
91  let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
92  let buf_ptr = buf.as_mut_ptr() as *mut u8;
93
94  unsafe {
95    ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
96    buf_ptr.add(bytes.len()).write(0);
97  }
98
99  match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) }) {
100    Ok(s) => f(s),
101    Err(_) => Err(null_err()),
102  }
103}
104
105
106#[cold]
107#[inline(never)]
108fn run_with_cstr_allocating<T>(
109  bytes: &[u8],
110  f: impl FnOnce(&CStr) -> io::Result<T>,
111) -> io::Result<T> {
112  match CString::new(bytes) {
113    Ok(s) => f(&s),
114    Err(_) => Err(null_err()),
115  }
116}