use wasmer::FromToNativeWasmType;
use super::*;
use crate::{
os::task::{OwnedTaskStatus, TaskStatus},
syscalls::*,
WasiFs, VIRTUAL_ROOT_FD,
};
#[instrument(level = "trace", skip_all, fields(name = field::Empty, %args_len), ret)]
pub fn proc_exec3<M: MemorySize>(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
name: WasmPtr<u8, M>,
name_len: M::Offset,
args: WasmPtr<u8, M>,
args_len: M::Offset,
envs: WasmPtr<u8, M>,
envs_len: M::Offset,
search_path: Bool,
path: WasmPtr<u8, M>,
path_len: M::Offset,
) -> Result<Errno, WasiError> {
WasiEnv::process_signals_and_exit(&mut ctx)?;
if let Some(exit_code) = unsafe { handle_rewind::<M, i32>(&mut ctx) } {
let exit_code = ExitCode::from_native(exit_code);
ctx.data().process.terminate(exit_code);
return Err(WasiError::Exit(exit_code));
}
let memory = unsafe { ctx.data().memory_view(&ctx) };
let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| {
warn!("failed to execve as the name could not be read - {}", err);
WasiError::Exit(Errno::Inval.into())
})?;
Span::current().record("name", name.as_str());
let args = args.read_utf8_string(&memory, args_len).map_err(|err| {
warn!("failed to execve as the args could not be read - {}", err);
WasiError::Exit(Errno::Inval.into())
})?;
let args: Vec<_> = args
.split(&['\n', '\r'])
.map(|a| a.to_string())
.filter(|a| !a.is_empty())
.collect();
let envs = if !envs.is_null() {
let envs = envs.read_utf8_string(&memory, envs_len).map_err(|err| {
warn!("failed to execve as the envs could not be read - {}", err);
WasiError::Exit(Errno::Inval.into())
})?;
let envs = envs
.split(&['\n', '\r'])
.map(|a| a.to_string())
.filter(|a| !a.is_empty());
let mut vec = vec![];
for env in envs {
let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval));
vec.push((key.to_string(), value.to_string()));
}
Some(vec)
} else {
None
};
if search_path == Bool::True && !name.contains('/') {
let path_str;
let path = if path.is_null() {
vec!["/usr/local/bin", "/bin", "/usr/bin"]
} else {
path_str = path.read_utf8_string(&memory, path_len).map_err(|err| {
warn!("failed to execve as the path could not be read - {}", err);
WasiError::Exit(Errno::Inval.into())
})?;
path_str.split(':').collect()
};
let (_, state, inodes) =
unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) {
FindExecutableResult::Found(p) => name = p,
FindExecutableResult::AccessError => return Ok(Errno::Access),
FindExecutableResult::NotFound => return Ok(Errno::Noexec),
}
} else if name.starts_with("./") {
name = ctx.data().state.fs.relative_path_to_absolute(name);
}
trace!(name);
let preopen = ctx.data().state.preopen.clone();
let (_, cur_dir) = {
let (memory, state, inodes) =
unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
match state.fs.get_current_dir(inodes, crate::VIRTUAL_ROOT_FD) {
Ok(a) => a,
Err(err) => {
warn!("failed to create subprocess for fork - {}", err);
return Err(WasiError::Exit(err.into()));
}
}
};
let new_store = ctx.data().runtime.new_store();
if let Some(mut vfork) = ctx.data_mut().vfork.take() {
let mut child_env = Box::new(ctx.data().clone());
let child_pid = ctx.data().process.pid();
tracing::debug!(
%child_pid,
vfork_pid = %vfork.env.process.pid(),
"proc_exec in presence of vfork"
);
let mut vfork_env = vfork.env.clone();
vfork_env.swap_inner(ctx.data_mut());
std::mem::swap(vfork_env.as_mut(), ctx.data_mut());
let mut wasi_env = *vfork_env;
wasi_env.owned_handles.push(vfork.handle.clone());
_prepare_wasi(&mut wasi_env, Some(args), envs, None);
let stack_lower = wasi_env.layout.stack_lower;
let stack_upper = wasi_env.layout.stack_upper;
let mut err_exit_code: ExitCode = Errno::Success.into();
let spawn_result = {
let bin_factory = Box::new(ctx.data().bin_factory.clone());
let tasks = wasi_env.tasks().clone();
let mut config = Some(wasi_env);
match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut config) {
Ok(a) => Ok(()),
Err(err) => {
if !err.is_not_found() {
error!("builtin failed - {}", err);
}
let env = config.take().unwrap();
let name_inner = name.clone();
__asyncify_light(ctx.data(), None, async {
let ret = bin_factory.spawn(name_inner, env).await;
match ret {
Ok(ret) => {
trace!(%child_pid, "spawned sub-process");
Ok(())
}
Err(err) => {
err_exit_code = conv_spawn_err_to_exit_code(&err);
debug!(%child_pid, "process failed with (err={})", err_exit_code);
Err(Errno::Noexec)
}
}
})
.unwrap()
}
}
};
match spawn_result {
Err(e) => {
child_env.swap_inner(ctx.data_mut());
std::mem::swap(child_env.as_mut(), ctx.data_mut());
ctx.data_mut().vfork = Some(vfork);
return Ok(e);
}
Ok(()) => {
let rewind_stack = vfork.rewind_stack.freeze();
let store_data = vfork.store_data;
unwind::<M, _>(ctx, move |mut ctx, _, _| {
match rewind::<M, _>(
ctx,
None,
rewind_stack,
store_data,
ForkResult {
pid: child_pid.raw() as Pid,
ret: Errno::Success,
},
) {
Errno::Success => OnCalledAction::InvokeAgain,
err => {
warn!("fork failed - could not rewind the stack - errno={}", err);
OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
}
}
})?;
Ok(Errno::Success)
}
}
}
else {
let mut wasi_env = ctx.data().clone();
_prepare_wasi(&mut wasi_env, Some(args), envs, None);
let bin_factory = ctx.data().bin_factory.clone();
let tasks = wasi_env.tasks().clone();
let bin_factory = Box::new(ctx.data().bin_factory.clone());
let mut builder = Some(wasi_env);
let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
Ok(a) => Ok(a),
Err(err) => {
if !err.is_not_found() {
error!("builtin failed - {}", err);
}
let env = builder.take().unwrap();
InlineWaker::block_on(bin_factory.spawn(name, env))
}
};
match process {
Ok(mut process) => {
let env = ctx.data();
let thread = env.thread.clone();
let res = __asyncify_with_deep_sleep::<M, _, _>(ctx, async move {
process
.wait_finished()
.await
.unwrap_or_else(|_| Errno::Child.into())
.to_native()
})?;
match res {
AsyncifyAction::Finish(mut ctx, result) => {
let exit_code = ExitCode::from_native(result);
ctx.data().process.terminate(exit_code);
WasiEnv::process_signals_and_exit(&mut ctx)?;
Err(WasiError::Exit(Errno::Unknown.into()))
}
AsyncifyAction::Unwind => Ok(Errno::Success),
}
}
Err(err) => {
warn!(
"failed to execve as the process could not be spawned (fork)[0] - {}",
err
);
Ok(Errno::Noexec)
}
}
}
}
pub(crate) enum FindExecutableResult {
Found(String),
AccessError,
NotFound,
}
pub(crate) fn find_executable_in_path<'a>(
fs: &WasiFs,
inodes: &WasiInodes,
path: impl IntoIterator<Item = &'a str>,
file_name: &str,
) -> FindExecutableResult {
let mut encountered_eaccess = false;
for p in path {
let full_path = format!("{}/{}", p.trim_end_matches('/'), file_name);
match fs.get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &full_path, true) {
Ok(_) => return FindExecutableResult::Found(full_path),
Err(Errno::Access) => encountered_eaccess = true,
Err(_) => (),
}
}
if encountered_eaccess {
FindExecutableResult::AccessError
} else {
FindExecutableResult::NotFound
}
}