properly propagate process exit code

This commit is contained in:
elliptic
2024-02-22 02:15:43 -05:00
parent 224b564595
commit 8080395fd2
8 changed files with 91 additions and 27 deletions

View File

@@ -72,15 +72,15 @@ fn enable_raw(orig: &Termios) -> Result<(), freebsd::nix::Error> {
tcsetattr(stdin, SetArg::TCSADRAIN, &tio)
}
fn with_raw_terminal<F>(f: F) -> Result<(), freebsd::nix::Error>
fn with_raw_terminal<F, R: Copy>(f: F) -> Result<R, freebsd::nix::Error>
where
F: Fn() -> Result<(), freebsd::nix::Error>,
F: Fn() -> Result<R, freebsd::nix::Error>,
{
let tio = freebsd::nix::sys::termios::tcgetattr(std::io::stdin())?;
enable_raw(&tio)?;
f()?;
let res = f()?;
tcsetattr(std::io::stdin(), SetArg::TCSAFLUSH, &tio)?;
Ok(())
Ok(res)
}
struct ForwardState {
@@ -112,7 +112,7 @@ impl ForwardState {
} else {
self.escaped = false;
}
} else if byte == &0x10 {
} else if byte == &0x10 /* ctrl-p */ {
self.escaped = true;
} else {
self.local_to_stream.push(*byte);
@@ -122,7 +122,7 @@ impl ForwardState {
}
}
pub fn run(path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
pub fn run(path: impl AsRef<std::path::Path>) -> Result<bool, std::io::Error> {
let path = path.as_ref();
// let path = "/var/run/xc.abcde";
let stream = UnixStream::connect(path)?;
@@ -137,6 +137,8 @@ pub fn run(path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
let mut events = [KEvent::zero(); 4];
let mut state = ForwardState::new();
let mut break_by_user = false;
'm: loop {
let n_ev = kq.wait_events(&add_events, &mut events)?;
// let n_ev = kevent_ts(kq, &add_events, &mut events, None)?;
@@ -169,6 +171,7 @@ pub fn run(path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
} else if event.ident() == STDIN_FILENO as usize {
let n_read = read(STDIN_FILENO, &mut state.buffer[..event.data() as usize])?;
if state.process_local_to_stream(n_read).is_some() {
break_by_user = true;
break 'm;
}
} else if event.ident() == STDOUT_FILENO as usize {
@@ -196,6 +199,6 @@ pub fn run(path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
// flush everything stream to local here
Ok(())
Ok(break_by_user)
})?)
}

View File

@@ -44,6 +44,7 @@ use crate::volume::{use_volume_action, VolumeAction};
use clap::Parser;
use freebsd::event::{eventfd, EventFdNotify};
use freebsd::libc::EXIT_FAILURE;
use freebsd::procdesc::{pd_fork, pdwait, PdForkResult};
use ipc::packet::codec::{Fd, Maybe};
use oci_util::digest::OciDigest;
@@ -57,6 +58,7 @@ use term_table::homogeneous::{TableLayout, TableSource, Title};
use term_table::{ColumnLayout, Pos};
use tracing::{debug, error, info};
use xc::container::request::NetworkAllocRequest;
use xc::container::runner::process_stat::decode_exit_code;
use xc::models::jail_image::JailConfig;
use xc::models::network::DnsSetting;
use xc::tasks::{ImportImageState, ImportImageStatus};
@@ -593,7 +595,7 @@ fn main() -> Result<(), ActionError> {
let dns = dns.make();
let (res, notify) = {
let (res, main_started_notify, main_exited_notify) = {
let main_started_notify = if args.detach {
Maybe::None
} else {
@@ -601,6 +603,12 @@ fn main() -> Result<(), ActionError> {
Maybe::Some(Fd(fd))
};
let main_exited_notify = if args.detach {
None
} else {
Some(unsafe { eventfd(0, freebsd::nix::libc::EFD_NONBLOCK) })
};
let reqt = InstantiateRequest {
dns,
create_only: false,
@@ -612,11 +620,14 @@ fn main() -> Result<(), ActionError> {
entry_point: args.entry_point,
entry_point_args: args.entry_point_args,
}),
main_exited_fd: Maybe::from_option(main_exited_notify.map(Fd)),
port_redirections: publish.into_iter().map(|p| p.to_host_spec()).collect(),
..create.create_request()?
};
let res = do_instantiate(&mut conn, reqt)?;
(res, main_started_notify)
let exit_notify = main_exited_notify.map(EventFdNotify::from_fd);
(res, main_started_notify, exit_notify)
};
if let Ok(res) = res {
@@ -643,17 +654,8 @@ fn main() -> Result<(), ActionError> {
}
}
for publish in publish.iter() {
let redirection = publish.to_host_spec();
let req = DoRdr {
name: res.id.clone(),
redirection,
};
let _res = do_rdr_container(&mut conn, req)?.unwrap();
}
if !args.detach {
if let Maybe::Some(notify) = notify {
if let Maybe::Some(notify) = main_started_notify {
EventFdNotify::from_fd(notify.as_raw_fd()).notified_sync();
}
let id = res.id;
@@ -672,7 +674,16 @@ fn main() -> Result<(), ActionError> {
.and_then(|proc| proc.spawn_info.as_ref())
.expect("process not started yet or not found");
if let Some(socket) = &spawn_info.terminal_socket {
_ = attach::run(socket);
if let Ok(exit_by_user) = attach::run(socket) {
if !exit_by_user {
if let Some(notify) = main_exited_notify {
if let Ok(exit_value) = notify.notified_sync_take_value() {
let exit_status = decode_exit_code(exit_value);
std::process::exit(exit_status.code().unwrap_or(EXIT_FAILURE))
}
}
}
}
} else {
info!("main process is not running with tty");
}
@@ -807,8 +818,9 @@ fn main() -> Result<(), ActionError> {
if let Some(socket) = response.terminal_socket {
_ = attach::run(socket);
}
let exit = n.notified_sync_take_value();
std::process::exit((exit.unwrap_or(2) - 1) as i32)
let exit = n.notified_sync_take_value()?;
let exit_status = decode_exit_code(exit);
std::process::exit(exit_status.code().unwrap_or(EXIT_FAILURE))
}
Err(err) => {
eprintln!("{err:?}")

View File

@@ -23,7 +23,7 @@
// SUCH DAMAGE.
mod control_stream;
mod process_stat;
pub mod process_stat;
use self::control_stream::{ControlStream, Readiness};
use self::process_stat::ProcessRunnerStat;
@@ -194,6 +194,10 @@ impl ProcessRunner {
info!("spawn: {exec:#?}");
container_runner::spawn_process!(|| (self.container.jid, id, exec));
let exit_notify = exit_notify.or(exec.notify.map(|e| Arc::new(EventFdNotify::from_fd(e))));
debug!(exit_notify=format!("{exit_notify:?}"), "==");
let mut envs = self.container.envs.clone();
let jail = freebsd::jail::RunningJail::from_jid_unchecked(self.container.jid);
@@ -563,6 +567,8 @@ impl ProcessRunner {
return true;
}
}
} else {
debug!(descentdant=format!("{descentdant:?}"), "remaining descentdants");
}
}
}

View File

@@ -26,7 +26,9 @@ use crate::container::ProcessStat;
use freebsd::event::EventFdNotify;
use std::sync::Arc;
use std::os::unix::process::ExitStatusExt;
use tokio::sync::watch::Sender;
use tracing::debug;
#[derive(Debug)]
pub struct ProcessRunnerStat {
@@ -37,6 +39,14 @@ pub struct ProcessRunnerStat {
pub(super) tree_exit_notify: Option<Arc<EventFdNotify>>,
}
pub fn encode_exit_code(ec: i32) -> u64 {
0x10000000_00000000 | (ec as u64)
}
pub fn decode_exit_code(v: u64) -> std::process::ExitStatus {
std::process::ExitStatus::from_raw((v & 0xffff_ffff) as i32)
}
impl ProcessRunnerStat {
pub(super) fn pid(&self) -> u32 {
self.pid
@@ -46,14 +56,16 @@ impl ProcessRunnerStat {
}
pub(super) fn set_exited(&mut self, exit_code: i32) {
debug!(pid=self.pid, exit_code=exit_code, "set_exited");
self.process_stat.send_if_modified(|status| {
status.set_exited(exit_code);
true
});
if let Some(notify) = &self.exit_notify {
debug!(pid=self.pid, exit_code=exit_code, "notifing listeners");
notify
.clone()
.notify_waiters_with_value(exit_code as u64 + 1);
.notify_waiters_with_value(encode_exit_code(exit_code));
}
}

View File

@@ -570,6 +570,17 @@ impl ServerContext {
warn!("pf is disabled");
}
if let Some(address) = MainAddressSelector::select(&blueprint.main_ip_selector, blueprint.ip_alloc.iter()) {
if !blueprint.port_redirections.is_empty() {
for rdr in blueprint.port_redirections.iter() {
let mut rdr = rdr.clone();
rdr.with_host_info(&this.config.ext_ifs, ipcidr::IpCidr::from_singleton(address.address));
this.port_forward_table.append_rule(id, rdr);
}
this.reload_pf_rdr_anchor()?;
}
}
match site.run_container(blueprint) {
Ok(_) => (),
Err(error) => {

View File

@@ -42,7 +42,7 @@ use xc::errx;
use xc::format::devfs_rules::DevfsRule;
use xc::models::exec::{Jexec, StdioMode};
use xc::models::jail_image::JailImage;
use xc::models::network::{DnsSetting, IpAssign, MainAddressSelector};
use xc::models::network::{DnsSetting, IpAssign, MainAddressSelector, PortRedirection};
use xc::models::EnforceStatfs;
pub struct CheckedInstantiateRequest {
@@ -249,6 +249,7 @@ pub struct InstantiateBlueprint {
pub children_max: u32,
pub main_ip_selector: Option<MainAddressSelector>,
pub created_interfaces: Vec<String>,
pub port_redirections: Vec<PortRedirection>
}
impl InstantiateBlueprint {
@@ -314,6 +315,11 @@ impl InstantiateBlueprint {
ipc::packet::codec::Maybe::None => None,
ipc::packet::codec::Maybe::Some(x) => Some(EventFdNotify::from_fd(x.as_raw_fd())),
};
let main_exited_notify = match request.request.main_exited_fd {
ipc::packet::codec::Maybe::None => None,
ipc::packet::codec::Maybe::Some(x) => Some(x.as_raw_fd()),
};
let mut ip_alloc = request.request.ips.clone();
@@ -506,6 +512,8 @@ impl InstantiateBlueprint {
let mut jexec = entry_point.resolve_args(&envs, &spec.entry_point_args)?;
jexec.output_mode = StdioMode::Terminal;
jexec.notify = main_exited_notify.map(|a| a.as_raw_fd());
tracing::warn!("jexec: {jexec:#?}");
Some(jexec)
}
None => None,
@@ -571,7 +579,8 @@ impl InstantiateBlueprint {
jailed_datasets: request.request.jail_datasets,
children_max: request.request.children_max,
main_ip_selector: request.request.main_ip_selector,
created_interfaces: tuntap_ifaces
created_interfaces: tuntap_ifaces,
port_redirections: request.request.port_redirections,
})
}
}

View File

@@ -240,6 +240,13 @@ pub struct InstantiateRequest {
pub main_ip_selector: Option<MainAddressSelector>,
pub tap_interfaces: Option<Vec<String>>,
pub tun_interfaces: Option<Vec<String>>,
pub main_exited_fd: Maybe<Fd>,
pub stdin: Maybe<Fd>,
pub stdout: Maybe<Fd>,
pub stderr: Maybe<Fd>,
pub port_redirections: Vec<PortRedirection>,
}
impl InstantiateRequest {
@@ -302,6 +309,11 @@ impl Default for InstantiateRequest {
main_ip_selector: None,
tap_interfaces: None,
tun_interfaces: None,
main_exited_fd: Maybe::None,
stdin: Maybe::None,
stdout: Maybe::None,
stderr: Maybe::None,
port_redirections: Vec::new(),
}
}
}

View File

@@ -112,7 +112,6 @@ impl Site {
notify: Arc::new(Notify::new()),
main_notify: None,
container_notify: None,
// ctl_channel: None,
state: SiteState::Empty,
main_started_interests: Vec::new(),
control_stream: None,