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}