diff --git a/ocitar/src/main.rs b/ocitar/src/main.rs index 5ef2e07..0d7db74 100644 --- a/ocitar/src/main.rs +++ b/ocitar/src/main.rs @@ -486,7 +486,7 @@ mod tests { chdir: Some(dir.to_string()), file: "test-materials/base.tar.zst".to_string(), compression: CompressionType::Auto, - print_input_digest: false + print_input_digest: false, }; do_extract(extract_arg).unwrap(); }); @@ -500,7 +500,7 @@ mod tests { chdir: Some(dir.to_string()), file: "test-materials/base.tar".to_string(), compression: CompressionType::Auto, - print_input_digest: false + print_input_digest: false, }; do_extract(extract_arg).unwrap(); }); diff --git a/xc-bin/src/jailfile/directives/from.rs b/xc-bin/src/jailfile/directives/from.rs index a650a86..e011a0e 100644 --- a/xc-bin/src/jailfile/directives/from.rs +++ b/xc-bin/src/jailfile/directives/from.rs @@ -22,8 +22,8 @@ // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. -use crate::jailfile::JailContext; use crate::jailfile::parse::Action; +use crate::jailfile::JailContext; use anyhow::{bail, Result}; use oci_util::image_reference::ImageReference; @@ -31,7 +31,7 @@ use xc::util::gen_id; pub(crate) struct FromDirective { image_reference: ImageReference, - alias: Option + alias: Option, } impl FromDirective { @@ -44,9 +44,15 @@ impl FromDirective { if action.args.len() > 1 { let Some("as") = action.args.get(1).map(|s| s.as_str()) else { bail!("unexpected ariable") }; let alias = action.args.get(2).expect("expected alias"); - Ok(FromDirective { image_reference, alias: Some(alias.to_string()) }) + Ok(FromDirective { + image_reference, + alias: Some(alias.to_string()), + }) } else { - Ok(FromDirective { image_reference, alias: None }) + Ok(FromDirective { + image_reference, + alias: None, + }) } } @@ -60,14 +66,11 @@ impl FromDirective { let name = format!("build-{}", gen_id()); /* create container */ if let Some(alias) = &self.alias { - context.containers.insert(alias.to_string(), name.to_string()); + context + .containers + .insert(alias.to_string(), name.to_string()); } context.container_id = Some(name); Ok(()) } } - - - - - diff --git a/xc-bin/src/jailfile/directives/run.rs b/xc-bin/src/jailfile/directives/run.rs index e69de29..8b13789 100644 --- a/xc-bin/src/jailfile/directives/run.rs +++ b/xc-bin/src/jailfile/directives/run.rs @@ -0,0 +1 @@ + diff --git a/xc-bin/src/jailfile/mod.rs b/xc-bin/src/jailfile/mod.rs index 8e25161..560a8d1 100644 --- a/xc-bin/src/jailfile/mod.rs +++ b/xc-bin/src/jailfile/mod.rs @@ -33,5 +33,5 @@ pub(crate) struct JailContext { /// Mapping to different containers for multi-stage build pub(crate) containers: HashMap, - conn: UnixStream + conn: UnixStream, } diff --git a/xc-bin/src/jailfile/parse.rs b/xc-bin/src/jailfile/parse.rs index dd2f1ca..7dd332e 100644 --- a/xc-bin/src/jailfile/parse.rs +++ b/xc-bin/src/jailfile/parse.rs @@ -103,38 +103,57 @@ mod tests { "#; let parsed = super::parse_jailfile(input).expect("cannot parse input"); - assert_eq!(parsed[0], Action { - directive_name: "FROM".to_string(), - directive_args: HashMap::new(), - args: vec!["node:18-alpine".to_string()], - heredoc: None - }); - assert_eq!(parsed[1], Action { - directive_name: "WORKDIR".to_string(), - directive_args: HashMap::new(), - args: vec!["/app".to_string()], - heredoc: None - }); - assert_eq!(parsed[2], Action { - directive_name: "COPY".to_string(), - directive_args: HashMap::new(), - args: vec![".".to_string(), ".".to_string()], - heredoc: None - }); - assert_eq!(parsed[3], Action { - directive_name: "RUN".to_string(), - directive_args: HashMap::new(), - args: vec!["yarn".to_string(), "install".to_string(), "--production".to_string()], - heredoc: None - }); - assert_eq!(parsed[4], Action { - directive_name: "RUN".to_string(), - directive_args: HashMap::new(), - args: Vec::new(), - heredoc: Some("\n This is some\n funny string\n ".to_string()) - }); + assert_eq!( + parsed[0], + Action { + directive_name: "FROM".to_string(), + directive_args: HashMap::new(), + args: vec!["node:18-alpine".to_string()], + heredoc: None + } + ); + assert_eq!( + parsed[1], + Action { + directive_name: "WORKDIR".to_string(), + directive_args: HashMap::new(), + args: vec!["/app".to_string()], + heredoc: None + } + ); + assert_eq!( + parsed[2], + Action { + directive_name: "COPY".to_string(), + directive_args: HashMap::new(), + args: vec![".".to_string(), ".".to_string()], + heredoc: None + } + ); + assert_eq!( + parsed[3], + Action { + directive_name: "RUN".to_string(), + directive_args: HashMap::new(), + args: vec![ + "yarn".to_string(), + "install".to_string(), + "--production".to_string() + ], + heredoc: None + } + ); + assert_eq!( + parsed[4], + Action { + directive_name: "RUN".to_string(), + directive_args: HashMap::new(), + args: Vec::new(), + heredoc: Some("\n This is some\n funny string\n ".to_string()) + } + ); -/* + /* eprintln!("{parsed:?}"); assert!(false); */ diff --git a/xc-bin/src/main.rs b/xc-bin/src/main.rs index a44bd7d..cba7585 100644 --- a/xc-bin/src/main.rs +++ b/xc-bin/src/main.rs @@ -143,6 +143,8 @@ enum Action { no_clean: bool, #[clap(long, default_value_t, action)] persist: bool, + #[clap(long="create-only", action)] + create_only: bool, image_reference: ImageReference, entry_point: Option, entry_point_args: Vec, @@ -192,8 +194,8 @@ enum Action { Exec { name: String, arg0: String, - args: Vec - } + args: Vec, + }, } fn main() -> Result<(), ActionError> { @@ -289,7 +291,7 @@ fn main() -> Result<(), ActionError> { } let password = password.unwrap_or_else(|| { - print!("Enter password: \n"); + println!("Enter password: "); rpassword::read_password().unwrap() }); @@ -444,6 +446,7 @@ fn main() -> Result<(), ActionError> { } Action::Run { image_reference, + create_only, detach, entry_point, entry_point_args, @@ -528,7 +531,7 @@ fn main() -> Result<(), ActionError> { Maybe::Some(Fd(fd)) }; - let reqt = InstantiateRequest { + let mut reqt = InstantiateRequest { alt_root: None, name, hostname, @@ -550,6 +553,12 @@ fn main() -> Result<(), ActionError> { main_started_notify: main_started_notify.clone(), }; + if create_only { + reqt.main_norun = true; + reqt.init_norun = true; + reqt.deinit_norun = true; + } + let res = do_instantiate(&mut conn, reqt)?; (res, main_started_notify) }; @@ -615,7 +624,7 @@ fn main() -> Result<(), ActionError> { } else { eprintln!("no such container"); } - }, + } Action::Exec { name, arg0, args } => { let n = EventFdNotify::new(); let request = ExecCommandRequest { @@ -627,7 +636,7 @@ fn main() -> Result<(), ActionError> { stdout: Maybe::Some(ipc::packet::codec::Fd(1)), stderr: Maybe::Some(ipc::packet::codec::Fd(2)), uid: 0, - notify: Maybe::Some(ipc::packet::codec::Fd(n.as_raw_fd())) + notify: Maybe::Some(ipc::packet::codec::Fd(n.as_raw_fd())), }; if let Ok(response) = do_exec(&mut conn, request)? { n.notified_sync(); diff --git a/xc/src/container/process.rs b/xc/src/container/process.rs index 61b1556..34cf1e3 100644 --- a/xc/src/container/process.rs +++ b/xc/src/container/process.rs @@ -104,9 +104,8 @@ pub(super) fn spawn_process_forward( cmd: &mut std::process::Command, stdin: Option, stdout: Option, - stderr: Option -) -> Result -{ + stderr: Option, +) -> Result { unsafe { cmd.pre_exec(move || { if let Some(fd) = stdin { diff --git a/xc/src/container/runner.rs b/xc/src/container/runner.rs index 1eccfb2..91740c9 100644 --- a/xc/src/container/runner.rs +++ b/xc/src/container/runner.rs @@ -32,7 +32,9 @@ use crate::util::exists_exec; use anyhow::Context; use freebsd::event::{EventFdNotify, KEventExt}; +use ipc::packet::codec::json::JsonPacket; use ipc::packet::Packet; +use ipc::proto::Request; use jail::process::Jailed; use nix::libc::intptr_t; use nix::sys::event::{kevent_ts, EventFilter, EventFlag, FilterFlag, KEvent}; @@ -88,6 +90,26 @@ impl ReadingPacket { } } +enum Readiness { + Pending, + Ready(T), +} + +impl Readiness { + fn map_can_fail(self, transform: F) -> Result, E> + where + F: FnOnce(T) -> Result, + { + match self { + Readiness::Pending => Ok(Readiness::Pending), + Readiness::Ready(value) => { + let x = transform(value)?; + Ok(Readiness::Ready(x)) + } + } + } +} + // XXX: naively pretend writes always successful #[derive(Debug)] pub struct ControlStream { @@ -103,7 +125,26 @@ impl ControlStream { } } - fn pour_in_bytes(&mut self, known_avail: usize) -> Result, anyhow::Error> { + fn try_get_request( + &mut self, + known_avail: usize, + ) -> Result, anyhow::Error> { + self.pour_in_bytes(known_avail) + .and_then(|readiness| match readiness { + Readiness::Pending => Ok(Readiness::Pending), + Readiness::Ready(packet) => { + let request: ipc::packet::TypedPacket = + packet.map_failable(|vec| serde_json::from_slice(vec))?; + + let method = request.data.method.to_string(); + let packet = request.map(|req| req.value.clone()); + + Ok(Readiness::Ready((method, packet))) + } + }) + } + + fn pour_in_bytes(&mut self, known_avail: usize) -> Result, anyhow::Error> { if let Some(reading_packet) = &mut self.processing { if reading_packet.ready() { panic!("the client is sending more bytes than expected"); @@ -115,13 +156,13 @@ impl ControlStream { } let Some(processing) = self.processing.take() else { panic!() }; if processing.ready() { - Ok(Some(Packet { + Ok(Readiness::Ready(Packet { data: processing.buffer, fds: processing.fds, })) } else { self.processing = Some(processing); - Ok(None) + Ok(Readiness::Pending) } } } @@ -351,10 +392,11 @@ impl ProcessRunner { let err_path = format!("/var/log/xc.{}.{}.err.log", self.container.id, id); spawn_process_files(&mut cmd, &Some(out_path), &Some(err_path))? } - StdioMode::Forward { stdin, stdout, stderr } => { - spawn_process_forward( - &mut cmd, *stdin, *stdout, *stderr)? - } + StdioMode::Forward { + stdin, + stdout, + stderr, + } => spawn_process_forward(&mut cmd, *stdin, *stdout, *stderr)?, }; tx.send_if_modified(|status| { @@ -421,6 +463,16 @@ impl ProcessRunner { } } + fn handle_control_stream_cmd(&mut self, method: String, request: JsonPacket) { + if method == "exec" { + let jexec: Jexec = serde_json::from_value(request.data).unwrap(); + let notify = Arc::new(EventFdNotify::from_fd(jexec.notify.unwrap())); + let _result = self.spawn_process(&crate::util::gen_id(), &jexec, Some(notify)); + } else if method == "run_main" { + self.should_run_main = true; + } + } + fn handle_pid_event( &mut self, event: KEvent, @@ -474,7 +526,9 @@ impl ProcessRunner { if descentdant_gone { stat.set_tree_exited(); if stat.id() == "main" { - if self.container.deinit_norun || deinits.is_empty() { + if (self.container.deinit_norun || deinits.is_empty()) + && !self.container.persist + { return true; } else { debug!("activating deinit queue"); @@ -566,16 +620,13 @@ impl ProcessRunner { } else if let Some(control_stream) = self.control_streams.get_mut(&(event.ident() as i32)) { - match control_stream.pour_in_bytes(event.data() as usize) { + match control_stream.try_get_request(event.data() as usize) { Err(_) => { self.control_streams.remove(&(event.ident() as i32)); } - Ok(None) => { - } - Ok(Some(packet)) => { - let jexec: Jexec = serde_json::from_slice(&packet.data).unwrap(); - let notify = Arc::new(EventFdNotify::from_fd(jexec.notify.unwrap())); - let _result = self.spawn_process(&crate::util::gen_id(), &jexec, Some(notify)); + Ok(Readiness::Pending) => {} + Ok(Readiness::Ready((method, request))) => { + self.handle_control_stream_cmd(method, request); } } } @@ -630,7 +681,7 @@ impl ProcessRunner { } pub fn run( - container: RunningContainer, + container: RunningContainer, control_stream: UnixStream, ) -> (i32, Receiver) { let kq = nix::sys::event::kqueue().unwrap(); diff --git a/xc/src/models/exec.rs b/xc/src/models/exec.rs index cb2b82e..691ccfe 100644 --- a/xc/src/models/exec.rs +++ b/xc/src/models/exec.rs @@ -42,8 +42,8 @@ pub enum StdioMode { Forward { stdin: Option, stdout: Option, - stderr: Option - } + stderr: Option, + }, } /// Executable parameters to be executed in container @@ -54,7 +54,7 @@ pub struct Jexec { pub envs: std::collections::HashMap, pub uid: u32, pub output_mode: StdioMode, - pub notify: Option + pub notify: Option, } pub struct ResolvedExec { @@ -75,7 +75,7 @@ impl ResolvedExec { stdout: None, stderr: None, }, - notify: None + notify: None, } } } diff --git a/xcd/src/context/instantiate.rs b/xcd/src/context/instantiate.rs index 4902d25..fccb52f 100644 --- a/xcd/src/context/instantiate.rs +++ b/xcd/src/context/instantiate.rs @@ -146,7 +146,7 @@ impl InstantiateBlueprint { envs, uid: 0, output_mode: StdioMode::Terminal, - notify: None + notify: None, } }; for assign in request.ips.iter() { diff --git a/xcd/src/context/mod.rs b/xcd/src/context/mod.rs index 952f724..466690d 100644 --- a/xcd/src/context/mod.rs +++ b/xcd/src/context/mod.rs @@ -469,8 +469,9 @@ impl ServerContext { rename_reference: Option, ) -> anyhow::Result<()> { // XXX: handle pull image error - let result = crate::image::pull::pull_image(self.image_manager.clone(), reference, rename_reference) - .await; + let result = + crate::image::pull::pull_image(self.image_manager.clone(), reference, rename_reference) + .await; if result.is_err() { error!("result: {result:#?}"); } diff --git a/xcd/src/ipc.rs b/xcd/src/ipc.rs index 76dcd7c..2a64070 100644 --- a/xcd/src/ipc.rs +++ b/xcd/src/ipc.rs @@ -34,7 +34,6 @@ use oci_util::digest::OciDigest; use oci_util::distribution::client::{BasicAuth, Registry}; use oci_util::image_reference::ImageReference; use serde::{Deserialize, Serialize}; -use xc::models::exec::{Jexec, StdioMode}; use std::collections::HashMap; use std::io::Seek; use std::net::IpAddr; @@ -45,6 +44,7 @@ use tokio::sync::RwLock; use tracing::*; use varutil::string_interpolation::InterpolatedString; use xc::container::request::{MountReq, NetworkAllocRequest}; +use xc::models::exec::{Jexec, StdioMode}; use xc::models::jail_image::JailConfig; use xc::models::network::{DnsSetting, IpAssign, PortRedirection}; use xc::res::network::Network; @@ -820,21 +820,20 @@ pub struct ExecCommandRequest { pub stdout: Maybe, pub stderr: Maybe, pub uid: u32, - pub notify: Maybe + pub notify: Maybe, } #[derive(Serialize, Deserialize, Debug)] pub struct ExecCommandResponse { - pub cid: String + pub cid: String, } #[ipc_method(method = "exec")] async fn exec( context: Arc>, local_context: &mut ConnectionContext, - request: ExecCommandRequest -) -> GenericResult -{ + request: ExecCommandRequest, +) -> GenericResult { info!("exec!!!"); let cid = gen_id(); let jexec = Jexec { @@ -845,9 +844,9 @@ async fn exec( output_mode: StdioMode::Forward { stdin: request.stdin.to_option().map(|fd| fd.0), stdout: request.stdout.to_option().map(|fd| fd.0), - stderr: request.stderr.to_option().map(|fd| fd.0) + stderr: request.stderr.to_option().map(|fd| fd.0), }, - notify: request.notify.to_option().map(|fd| fd.0) + notify: request.notify.to_option().map(|fd| fd.0), }; if let Some(arc_site) = context.write().await.get_site(&request.name) { let mut site = arc_site.write().await; @@ -858,6 +857,28 @@ async fn exec( } } +#[derive(Serialize, Deserialize, Debug)] +pub struct RunMainRequest { + pub name: String +} + +/// XXX: Temporary +#[ipc_method(method = "run_main")] +async fn run_main( + context: Arc>, + local_context: &mut ConnectionContext, + request: RunMainRequest, +) -> GenericResult<()> +{ + if let Some(arc_site) = context.write().await.get_site(&request.name) { + let mut site = arc_site.write().await; + site.run_main(); + Ok(()) + }else { + enoent("container not found") + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct PushImageRequest { pub image_reference: ImageReference, diff --git a/xcd/src/site.rs b/xcd/src/site.rs index 0c9ca29..9b0ca1a 100644 --- a/xcd/src/site.rs +++ b/xcd/src/site.rs @@ -26,8 +26,8 @@ use anyhow::{anyhow, bail, Context}; use freebsd::event::{EventFdNotify, Notify}; use freebsd::fs::zfs::ZfsHandle; use ipc::packet::Packet; +use ipc::proto::Request; use ipc::transport::PacketTransport; -use xc::models::exec::Jexec; use std::ffi::OsString; use std::os::fd::RawFd; use std::os::unix::net::UnixStream; @@ -37,6 +37,7 @@ use tracing::info; use xc::config::XcConfig; use xc::container::effect::UndoStack; use xc::container::{Container, ContainerManifest}; +use xc::models::exec::Jexec; use xc::models::jail_image::JailImage; pub enum SiteState { @@ -113,8 +114,29 @@ impl Site { } pub fn exec(&mut self, jexec: Jexec) { - let encoded = serde_json::to_vec(&jexec).unwrap(); - let packet = Packet { data: encoded, fds: Vec::new() }; + let value = serde_json::to_value(jexec).unwrap(); + let request = Request { method: "exec".to_string(), value }; + let encoded = serde_json::to_vec(&request).unwrap(); + let packet = Packet { + data: encoded, + fds: Vec::new(), + }; + if let Some(stream) = self.control_stream.as_mut() { + let _result = stream.send_packet(&packet); + } + } + + /// XXX: CHANGE ME + pub fn run_main(&mut self) { + let request = Request { + method: "run_main".to_string(), + value: serde_json::json!({}) + }; + let encoded = serde_json::to_vec(&request).unwrap(); + let packet = Packet { + data: encoded, + fds: Vec::new(), + }; if let Some(stream) = self.control_stream.as_mut() { let _result = stream.send_packet(&packet); }