mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-12-01 15:14:26 +08:00
Add Rust support for null terminated arrays
This adds support for "null-terminated arrays of nul-terminated strings" as used in execve, etc.
This commit is contained in:
parent
ed3fdaa665
commit
621a3a6a8b
|
@ -39,6 +39,7 @@ mod io;
|
|||
mod job_group;
|
||||
mod locale;
|
||||
mod nix;
|
||||
mod null_terminated_array;
|
||||
mod parse_constants;
|
||||
mod parse_tree;
|
||||
mod parse_util;
|
||||
|
|
134
fish-rust/src/null_terminated_array.rs
Normal file
134
fish-rust/src/null_terminated_array.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use std::ffi::{c_char, CStr, CString};
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
|
||||
pub trait NulTerminatedString {
|
||||
type CharType: Copy;
|
||||
|
||||
/// Return a pointer to the null-terminated string.
|
||||
fn c_str(&self) -> *const Self::CharType;
|
||||
}
|
||||
|
||||
impl NulTerminatedString for CStr {
|
||||
type CharType = c_char;
|
||||
|
||||
fn c_str(&self) -> *const c_char {
|
||||
self.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// This supports the null-terminated array of NUL-terminated strings consumed by exec.
|
||||
/// Given a list of strings, construct a vector of pointers to those strings contents.
|
||||
/// This is used for building null-terminated arrays of null-terminated strings.
|
||||
/// *Important*: the vector stores pointers into the interior of the input strings, which may be
|
||||
/// subject to the small-string optimization. This means that pointers will be left dangling if any
|
||||
/// input string is deallocated *or moved*. This class should only be used in transient calls.
|
||||
pub struct NullTerminatedArray<'p, T: NulTerminatedString + ?Sized> {
|
||||
pointers: Vec<*const T::CharType>,
|
||||
_phantom: PhantomData<&'p T>,
|
||||
}
|
||||
|
||||
impl<'p, Str: NulTerminatedString + ?Sized> NullTerminatedArray<'p, Str> {
|
||||
/// Return the list of pointers, appropriate for envp or argv.
|
||||
/// Note this returns a mutable array of const strings. The caller may rearrange the strings but
|
||||
/// not modify their contents.
|
||||
/// We freely give out mutable pointers even though we are not mut; this is because most of the uses
|
||||
/// expect the array to be mutable even though fish does not mutate it, so it's either this or cast
|
||||
/// away the const at the call site.
|
||||
fn get(&self) -> *mut *const Str::CharType {
|
||||
assert!(
|
||||
!self.pointers.is_empty() && self.pointers.last().unwrap().is_null(),
|
||||
"Should have null terminator"
|
||||
);
|
||||
self.pointers.as_ptr() as *mut *const Str::CharType
|
||||
}
|
||||
|
||||
/// Construct from a list of "strings".
|
||||
/// This holds pointers into the strings.
|
||||
pub fn new<S: AsRef<Str>>(strs: &'p [S]) -> Self {
|
||||
let mut pointers = Vec::with_capacity(1 + strs.len());
|
||||
for s in strs {
|
||||
pointers.push(s.as_ref().c_str());
|
||||
}
|
||||
pointers.push(ptr::null());
|
||||
NullTerminatedArray {
|
||||
pointers,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A container which exposes a null-terminated array of pointers to strings that it owns.
|
||||
/// This is useful for persisted null-terminated arrays, e.g. the exported environment variable
|
||||
/// list. This assumes u8, since we don't need this for wide chars.
|
||||
pub struct OwningNullTerminatedArray {
|
||||
// Note that null_terminated_array holds pointers into our boxed strings.
|
||||
// The 'static is a lie.
|
||||
strings: Pin<Box<[CString]>>,
|
||||
null_terminated_array: NullTerminatedArray<'static, CStr>,
|
||||
}
|
||||
|
||||
impl OwningNullTerminatedArray {
|
||||
/// Cover over null_terminated_array.get().
|
||||
fn get(&self) -> *mut *const c_char {
|
||||
self.null_terminated_array.get()
|
||||
}
|
||||
|
||||
/// Construct, taking ownership of a list of strings.
|
||||
pub fn new(strs: Vec<CString>) -> Self {
|
||||
let strings = strs.into_boxed_slice();
|
||||
// Safety: we're pinning the strings, so they won't move.
|
||||
let string_slice: &'static [CString] = unsafe { std::mem::transmute(&*strings) };
|
||||
OwningNullTerminatedArray {
|
||||
strings: Pin::from(strings),
|
||||
null_terminated_array: NullTerminatedArray::new(string_slice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a null-terminated array of pointers to something.
|
||||
pub fn null_terminated_array_length<T>(mut arr: *const *const T) -> usize {
|
||||
let mut len = 0;
|
||||
// Safety: caller must ensure that arr is null-terminated.
|
||||
unsafe {
|
||||
while !arr.read().is_null() {
|
||||
arr = arr.offset(1);
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_terminated_array_length() {
|
||||
let arr = [&1, &2, &3, std::ptr::null()];
|
||||
assert_eq!(null_terminated_array_length(arr.as_ptr()), 3);
|
||||
let arr: &[*const u64] = &[std::ptr::null()];
|
||||
assert_eq!(null_terminated_array_length(arr.as_ptr()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_terminated_array() {
|
||||
let owned_strs = &[CString::new("foo").unwrap(), CString::new("bar").unwrap()];
|
||||
let strs = owned_strs.iter().map(|s| s.as_c_str()).collect::<Vec<_>>();
|
||||
let arr = NullTerminatedArray::new(&strs);
|
||||
let ptr = arr.get();
|
||||
unsafe {
|
||||
assert_eq!(CStr::from_ptr(*ptr).to_str().unwrap(), "foo");
|
||||
assert_eq!(CStr::from_ptr(*ptr.offset(1)).to_str().unwrap(), "bar");
|
||||
assert_eq!(*ptr.offset(2), ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owning_null_terminated_array() {
|
||||
let owned_strs = vec![CString::new("foo").unwrap(), CString::new("bar").unwrap()];
|
||||
let arr = OwningNullTerminatedArray::new(owned_strs);
|
||||
let ptr = arr.get();
|
||||
unsafe {
|
||||
assert_eq!(CStr::from_ptr(*ptr).to_str().unwrap(), "foo");
|
||||
assert_eq!(CStr::from_ptr(*ptr.offset(1)).to_str().unwrap(), "bar");
|
||||
assert_eq!(*ptr.offset(2), ptr::null());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user