1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//! `pelagos rm` — remove a container.
use super::{check_liveness, container_dir, read_state, ContainerStatus};
pub fn cmd_rm(name: &str, force: bool) -> Result<(), Box<dyn std::error::Error>> {
let state = read_state(name).map_err(|_| format!("no container named '{}'", name))?;
if state.status == ContainerStatus::Running && check_liveness(state.pid) {
if force {
// SIGTERM first so the watcher can run teardown (veth/netns/nftables cleanup).
// The watcher's SIGTERM handler forwards the signal to the container; when the
// container exits the watcher's wait() returns and teardown_resources() runs.
let r = unsafe { libc::kill(state.pid, libc::SIGTERM) };
if r != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ESRCH) {
return Err(format!("kill({}): {}", state.pid, err).into());
}
}
// Give the container up to 5 s to exit cleanly before resorting to SIGKILL.
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
while check_liveness(state.pid) && std::time::Instant::now() < deadline {
std::thread::sleep(std::time::Duration::from_millis(100));
}
if check_liveness(state.pid) {
unsafe { libc::kill(state.pid, libc::SIGKILL) };
for _ in 0..10 {
std::thread::sleep(std::time::Duration::from_millis(100));
if !check_liveness(state.pid) {
break;
}
}
}
// Sweep the whole container cgroup so forked/`setsid`'d descendants (which
// the single-PID kill above misses) can't survive as orphans (#412).
if let Some(ref cg) = state.cgroup_name {
pelagos::cgroup::kill_cgroup(cg);
}
// Wait for the watcher to finish cleanup (nftables/veth/overlay teardown)
// before removing state. The watcher exits shortly after the container dies.
if state.watcher_pid > 0 {
let watcher_deadline =
std::time::Instant::now() + std::time::Duration::from_secs(5);
while check_liveness(state.watcher_pid as i32)
&& std::time::Instant::now() < watcher_deadline
{
std::thread::sleep(std::time::Duration::from_millis(50));
}
}
} else {
return Err(format!(
"container '{}' is running; use --force to remove it or `pelagos stop {}` first",
name, name
)
.into());
}
}
// Reject an empty/separator-laden name before building a removal path: an
// empty name makes `container_dir("")` resolve to the containers parent dir
// (wiping ALL containers), and a `/`-leading name escapes it entirely (#347).
if name.is_empty() || name.contains('/') || name == "." || name == ".." {
return Err(format!("invalid container name '{}'", name).into());
}
let dir = container_dir(name);
pelagos::paths::guarded_remove_dir_all(&dir)
.map_err(|e| format!("remove {}: {}", dir.display(), e))?;
Ok(())
}