use std::{
ffi::CString,
fs::File,
io::{Read as _, Seek},
os::fd::{AsRawFd, OwnedFd, RawFd},
ptr,
};
use brunch::{benches, Bench};
use libc::{_exit, c_char, c_int, execve, fork, waitpid, SYS_execve, SYS_execveat, AT_EMPTY_PATH};
use nix::{
errno::Errno,
fcntl::{open, OFlag},
sys::{
memfd::{memfd_create, MFdFlags},
stat::Mode,
},
unistd::{lseek, write as nix_write},
};
fn do_fork_exec(exec_fn: impl FnOnce() + Send + 'static) {
unsafe {
let pid = fork();
if pid == 0 {
exec_fn();
_exit(127);
} else if pid < 0 {
panic!("fork() failed: {:?}", Errno::last());
} else {
let mut status: c_int = 0;
let w = waitpid(pid, &mut status, 0);
if w < 0 {
panic!("waitpid() failed: {:?}", Errno::last());
}
}
}
}
fn bench_execve_dev_null() {
do_fork_exec(|| unsafe {
let path_c = CString::new("/dev/null").unwrap();
let argv: [*const c_char; 1] = [ptr::null()];
let envp: [*const c_char; 1] = [ptr::null()];
execve(path_c.as_ptr(), argv.as_ptr(), envp.as_ptr());
});
}
fn bench_execve_bin_true() {
do_fork_exec(|| unsafe {
let path_c = CString::new("/bin/true").unwrap();
let argv: [*const c_char; 1] = [ptr::null()];
let envp: [*const c_char; 1] = [ptr::null()];
execve(path_c.as_ptr(), argv.as_ptr(), envp.as_ptr());
});
}
fn bench_execve_bin_true_null_null() {
do_fork_exec(|| unsafe {
let path_c = CString::new("/bin/true").unwrap();
libc::syscall(
SYS_execve,
path_c.as_ptr(),
ptr::null::<*const c_char>(),
ptr::null::<*const c_char>(),
);
});
}
fn bench_execveat_bin_true_fd(fd: RawFd) {
do_fork_exec(move || unsafe {
let c_empty = CString::new("").unwrap();
let argv: [*const c_char; 1] = [ptr::null()];
let envp: [*const c_char; 1] = [ptr::null()];
libc::syscall(
SYS_execveat,
fd,
c_empty.as_ptr(),
argv.as_ptr(),
envp.as_ptr(),
AT_EMPTY_PATH,
);
});
}
fn bench_execveat_memfd_bin_true_fd(memfd_fd: RawFd) {
do_fork_exec(move || unsafe {
let c_empty = CString::new("").unwrap();
let argv: [*const c_char; 1] = [ptr::null()];
let envp: [*const c_char; 1] = [ptr::null()];
libc::syscall(
SYS_execveat,
memfd_fd,
c_empty.as_ptr(),
argv.as_ptr(),
envp.as_ptr(),
AT_EMPTY_PATH,
);
});
}
fn prepare_memfd_with_bin_true() -> OwnedFd {
let bintrue_file = open("/bin/true", OFlag::O_RDONLY, Mode::empty())
.map(File::from)
.expect("Failed to open /bin/true");
let memfd =
memfd_create("memfd_bin_true", MFdFlags::MFD_CLOEXEC).expect("Failed to create memfd");
let mut buf = [0u8; 4096];
let mut total_file = bintrue_file;
total_file
.seek(std::io::SeekFrom::Start(0))
.expect("seek /bin/true failed");
loop {
let n = total_file.read(&mut buf).unwrap();
if n == 0 {
break;
}
let written = nix_write(&memfd, &buf[..n]).expect("write to memfd failed");
if written < n {
panic!("short write to memfd?! wrote {}", written);
}
}
let _ = lseek(&memfd, 0, nix::unistd::Whence::SeekSet);
memfd
}
fn main() {
let bintrue_fd = open("/bin/true", OFlag::O_RDONLY, Mode::empty())
.expect("Failed to open /bin/true for execveat");
let memfd_fd = prepare_memfd_with_bin_true();
benches!(
inline:
Bench::new("Execve(/dev/null, [], [])").run(|| {
bench_execve_dev_null();
}),
Bench::new("Execve(/bin/true, [], [])").run(|| {
bench_execve_bin_true();
}),
Bench::new("Execve(/bin/true, NULL, NULL)").run(|| {
bench_execve_bin_true_null_null();
}),
Bench::new("Execveat(fd-of-/bin/true, [], [], AT_EMPTY_PATH)").run(|| {
bench_execveat_bin_true_fd(bintrue_fd.as_raw_fd());
}),
Bench::new("Execveat(memfd-of-/bin/true, [], [], AT_EMPTY_PATH)").run(|| {
bench_execveat_memfd_bin_true_fd(memfd_fd.as_raw_fd());
}),
);
}