Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 251 additions & 33 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ path = "src/main.rs"

[dependencies]
clap = { version = "4.5", default-features = false, features = ["std", "derive"] }
nix = { version = "0.30.1", features = ["process", "signal", "resource", "fs", "poll"] }
nix = { version = "0.30.1", features = ["process", "signal", "resource", "fs", "poll", "socket"] }
serde_json = "1"
log = { version = "0.4", features = ["std"] }
chrono = "0.4"

[dev-dependencies]
tempfile = "3"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ conmon-v2: ## Fetch the conmon-v2 into "conmon-v2" directory.
echo "Adding worktree at $(CONMON_V2_DIR) -> $(CONMON_V2_REMOTE)/$(CONMON_V2_BRANCH)"; \
git worktree add --force "$(CONMON_V2_DIR)" "$(CONMON_V2_REMOTE)/$(CONMON_V2_BRANCH)"; \
fi; \
# Re-create the CONMONV_V2_DIR in case it has been removed.
# Re-create the CONMON_V2_DIR in case it has been removed.
if [ ! -d "$(CONMON_V2_DIR)" ]; then \
git worktree add --force "$(CONMON_V2_DIR)" "$(CONMON_V2_REMOTE)/$(CONMON_V2_BRANCH)"; \
fi; \
Expand Down
56 changes: 29 additions & 27 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use clap::{ArgAction, Parser};
override_usage = "conmon [OPTIONS] -c <CID> --runtime <PATH>",
disable_version_flag = true
)]
#[derive(Default)]
#[derive(Default, Debug)]
pub struct Opts {
/// Conmon API version to use
#[arg(long = "api-version", value_parser = clap::value_parser!(i32))]
Expand Down Expand Up @@ -226,32 +226,33 @@ pub struct CommonCfg {
pub runtime_opts: Vec<String>,
pub no_pivot: bool,
pub no_new_keyring: bool,
pub conmon_pidfile: Option<PathBuf>,
pub container_pidfile: PathBuf,
pub bundle: PathBuf,
pub full_attach: bool,
pub socket_dir_path: Option<PathBuf>,
pub stdin: bool,
pub leave_stdin_open: bool,
}

#[derive(Debug, Default)]
pub struct CreateCfg {
pub common: CommonCfg,
pub bundle: PathBuf,
pub container_pidfile: PathBuf,
pub systemd_cgroup: bool,
pub conmon_pidfile: Option<PathBuf>,
}

#[derive(Debug, Default)]
pub struct ExecCfg {
pub common: CommonCfg,
pub exec_process_spec: PathBuf,
pub attach: bool,
pub container_pidfile: PathBuf,
}

#[derive(Debug, Default)]
pub struct RestoreCfg {
pub common: CommonCfg,
pub restore_path: PathBuf,
pub systemd_cgroup: bool,
pub container_pidfile: PathBuf,
pub bundle: PathBuf,
}

/// Try to detect "executable" bit.
Expand Down Expand Up @@ -316,6 +317,18 @@ pub fn determine_cmd(mut opts: Opts) -> ConmonResult<Cmd> {
));
}

let cwd = std::env::current_dir()
.map_err(|e| ConmonError::new(format!("Failed to get working directory: {e}"), 1))?;

// container-pidfile defaults to "$cwd/pidfile-$cid" if none provided
let container_pidfile = opts
.container_pidfile
.take()
.unwrap_or_else(|| cwd.join(format!("pidfile-{}", cid)));

// bundle defaults to "$cwd" if none provided
let bundle = opts.bundle.take().unwrap_or_else(|| cwd.clone());

let common = CommonCfg {
api_version,
cid,
Expand All @@ -325,28 +338,21 @@ pub fn determine_cmd(mut opts: Opts) -> ConmonResult<Cmd> {
runtime_opts: opts.runtime_opts,
no_pivot: opts.no_pivot,
no_new_keyring: opts.no_new_keyring,
conmon_pidfile: opts.conmon_pidfile,
container_pidfile,
bundle,
full_attach: opts.full_attach,
socket_dir_path: opts.socket_dir_path,
stdin: opts.stdin,
leave_stdin_open: opts.leave_stdin_open,
};

let cwd = std::env::current_dir()
.map_err(|e| ConmonError::new(format!("Failed to get working directory: {e}"), 1))?;

// bundle defaults to "$cwd" if none provided
let bundle = opts.bundle.take().unwrap_or_else(|| cwd.clone());

// container-pidfile defaults to "$cwd/pidfile-$cid" if none provided
let container_pidfile = opts
.container_pidfile
.take()
.unwrap_or_else(|| cwd.join(format!("pidfile-{}", common.cid)));

// decide which subcommand this flag combination means
if let Some(restore_path) = opts.restore.take() {
Ok(Cmd::Restore(RestoreCfg {
common,
restore_path,
systemd_cgroup: opts.systemd_cgroup,
container_pidfile,
bundle,
}))
} else if opts.exec {
let exec_process_spec = opts.exec_process_spec.take().ok_or_else(|| {
Expand All @@ -359,15 +365,11 @@ pub fn determine_cmd(mut opts: Opts) -> ConmonResult<Cmd> {
common,
exec_process_spec,
attach: opts.attach,
container_pidfile,
}))
} else {
Ok(Cmd::Create(CreateCfg {
common,
bundle,
container_pidfile,
systemd_cgroup: opts.systemd_cgroup,
conmon_pidfile: opts.conmon_pidfile,
}))
}
}
Expand Down Expand Up @@ -616,9 +618,9 @@ mod tests {
match cmd {
Cmd::Create(cfg) => {
// bundle defaults to cwd
assert_eq!(cfg.bundle, cwd);
assert_eq!(cfg.common.bundle, cwd);
// container-pidfile defaults to "$cwd/pidfile-$cid"
assert_eq!(cfg.container_pidfile, cwd.join("pidfile-abc"));
assert_eq!(cfg.common.container_pidfile, cwd.join("pidfile-abc"));
}
_ => panic!("expected Run"),
}
Expand Down
126 changes: 41 additions & 85 deletions src/commands/create.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use std::process::ExitCode;

use crate::cli::CreateCfg;
use crate::error::{ConmonError, ConmonResult};
use crate::error::ConmonResult;
use crate::logging::plugin::LogPlugin;
use crate::parent_pipe::{get_pipe_fd_from_env, write_or_close_sync_fd};
use crate::runtime::args::{RuntimeArgsGenerator, generate_runtime_args};
use crate::runtime::run::{run_runtime, wait_for_runtime};
use crate::runtime::stdio::{create_pipe, handle_stdio, read_pipe};
use std::fs;
use std::process::Stdio;
use crate::runtime::args::RuntimeArgsGenerator;

pub struct Create {
cfg: CreateCfg,
Expand All @@ -17,81 +14,30 @@ impl Create {
Self { cfg }
}

// Helper function to read and return the container pid.
fn read_container_pid(&self) -> ConmonResult<i32> {
let contents = fs::read_to_string(self.cfg.container_pidfile.as_path())?;
let pid = contents.trim().parse::<i32>().map_err(|e| {
ConmonError::new(
format!(
"Invalid PID contents in {}: {} ({})",
self.cfg.container_pidfile.display(),
contents.trim(),
e
),
1,
)
})?;
Ok(pid)
}

pub fn exec(&self, log_plugin: &mut dyn LogPlugin) -> ConmonResult<()> {
// Get the sync_pipe FD. It is used by the conmon caller to obtain the container_pid
// or the runtime error message later.
let sync_pipe_fd = get_pipe_fd_from_env("_OCI_SYNCPIPE")?;

// Generate the list of arguments for runtime.
let runtime_args = generate_runtime_args(&self.cfg.common, self)?;

// Generate pipes to handle stdio.
let (mainfd_stdout, workerfd_stdout) = create_pipe()?;
let (mainfd_stderr, workerfd_stderr) = create_pipe()?;

// Run the `runtime create` and store our PID after first fork to `conmon_pidfile.
let runtime_pid = run_runtime(
&runtime_args,
Stdio::null(), // TODO
Stdio::from(workerfd_stdout),
Stdio::from(workerfd_stderr),
)?;
if let Some(pidfile) = &self.cfg.conmon_pidfile {
std::fs::write(pidfile, runtime_pid.to_string())?;
}
pub fn exec(&self, log_plugin: &mut dyn LogPlugin) -> ConmonResult<ExitCode> {
// Start the `runtime create` session.
let mut runtime_session = crate::runtime::session::RuntimeSession::new();
runtime_session.launch(&self.cfg.common, self, false)?;

// ===
// Now, after the `run_runtime`, we are in the child process of our original process
// (See `run_runtime` code and description for more information).
// Now, after the `launch()`, we are in the child process of our original process,
// because we double-fork in the RuntimeProcess::spawn.
// (See `RuntimeProcess::spawn` code and description for more information).
// ===

// Wait until the `runtime create` finishes.
let runtime_status = wait_for_runtime(runtime_pid)?;
if runtime_status != 0 {
// Pass the error to sync_pipe if there is one.
if let Some(fd) = sync_pipe_fd {
let err_bytes = read_pipe(&mainfd_stderr)?;
let err_str = String::from_utf8(err_bytes)?;
write_or_close_sync_fd(fd, -1, Some(&err_str), self.cfg.common.api_version, false)?;
}
return Err(ConmonError::new(
format!("Runtime exited with status: {runtime_status}"),
1,
));
}

// Pass the container_pid to sync_pipe if there is one.
if let Some(fd) = sync_pipe_fd {
let container_pid = self.read_container_pid()?;
write_or_close_sync_fd(fd, container_pid, None, self.cfg.common.api_version, false)?;
}
// Wait until the `runtime create` finishes and return an error in case it fails.
runtime_session.wait_for_success(self.cfg.common.api_version)?;
runtime_session.write_container_pid_file(&self.cfg.common)?;

// ===
// Now we wait for an external application like podman to really start the container.
// and handle the containers stdio or its termination.
// ===

// Handle the stdio.
handle_stdio(log_plugin, mainfd_stdout, mainfd_stderr)?;
// Run the eventloop to forward log messages to log plugin.
runtime_session.run_event_loop(log_plugin, self.cfg.common.leave_stdin_open)?;

Ok(())
Ok(ExitCode::SUCCESS)
}
}

Expand All @@ -107,9 +53,13 @@ impl RuntimeArgsGenerator for Create {
argv.extend([
"create".to_string(),
"--bundle".to_string(),
self.cfg.bundle.to_string_lossy().into_owned(),
self.cfg.common.bundle.to_string_lossy().into_owned(),
"--pid-file".to_string(),
self.cfg.container_pidfile.to_string_lossy().into_owned(),
self.cfg
.common
.container_pidfile
.to_string_lossy()
.into_owned(),
]);
Ok(())
}
Expand All @@ -127,6 +77,8 @@ mod tests {
runtime_opts: Vec<&str>,
no_pivot: bool,
no_new_keyring: bool,
pidfile: &str,
bundle: &str,
) -> CommonCfg {
CommonCfg {
runtime: PathBuf::from("./runtime"),
Expand All @@ -135,22 +87,16 @@ mod tests {
runtime_opts: runtime_opts.into_iter().map(|s| s.to_string()).collect(),
no_pivot,
no_new_keyring,
container_pidfile: PathBuf::from(pidfile),
bundle: PathBuf::from(bundle),
..Default::default()
}
}

fn mk_create_cfg(
systemd_cgroup: bool,
bundle: &str,
pidfile: &str,
common: CommonCfg,
) -> CreateCfg {
fn mk_create_cfg(systemd_cgroup: bool, common: CommonCfg) -> CreateCfg {
CreateCfg {
systemd_cgroup,
bundle: PathBuf::from(bundle),
container_pidfile: PathBuf::from(pidfile),
common,
..Default::default()
}
}

Expand All @@ -162,8 +108,10 @@ mod tests {
vec!["--optA", "X"],
false,
false,
"/tmp/pid-A",
"/tmp/bundle-A",
);
let cfg = mk_create_cfg(true, "/tmp/bundle-A", "/tmp/pid-A", common);
let cfg = mk_create_cfg(true, common);
let create = Create::new(cfg);

let argv =
Expand All @@ -188,8 +136,16 @@ mod tests {

#[test]
fn generate_args_create_without_systemd_cgroup_with_generic_flags() {
let common = mk_common("cid456", vec![], vec!["--optB"], true, true);
let cfg = mk_create_cfg(false, "/tmp/bundle-B", "/tmp/pid-B", common);
let common = mk_common(
"cid456",
vec![],
vec!["--optB"],
true,
true,
"/tmp/pid-B",
"/tmp/bundle-B",
);
let cfg = mk_create_cfg(false, common);
let create = Create::new(cfg);

let argv =
Expand Down
Loading