The signature of execv
(the most convenient function in the exec family in this case):
pub unsafe extern fn execv(prog: *const c_char,
argv: *const *const c_char)
-> c_int
It takes a C string and an array of C strings; the latter doesn't have its size passed explicitly but is terminated with a null entry.
Rust has a wrapper around C strings, i.e. nul-terminated strings, called CString
(owned) or CStr
(borrowed). On Unix, to go from an OsStr
to CString
, we can call as_bytes
on the OsStr
to get a &[u8]
and pass to CString::new
, which copies the bytes into a new Vec<u8>
and adds a nul terminator. (We can't go directly to CStr
without copying because the existing buffer in an OsStr
might not be nul-terminated. The original argv from libc might be, but not the copy made by args_os
.)
use std::os::unix::ffi::OsStrExt;
let args: Vec<CString> = env::args_os().map(|arg| CString::new(arg.as_bytes()).unwrap()).collect();
But that's a vector of CString wrapper objects; we need a vector of raw *const c_char
s. The original Vec
is still needed to keep the allocations alive, though.
let mut args_raw: Vec<*const c_char> = args.iter().map(|arg| arg.as_ptr()).collect();
Add the null value:
args_raw.push(std::ptr::null());
And we can get the raw argv
to pass to C:
let argv: *const *const c_char = args_raw.as_ptr();
A similar process to get prog
(using the as_os_str
method on Path
):
let exec_cstr: CString = CString::new(exec.as_os_str().as_bytes()).unwrap();
let prog: *const c_char = exec_cstr.as_ptr();
And do the exec:
unsafe { libc::execv(prog, argv); }
If execution continues, there was an error. C code would check errno
, but errno
is typically defined as a macro which expands to something like (*__errno_location())
, depending on the OS. The libc
crate directly exposes the underlying function rather than abstracting, so it's best not to use it directly. But such an abstraction can be found in std
:
use std::io::Error;
let errno: i32 = Error::last_os_error().raw_os_error().unwrap();
println!("errno = {}", errno);
(On Windows, things are a bit different: OsStr
logically represents a series of UTF-16 u16
values, which on Windows is equivalent to wchar_t
. So you'd probably want to use the wide-char based function _wexecv
and encode_wide
in std::os::windows::ffi::OsStrExt
.)