Skip to content

Commit 2a840e7

Browse files
authored
Merge pull request #4 from alexlarsson/cleanup
Cleanup
2 parents a52a4cf + aaa470d commit 2a840e7

4 files changed

Lines changed: 42 additions & 82 deletions

File tree

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# composefs-run
22

3+
**NOTE**: This is currently a proof of concept, and not intended for
4+
production use
5+
36
A minimal container runner that runs OCI containers directly from
47
[composefs-rs](https://github.com/containers/composefs-rs)
58
repositories using [crun](https://github.com/containers/crun), without
@@ -14,12 +17,11 @@ tracking.
1417
- **Rootless mode**: FUSE-based composefs + unprivileged overlayfs
1518
(`userxattr`), user namespace with subuid/subgid mapping,
1619
[pasta](https://passt.top/) networking
17-
- SELinux labeling with MCS category separation
18-
- Seccomp profiles (default from containers-common, or from image labels)
19-
- Minimal host state: transient overlay in `/var/tmp`, tmpfs-backed bundle,
20-
no global database
21-
- Default to transient overlays, persistent overlay via `--overlay-dir`
22-
- OCI poststop hooks for automatic cleanup
20+
- Minimal host state, containers are just child processes. There is no
21+
equivalent of podman ps/rm etc.
22+
- No detached mode, always assumes `-i`.
23+
- By default, writable overlays are transient, stored in /var/tmp.
24+
This can be overridden with `--overlay-dir`
2325

2426
### Quick start
2527

composefs-run/src/main.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub(crate) struct CleanupArgs {
3030
/// Allocated container IP (for netavark teardown + IPAM release)
3131
#[clap(long)]
3232
container_ip: Option<std::net::IpAddr>,
33+
34+
/// PID of the pasta process to kill on cleanup
35+
#[clap(long)]
36+
pasta_pid: Option<i32>,
3337
}
3438

3539
#[derive(Clone, Debug)]
@@ -309,14 +313,10 @@ pub(crate) struct Cli {
309313
#[clap(short = 't', long)]
310314
tty: bool,
311315

312-
/// Keep stdin open
313-
#[clap(short = 'i', long)]
316+
/// Accepted for compatibility, ignored
317+
#[clap(short = 'i', long, hide = true)]
314318
interactive: bool,
315319

316-
/// Write the container init PID to this file
317-
#[clap(long)]
318-
pidfile: Option<PathBuf>,
319-
320320
/// Add a host device to the container (HOST_PATH[:CONTAINER_PATH[:PERMISSIONS]])
321321
#[clap(long, value_parser = clap::value_parser!(DeviceSpec))]
322322
device: Vec<DeviceSpec>,

composefs-run/src/run.rs

Lines changed: 27 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub fn run(
9494
}
9595

9696
let mut container_ip = None;
97+
let mut pasta_pid = None;
9798
let netns_path = if network != NetworkMode::Host {
9899
Some(setup_netns(&bundle_dir)?)
99100
} else {
@@ -109,7 +110,11 @@ pub fn run(
109110
} else if network == NetworkMode::Pasta
110111
&& let Some(ref ns) = netns_path
111112
{
112-
setup_pasta(ns, &cli.publish)?;
113+
pasta_pid = Some(setup_pasta(
114+
ns,
115+
&bundle_dir.join("pasta.pid"),
116+
&cli.publish,
117+
)?);
113118
}
114119

115120
// ── OCI spec + exec ────────────────────────────────────────────────
@@ -139,28 +144,19 @@ pub fn run(
139144
&network,
140145
netns_path.as_deref(),
141146
container_ip,
147+
pasta_pid,
142148
)?;
143149

144150
let config_json = serde_json::to_string_pretty(&spec)?;
145151
fs::write(bundle_dir.join("config.json"), &config_json)?;
146152

147-
if cli.interactive || tty {
148-
let err = Command::new("crun")
149-
.arg("run")
150-
.arg("--bundle")
151-
.arg(bundle_dir)
152-
.arg(container_id)
153-
.exec();
154-
Err(err).context("Failed to exec crun")
155-
} else {
156-
let image_name = cli.image.as_deref().unwrap_or("container");
157-
run_detached(
158-
&bundle_dir,
159-
container_id,
160-
image_name,
161-
cli.pidfile.as_deref(),
162-
)
163-
}
153+
let err = Command::new("crun")
154+
.arg("run")
155+
.arg("--bundle")
156+
.arg(bundle_dir)
157+
.arg(container_id)
158+
.exec();
159+
Err(err).context("Failed to exec crun")
164160
}
165161

166162
/// OCI poststop hook: unmount and remove the container state directory.
@@ -178,6 +174,9 @@ pub fn cleanup() -> Result<()> {
178174
);
179175
let rootfs = dir.join("bundle/rootfs");
180176
let _ = rustix::mount::unmount(&rootfs, rustix::mount::UnmountFlags::DETACH);
177+
if let Some(pid) = args.pasta_pid {
178+
unsafe { libc::kill(pid, libc::SIGTERM) };
179+
}
181180
let netns = dir.join("bundle/netns");
182181
if netns.exists() {
183182
if let (Some(id), Some(ip)) = (&args.container_id, args.container_ip) {
@@ -192,55 +191,6 @@ pub fn cleanup() -> Result<()> {
192191
Ok(())
193192
}
194193

195-
fn journal_stream_fd(identifier: &str) -> Result<std::os::fd::OwnedFd> {
196-
use std::io::Write;
197-
use std::os::unix::net::UnixStream;
198-
199-
let mut stream = UnixStream::connect("/run/systemd/journal/stdout")
200-
.context("Connecting to journal socket")?;
201-
202-
write!(stream, "{identifier}\n\n6\n0\n0\n0\n0\n")?;
203-
stream.flush()?;
204-
205-
Ok(std::os::fd::OwnedFd::from(stream))
206-
}
207-
208-
fn run_detached(
209-
bundle_dir: &Path,
210-
container_id: &str,
211-
image_name: &str,
212-
pidfile: Option<&Path>,
213-
) -> Result<()> {
214-
let journal_fd =
215-
journal_stream_fd(&format!("cfsrun:{image_name}")).context("Creating journal stream fd")?;
216-
let journal_fd2 = rustix::io::dup(&journal_fd)?;
217-
218-
let pidfile_path = match pidfile {
219-
Some(pf) => pf.to_owned(),
220-
None => bundle_dir.join("container.pid"),
221-
};
222-
223-
let status = Command::new("crun")
224-
.arg("run")
225-
.arg("--detach")
226-
.arg("--bundle")
227-
.arg(bundle_dir)
228-
.arg("--pid-file")
229-
.arg(&pidfile_path)
230-
.arg(container_id)
231-
.stdin(std::process::Stdio::null())
232-
.stdout(std::process::Stdio::from(std::fs::File::from(journal_fd)))
233-
.stderr(std::process::Stdio::from(std::fs::File::from(journal_fd2)))
234-
.status()
235-
.context("Failed to run crun")?;
236-
ensure!(status.success(), "crun run --detach failed: {status}");
237-
238-
let pid = fs::read_to_string(&pidfile_path).context("Reading PID file")?;
239-
println!("{}", pid.trim());
240-
241-
Ok(())
242-
}
243-
244194
/// Mount a composefs image via FUSE (rootless).
245195
fn mount_rootfs_with_fuse(
246196
repo_path: &Path,
@@ -421,14 +371,16 @@ fn setup_netns(bundle_dir: &Path) -> Result<PathBuf> {
421371
Ok(netns_path)
422372
}
423373

424-
fn setup_pasta(netns_path: &Path, publish: &[PortSpec]) -> Result<()> {
374+
fn setup_pasta(netns_path: &Path, pid_file: &Path, publish: &[PortSpec]) -> Result<i32> {
425375
let mut pasta_cmd = Command::new("pasta");
426376
pasta_cmd
427377
.arg("--config-net")
428378
.arg("--dns-forward")
429379
.arg("169.254.1.1")
430380
.arg("--netns")
431381
.arg(netns_path)
382+
.arg("--pid")
383+
.arg(pid_file)
432384
.arg("--quiet");
433385

434386
if publish.is_empty() {
@@ -449,7 +401,8 @@ fn setup_pasta(netns_path: &Path, publish: &[PortSpec]) -> Result<()> {
449401
String::from_utf8_lossy(&output.stderr)
450402
);
451403

452-
Ok(())
404+
let pid_str = fs::read_to_string(pid_file).context("Reading pasta PID file")?;
405+
pid_str.trim().parse::<i32>().context("Parsing pasta PID")
453406
}
454407

455408
fn create_detached_tmpfs() -> Result<rustix::fd::OwnedFd> {
@@ -597,6 +550,7 @@ fn build_runtime_spec(
597550
network: &NetworkMode,
598551
netns_path: Option<&Path>,
599552
container_ip: Option<std::net::IpAddr>,
553+
pasta_pid: Option<i32>,
600554
) -> Result<Spec> {
601555
use std::collections::HashSet;
602556

@@ -995,6 +949,10 @@ fn build_runtime_spec(
995949
hook_args.push("--container-ip".into());
996950
hook_args.push(ip.to_string());
997951
}
952+
if let Some(pid) = pasta_pid {
953+
hook_args.push("--pasta-pid".into());
954+
hook_args.push(pid.to_string());
955+
}
998956
let hook = HookBuilder::default()
999957
.path(self_exe)
1000958
.args(hook_args)

composefs-run/tests/integration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ fn repo() -> &'static Path {
4848

4949
fn cfsrun() -> Command {
5050
let mut cmd = Command::new(env!("CARGO_BIN_EXE_cfsrun"));
51-
cmd.arg("--repo").arg(repo()).arg("-i");
51+
cmd.arg("--repo").arg(repo());
5252
cmd
5353
}
5454

0 commit comments

Comments
 (0)