Search code examples
rustlibcunsafeexecvp

Passing Vec<String> from Rust to char** in C


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()?


Solution

  • 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