diff --git a/xc-bin/src/attach.rs b/xc-bin/src/attach.rs index daef4b2..ec0d98d 100644 --- a/xc-bin/src/attach.rs +++ b/xc-bin/src/attach.rs @@ -72,15 +72,15 @@ fn enable_raw(orig: &Termios) -> Result<(), freebsd::nix::Error> { tcsetattr(stdin, SetArg::TCSADRAIN, &tio) } -fn with_raw_terminal(f: F) -> Result<(), freebsd::nix::Error> +fn with_raw_terminal(f: F) -> Result where - F: Fn() -> Result<(), freebsd::nix::Error>, + F: Fn() -> Result, { 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) -> Result<(), std::io::Error> { +pub fn run(path: impl AsRef) -> Result { 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) -> 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) -> 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) -> Result<(), std::io::Error> { // flush everything stream to local here - Ok(()) + Ok(break_by_user) })?) } diff --git a/xc-bin/src/main.rs b/xc-bin/src/main.rs index 9c410e7..1a9f8e5 100644 --- a/xc-bin/src/main.rs +++ b/xc-bin/src/main.rs @@ -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:?}") diff --git a/xc/src/container/runner/mod.rs b/xc/src/container/runner/mod.rs index fa56d9d..8733cca 100644 --- a/xc/src/container/runner/mod.rs +++ b/xc/src/container/runner/mod.rs @@ -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"); } } } diff --git a/xc/src/container/runner/process_stat.rs b/xc/src/container/runner/process_stat.rs index 5d5186a..fd05b68 100644 --- a/xc/src/container/runner/process_stat.rs +++ b/xc/src/container/runner/process_stat.rs @@ -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>, } +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)); } } diff --git a/xcd/src/context.rs b/xcd/src/context.rs index 3d74458..48757ea 100644 --- a/xcd/src/context.rs +++ b/xcd/src/context.rs @@ -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) => { diff --git a/xcd/src/instantiate.rs b/xcd/src/instantiate.rs index de2d080..0895570 100644 --- a/xcd/src/instantiate.rs +++ b/xcd/src/instantiate.rs @@ -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, pub created_interfaces: Vec, + pub port_redirections: Vec } 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, }) } } diff --git a/xcd/src/ipc.rs b/xcd/src/ipc.rs index 9435846..2219e55 100644 --- a/xcd/src/ipc.rs +++ b/xcd/src/ipc.rs @@ -240,6 +240,13 @@ pub struct InstantiateRequest { pub main_ip_selector: Option, pub tap_interfaces: Option>, pub tun_interfaces: Option>, + + pub main_exited_fd: Maybe, + pub stdin: Maybe, + pub stdout: Maybe, + pub stderr: Maybe, + + pub port_redirections: Vec, } 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(), } } } diff --git a/xcd/src/site.rs b/xcd/src/site.rs index d6c7667..2c3c174 100644 --- a/xcd/src/site.rs +++ b/xcd/src/site.rs @@ -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,