#![deny(unsafe_code)]
mod event;
mod interface;
mod io_util;
mod no_pty;
mod use_pty;
use std::{
borrow::Cow,
ffi::{CString, OsStr},
io,
os::unix::ffi::OsStrExt,
os::unix::process::CommandExt,
process::Command,
time::Duration,
};
use signal_hook::consts::*;
use crate::{
common::Environment,
system::{interface::ProcessId, killpg},
};
use crate::{
exec::no_pty::exec_no_pty,
log::dev_info,
system::{set_target_user, signal::SignalNumber, term::UserTerm},
};
use crate::{log::user_error, system::kill};
pub use interface::RunOptions;
use self::use_pty::{exec_pty, SIGCONT_BG, SIGCONT_FG};
pub fn run_command(
options: impl RunOptions,
env: Environment,
) -> io::Result<(ExitReason, impl FnOnce())> {
let mut command = Command::new(options.command());
command.args(options.arguments()).env_clear().envs(env);
let path = options.chdir().cloned().or_else(|| {
options.is_login().then(|| {
let mut process_name = options
.command()
.file_name()
.map(|osstr| osstr.as_bytes().to_vec())
.unwrap_or_else(Vec::new);
process_name.insert(0, b'-');
command.arg0(OsStr::from_bytes(&process_name));
options.user().home.clone()
})
});
if let Some(path) = path {
let is_chdir = options.chdir().is_some();
#[allow(unsafe_code)]
unsafe {
command.pre_exec(move || {
let bytes = path.as_os_str().as_bytes();
let c_path =
CString::new(bytes).expect("nul byte found in provided directory path");
if let Err(err) = crate::system::chdir(&c_path) {
user_error!("unable to change directory to {}: {}", path.display(), err);
if is_chdir {
return Err(err);
}
}
Ok(())
});
}
}
set_target_user(
&mut command,
options.user().clone(),
options.group().clone(),
);
match UserTerm::open() {
Ok(user_tty) => exec_pty(options.pid(), command, user_tty),
Err(err) => {
dev_info!("Could not open user's terminal, not allocating a pty: {err}");
exec_no_pty(options.pid(), command)
}
}
}
#[derive(Debug)]
pub enum ExitReason {
Code(i32),
Signal(i32),
}
fn terminate_process(pid: ProcessId, use_killpg: bool) {
let kill_fn = if use_killpg { killpg } else { kill };
kill_fn(pid, SIGHUP).ok();
kill_fn(pid, SIGTERM).ok();
std::thread::sleep(Duration::from_secs(2));
kill_fn(pid, SIGKILL).ok();
}
fn signal_fmt(signal: SignalNumber) -> Cow<'static, str> {
signal_hook::low_level::signal_name(signal)
.or_else(|| (signal == SIGCONT_FG).then_some("SIGCONT_FG"))
.or_else(|| (signal == SIGCONT_BG).then_some("SIGCONT_BG"))
.map(|name| name.into())
.unwrap_or_else(|| format!("unknown signal #{}", signal).into())
}
const fn cond_fmt<'a>(cond: bool, true_s: &'a str, false_s: &'a str) -> &'a str {
if cond {
true_s
} else {
false_s
}
}
const fn opt_fmt(cond: bool, s: &str) -> &str {
cond_fmt(cond, s, "")
}