I've been trying to write a shell in Rust that links directly to the libc
library. I've used a Vec<String>
to hold the arguments to be passed to execvp()
, but it seems that my conversion to char **
has not been successful. Upon execution, all the parameters became null strings.
Here's the piece of code involved.
fn safe_execvp(path: String, argv: Vec<String>) -> Result<(), i32> {
unsafe {
let c_path = CString::new(path.as_str()).unwrap();
let mut c_argv_vec = Vec::new();
for arg in &argv {
let c_arg = CString::new(arg.as_str()).unwrap().as_ptr();
c_argv_vec.push(c_arg);
}
c_argv_vec.push(std::ptr::null());
match execvp(c_file.as_ptr(), c_argv_vec.as_ptr()) {
num => Err(num),
}
}
}
execvp
is the C library function defined as fn execvp(file: *const i8, argv: *const*const i8) -> i32;
.
I'm not sure what I've done wrong. Is it because the memory for the arguments were released before calling execvp()
?
You are creating CString
instances and immediately fetching a pointer. Consequently, as you well guessed, ownership of this string was dropped prematurely. This is similar to returning a reference to a local instance, except that since pointers do not retain lifetime information, this case does not trigger a compilation error.
The solution to your problem is to retain the owned C-style strings during the function's scope, and produce a pointer to pointers of the same content separately.
let cstr_argv: Vec<_> = argv.iter()
.map(|arg| CString::new(arg.as_str()).unwrap())
.collect();
let mut p_argv: Vec<_> = cstr_argv.iter() // do NOT into_iter()
.map(|arg| arg.as_ptr())
.collect();
p_argv.push(std::ptr::null());
let p: *const *const c_char = p_argv.as_ptr();
Playground.
See also: CString::new().unwrap().as_ptr() gives empty *const c_char
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With