mirror of
https://github.com/michael-yuji/xc.git
synced 2026-03-12 06:04:12 +01:00
properly propagate process exit code
This commit is contained in:
@@ -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)
|
||||
})?)
|
||||
}
|
||||
|
||||
@@ -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:?}")
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user