chroot-distro 2.2.0__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/PKG-INFO +1 -1
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/pyproject.toml +1 -1
- chroot_distro-2.2.1/src/chroot_distro/commands/kill.py +157 -0
- chroot_distro-2.2.1/tests/unit/test_kill.py +317 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/uv.lock +1 -1
- chroot_distro-2.2.0/src/chroot_distro/commands/kill.py +0 -88
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.editorconfig +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.github/codeql/codeql-config.yml +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.github/dependabot.yml +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.github/workflows/ci.yml +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.github/workflows/codeql.yml +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.github/workflows/publish.yml +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.gitignore +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/.python-version +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/LICENSE +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/README.md +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/check-before-commit.sh +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/arch.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/atomic.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/cli.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/backup.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/build.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/clear_cache.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/copy.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/diff.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/help/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/help/pages.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/help/render.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/install.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/install_local.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/list_cmd.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/login/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/login/bindings.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/login/chroot_cmd.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/login/env.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/login/passwd.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/ps.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/push.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/remove.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/rename.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/reset.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/restore.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/run.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/search.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/sync.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/commands/unmount.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/completions/_chroot-distro +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/completions/chroot-distro.bash +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/completions/chroot-distro.fish +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/constants.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/elevate.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/exceptions.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/android.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_cache.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/constants.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/copy_step.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/dockerignore.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/engine.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/errors.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/handlers.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/parsing.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/run_step.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/stage.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/users.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/display.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/__init__.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/cache.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/layers.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/media.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/pull.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/push.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/refs.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/docker/transport.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/dockerfile.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/download.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/gpu.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/layer_diff.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/mount_manager.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/namespace.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/nvidia.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/oci_writer.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/rootfs.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/session.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/sound.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/tar_extract.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/wayland.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/x11.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/locking.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/message.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/names.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/parser.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/paths.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/progress.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/py.typed +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/rate_limit.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/conftest.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_android.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_arch.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_backup_restore.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_bind_options.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_cli.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_constants.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_diff_baseline_cache.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_display.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_display_sockets.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_docker_refs.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_dockerfile.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_download_algorithms.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_download_blob_multi.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_download_multi.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_elevate.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_gpu.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_install.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_install_local.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_layer_diff.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_list.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_locking.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_login_helpers.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_message.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_mount_manager_ns.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_names.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_namespace.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_parser.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_paths.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_progress.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_push_chunked.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_remove.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_rootfs.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_sound.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_tar_extract.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_unmount.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_wayland.py +0 -0
- {chroot_distro-2.2.0 → chroot_distro-2.2.1}/tests/unit/test_x11.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chroot-distro
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: chroot-distro is a lightweight Linux container management utility built around chroot.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sabamdarif/chroot-distro
|
|
6
6
|
Project-URL: Repository, https://github.com/sabamdarif/chroot-distro
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chroot-distro"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.1"
|
|
8
8
|
description = "chroot-distro is a lightweight Linux container management utility built around chroot."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import chroot_distro.helpers.mount_manager as mount_manager
|
|
9
|
+
import chroot_distro.helpers.namespace as namespace
|
|
10
|
+
import chroot_distro.helpers.session as session
|
|
11
|
+
from chroot_distro.locking import ContainerLock
|
|
12
|
+
from chroot_distro.message import crit_error, log_info, warn
|
|
13
|
+
from chroot_distro.names import require_valid_name
|
|
14
|
+
from chroot_distro.paths import container_rootfs
|
|
15
|
+
|
|
16
|
+
_SIGTERM_GRACE_SECS = 1.0
|
|
17
|
+
_SIGKILL_WAIT_SECS = 2.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _wait_until_gone(container_name: str, timeout: float) -> list[int]:
|
|
21
|
+
"""Poll for active chroot PIDs until none remain or *timeout* elapses."""
|
|
22
|
+
deadline = time.time() + timeout
|
|
23
|
+
while time.time() < deadline:
|
|
24
|
+
remaining = session.get_active_chroot_pids(container_name)
|
|
25
|
+
if not remaining:
|
|
26
|
+
return []
|
|
27
|
+
time.sleep(0.1)
|
|
28
|
+
return session.get_active_chroot_pids(container_name)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def command_kill(args) -> None:
|
|
32
|
+
"""Forcibly stop all processes in a container and tear it down.
|
|
33
|
+
|
|
34
|
+
First try standard unmount, then lazy unmount. If mounts remain or processes
|
|
35
|
+
are active, kill all processes and retry unmounting. If still failing,
|
|
36
|
+
try forceful unmount and print a detailed error if mounts remain.
|
|
37
|
+
"""
|
|
38
|
+
container_name = args.container_name
|
|
39
|
+
require_valid_name(container_name)
|
|
40
|
+
|
|
41
|
+
rootfs_dir = container_rootfs(container_name)
|
|
42
|
+
if not os.path.isdir(rootfs_dir):
|
|
43
|
+
crit_error(f"container '{container_name}' is not installed.")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
holder = namespace.get_live_holder(container_name)
|
|
47
|
+
|
|
48
|
+
active_pids = session.get_active_chroot_pids(container_name)
|
|
49
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
50
|
+
if not active_pids and holder is None and not active_mounts:
|
|
51
|
+
log_info(f"Container '{container_name}' is not running.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
umount_bin = mount_manager._resolve_umount()
|
|
55
|
+
|
|
56
|
+
def run_umount(target_path: str, flags: list[str] | None = None) -> bool:
|
|
57
|
+
cmd = [umount_bin]
|
|
58
|
+
if flags:
|
|
59
|
+
cmd.extend(flags)
|
|
60
|
+
cmd.append(target_path)
|
|
61
|
+
|
|
62
|
+
if holder is not None:
|
|
63
|
+
res = holder.run(cmd, capture_output=True, text=True)
|
|
64
|
+
else:
|
|
65
|
+
res = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
66
|
+
return res.returncode == 0
|
|
67
|
+
|
|
68
|
+
lock = ContainerLock(container_name, exclusive=True, command="kill")
|
|
69
|
+
acquired = lock.acquire()
|
|
70
|
+
if not acquired:
|
|
71
|
+
log_info(f"Container '{container_name}' is busy (active sessions exist). Forcing cleanup...")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Step 1: Try standard unmount
|
|
75
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
76
|
+
if active_mounts:
|
|
77
|
+
log_info("Attempting standard unmount of active mount points...")
|
|
78
|
+
for m in active_mounts:
|
|
79
|
+
run_umount(m)
|
|
80
|
+
|
|
81
|
+
# Step 2: Try lazy unmount if mounts remain
|
|
82
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
83
|
+
if active_mounts:
|
|
84
|
+
log_info("Some mounts remain busy. Attempting lazy unmount...")
|
|
85
|
+
for m in active_mounts:
|
|
86
|
+
run_umount(m, ["-l"])
|
|
87
|
+
|
|
88
|
+
# Step 3: Kill processes and unmount again if active PIDs or mounts remain
|
|
89
|
+
active_pids = session.get_active_chroot_pids(container_name)
|
|
90
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
91
|
+
if active_pids or active_mounts:
|
|
92
|
+
if active_pids:
|
|
93
|
+
log_info(
|
|
94
|
+
f"Killing {len(active_pids)} process(es) in container '{container_name}' (PIDs: {active_pids})..."
|
|
95
|
+
)
|
|
96
|
+
for pid in active_pids:
|
|
97
|
+
with contextlib.suppress(OSError):
|
|
98
|
+
os.kill(pid, signal.SIGTERM)
|
|
99
|
+
|
|
100
|
+
remaining = _wait_until_gone(container_name, _SIGTERM_GRACE_SECS)
|
|
101
|
+
if remaining:
|
|
102
|
+
log_info(f"Processes {remaining} did not exit; sending SIGKILL...")
|
|
103
|
+
for pid in remaining:
|
|
104
|
+
with contextlib.suppress(OSError):
|
|
105
|
+
os.kill(pid, signal.SIGKILL)
|
|
106
|
+
remaining = _wait_until_gone(container_name, _SIGKILL_WAIT_SECS)
|
|
107
|
+
if remaining:
|
|
108
|
+
warn(f"Some processes could not be killed: {remaining}")
|
|
109
|
+
|
|
110
|
+
# Now that processes have been signaled, attempt to acquire the exclusive lock
|
|
111
|
+
if not acquired:
|
|
112
|
+
acquired = lock.acquire()
|
|
113
|
+
|
|
114
|
+
# Retry unmounting after killing processes
|
|
115
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
116
|
+
if active_mounts:
|
|
117
|
+
log_info("Retrying standard unmount after killing processes...")
|
|
118
|
+
for m in active_mounts:
|
|
119
|
+
run_umount(m)
|
|
120
|
+
|
|
121
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
122
|
+
if active_mounts:
|
|
123
|
+
log_info("Retrying lazy unmount after killing processes...")
|
|
124
|
+
for m in active_mounts:
|
|
125
|
+
run_umount(m, ["-l"])
|
|
126
|
+
|
|
127
|
+
# Step 4: Forceful unmount and detailed error if still failed
|
|
128
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
129
|
+
if active_mounts:
|
|
130
|
+
log_info("Some mounts still remain. Attempting forceful unmount...")
|
|
131
|
+
for m in active_mounts:
|
|
132
|
+
run_umount(m, ["-f"])
|
|
133
|
+
|
|
134
|
+
active_mounts = mount_manager.get_active_mounts(rootfs_dir, holder=holder)
|
|
135
|
+
if active_mounts:
|
|
136
|
+
active_pids = session.get_active_chroot_pids(container_name)
|
|
137
|
+
crit_error(
|
|
138
|
+
f"Failed to kill and unmount container '{container_name}'.\n"
|
|
139
|
+
f"Remaining active mounts:\n" + "\n".join(f" - {m}" for m in active_mounts) + "\n"
|
|
140
|
+
f"Remaining active process PIDs: {active_pids if active_pids else 'None'}"
|
|
141
|
+
)
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
# Cleanup namespace and sessions
|
|
145
|
+
session.reset(container_name)
|
|
146
|
+
if holder is not None:
|
|
147
|
+
namespace.release_holder(container_name)
|
|
148
|
+
namespace.clear_isolation_mode(container_name)
|
|
149
|
+
|
|
150
|
+
log_info(f"Container '{container_name}' successfully killed and unmounted.")
|
|
151
|
+
|
|
152
|
+
finally:
|
|
153
|
+
if acquired:
|
|
154
|
+
lock.release()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = ("command_kill",)
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import signal
|
|
2
|
+
import sys
|
|
3
|
+
from unittest.mock import MagicMock, call, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from chroot_distro.commands.kill import command_kill
|
|
8
|
+
from chroot_distro.exceptions import LockConflictError
|
|
9
|
+
from chroot_distro.parser import build_parser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_parser_kill():
|
|
13
|
+
parser = build_parser()
|
|
14
|
+
args = parser.parse_args(["kill", "ubuntu"])
|
|
15
|
+
assert args.command == "kill"
|
|
16
|
+
assert args.container_name == "ubuntu"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
20
|
+
@patch("os.path.isdir", return_value=False)
|
|
21
|
+
@patch("chroot_distro.commands.kill.crit_error")
|
|
22
|
+
def test_kill_container_not_installed(mock_crit_error, mock_isdir, mock_rootfs):
|
|
23
|
+
args = MagicMock()
|
|
24
|
+
args.container_name = "ubuntu"
|
|
25
|
+
|
|
26
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
27
|
+
command_kill(args)
|
|
28
|
+
|
|
29
|
+
assert exc_info.value.code == 1
|
|
30
|
+
mock_crit_error.assert_called_once_with("container 'ubuntu' is not installed.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
34
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
35
|
+
@patch("os.path.isdir", return_value=True)
|
|
36
|
+
@patch("chroot_distro.commands.kill.session")
|
|
37
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
38
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
39
|
+
def test_kill_not_running(mock_log, mock_mount, mock_session, mock_isdir, mock_rootfs, *_mocks):
|
|
40
|
+
args = MagicMock()
|
|
41
|
+
args.container_name = "ubuntu"
|
|
42
|
+
|
|
43
|
+
mock_session.get_active_chroot_pids.return_value = []
|
|
44
|
+
mock_mount.get_active_mounts.return_value = []
|
|
45
|
+
|
|
46
|
+
command_kill(args)
|
|
47
|
+
|
|
48
|
+
mock_log.assert_called_once_with("Container 'ubuntu' is not running.")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
52
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
53
|
+
@patch("os.path.isdir", return_value=True)
|
|
54
|
+
@patch("chroot_distro.commands.kill.ContainerLock")
|
|
55
|
+
@patch("chroot_distro.commands.kill.session")
|
|
56
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
57
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
58
|
+
@patch("subprocess.run")
|
|
59
|
+
def test_kill_standard_unmount_success(
|
|
60
|
+
mock_run, mock_log, mock_mount, mock_session, mock_lock, mock_isdir, mock_rootfs, *_mocks
|
|
61
|
+
):
|
|
62
|
+
"""If standard unmount succeeds, we don't need lazy/kill/forceful."""
|
|
63
|
+
args = MagicMock()
|
|
64
|
+
args.container_name = "ubuntu"
|
|
65
|
+
|
|
66
|
+
mock_session.get_active_chroot_pids.return_value = []
|
|
67
|
+
# get_active_mounts returns active mounts for first check, then empty for subsequent checks
|
|
68
|
+
mock_mount.get_active_mounts.side_effect = [
|
|
69
|
+
["/mock/containers/ubuntu/rootfs/proc"], # initial check
|
|
70
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 1 check
|
|
71
|
+
[], # Step 2 check
|
|
72
|
+
[], # Step 3 check
|
|
73
|
+
[], # Step 4 check
|
|
74
|
+
]
|
|
75
|
+
mock_mount._resolve_umount.return_value = "/bin/umount"
|
|
76
|
+
|
|
77
|
+
mock_lock_instance = MagicMock()
|
|
78
|
+
mock_lock_instance.acquire.return_value = True
|
|
79
|
+
mock_lock.return_value = mock_lock_instance
|
|
80
|
+
|
|
81
|
+
mock_run_res = MagicMock()
|
|
82
|
+
mock_run_res.returncode = 0
|
|
83
|
+
mock_run.return_value = mock_run_res
|
|
84
|
+
|
|
85
|
+
command_kill(args)
|
|
86
|
+
|
|
87
|
+
mock_lock_instance.acquire.assert_called_once()
|
|
88
|
+
mock_run.assert_called_once_with(
|
|
89
|
+
["/bin/umount", "/mock/containers/ubuntu/rootfs/proc"], capture_output=True, text=True, check=False
|
|
90
|
+
)
|
|
91
|
+
mock_session.reset.assert_called_once_with("ubuntu")
|
|
92
|
+
mock_log.assert_any_call("Container 'ubuntu' successfully killed and unmounted.")
|
|
93
|
+
mock_lock_instance.release.assert_called_once()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
97
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
98
|
+
@patch("os.path.isdir", return_value=True)
|
|
99
|
+
@patch("chroot_distro.commands.kill.ContainerLock")
|
|
100
|
+
@patch("chroot_distro.commands.kill.session")
|
|
101
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
102
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
103
|
+
@patch("subprocess.run")
|
|
104
|
+
def test_kill_lazy_unmount_success(
|
|
105
|
+
mock_run, mock_log, mock_mount, mock_session, mock_lock, mock_isdir, mock_rootfs, *_mocks
|
|
106
|
+
):
|
|
107
|
+
"""If standard unmount fails, lazy unmount succeeds."""
|
|
108
|
+
args = MagicMock()
|
|
109
|
+
args.container_name = "ubuntu"
|
|
110
|
+
|
|
111
|
+
mock_session.get_active_chroot_pids.return_value = []
|
|
112
|
+
# get_active_mounts sequences:
|
|
113
|
+
mock_mount.get_active_mounts.side_effect = [
|
|
114
|
+
["/mock/containers/ubuntu/rootfs/proc"], # initial check
|
|
115
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 1 check
|
|
116
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 2 check
|
|
117
|
+
[], # Step 3 check
|
|
118
|
+
[], # Step 4 check
|
|
119
|
+
]
|
|
120
|
+
mock_mount._resolve_umount.return_value = "/bin/umount"
|
|
121
|
+
|
|
122
|
+
mock_lock_instance = MagicMock()
|
|
123
|
+
mock_lock_instance.acquire.return_value = True
|
|
124
|
+
mock_lock.return_value = mock_lock_instance
|
|
125
|
+
|
|
126
|
+
# Standard umount fails, lazy umount succeeds
|
|
127
|
+
mock_run_res_fail = MagicMock(returncode=1)
|
|
128
|
+
mock_run_res_ok = MagicMock(returncode=0)
|
|
129
|
+
mock_run.side_effect = [mock_run_res_fail, mock_run_res_ok]
|
|
130
|
+
|
|
131
|
+
command_kill(args)
|
|
132
|
+
|
|
133
|
+
mock_run.assert_has_calls(
|
|
134
|
+
[
|
|
135
|
+
call(["/bin/umount", "/mock/containers/ubuntu/rootfs/proc"], capture_output=True, text=True, check=False),
|
|
136
|
+
call(
|
|
137
|
+
["/bin/umount", "-l", "/mock/containers/ubuntu/rootfs/proc"],
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
check=False,
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
mock_session.reset.assert_called_once_with("ubuntu")
|
|
145
|
+
mock_log.assert_any_call("Container 'ubuntu' successfully killed and unmounted.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
149
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
150
|
+
@patch("os.path.isdir", return_value=True)
|
|
151
|
+
@patch("chroot_distro.commands.kill.ContainerLock")
|
|
152
|
+
@patch("chroot_distro.commands.kill.session")
|
|
153
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
154
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
155
|
+
@patch("subprocess.run")
|
|
156
|
+
@patch("chroot_distro.commands.kill.os.kill")
|
|
157
|
+
@patch("chroot_distro.commands.kill.time.sleep")
|
|
158
|
+
@patch("chroot_distro.commands.kill.time.time")
|
|
159
|
+
def test_kill_process_then_unmount(
|
|
160
|
+
mock_time,
|
|
161
|
+
mock_sleep,
|
|
162
|
+
mock_kill,
|
|
163
|
+
mock_run,
|
|
164
|
+
mock_log,
|
|
165
|
+
mock_mount,
|
|
166
|
+
mock_session,
|
|
167
|
+
mock_lock,
|
|
168
|
+
mock_isdir,
|
|
169
|
+
mock_rootfs,
|
|
170
|
+
*_mocks,
|
|
171
|
+
):
|
|
172
|
+
"""Processes are active, we terminate them and successfully retry unmounting."""
|
|
173
|
+
args = MagicMock()
|
|
174
|
+
args.container_name = "ubuntu"
|
|
175
|
+
|
|
176
|
+
# PIDs sequence:
|
|
177
|
+
# 1. Initial check (PIDs active) -> [1000]
|
|
178
|
+
# 2. Step 3 check -> [1000]
|
|
179
|
+
# 3. _wait_until_gone loop check -> [] (exited after SIGTERM)
|
|
180
|
+
mock_session.get_active_chroot_pids.side_effect = [[1000], [1000], []]
|
|
181
|
+
|
|
182
|
+
# Mounts sequence:
|
|
183
|
+
# 1. Initial check -> ["/mock/containers/ubuntu/rootfs/proc"]
|
|
184
|
+
# 2. Step 1 (standard) -> ["/mock/containers/ubuntu/rootfs/proc"]
|
|
185
|
+
# 3. Step 2 (lazy) -> ["/mock/containers/ubuntu/rootfs/proc"]
|
|
186
|
+
# 4. Step 3 (post-kill standard retry) -> ["/mock/containers/ubuntu/rootfs/proc"]
|
|
187
|
+
# 5. Step 3 (post-kill lazy retry) -> []
|
|
188
|
+
# 6. Step 4 (forceful check) -> []
|
|
189
|
+
mock_mount.get_active_mounts.side_effect = [
|
|
190
|
+
["/mock/containers/ubuntu/rootfs/proc"],
|
|
191
|
+
["/mock/containers/ubuntu/rootfs/proc"],
|
|
192
|
+
["/mock/containers/ubuntu/rootfs/proc"],
|
|
193
|
+
["/mock/containers/ubuntu/rootfs/proc"],
|
|
194
|
+
["/mock/containers/ubuntu/rootfs/proc"],
|
|
195
|
+
[],
|
|
196
|
+
[],
|
|
197
|
+
]
|
|
198
|
+
mock_mount._resolve_umount.return_value = "/bin/umount"
|
|
199
|
+
|
|
200
|
+
mock_lock_instance = MagicMock()
|
|
201
|
+
mock_lock_instance.acquire.return_value = True
|
|
202
|
+
mock_lock.return_value = mock_lock_instance
|
|
203
|
+
|
|
204
|
+
mock_time.side_effect = [0, 0.1, 0.2, 0.3]
|
|
205
|
+
|
|
206
|
+
# Standard umounts fail, lazy umounts succeed
|
|
207
|
+
mock_run_res_fail = MagicMock(returncode=1)
|
|
208
|
+
mock_run_res_ok = MagicMock(returncode=0)
|
|
209
|
+
mock_run.side_effect = [
|
|
210
|
+
mock_run_res_fail, # Step 1 standard
|
|
211
|
+
mock_run_res_fail, # Step 2 lazy
|
|
212
|
+
mock_run_res_fail, # Step 3 post-kill standard
|
|
213
|
+
mock_run_res_ok, # Step 3 post-kill lazy
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
command_kill(args)
|
|
217
|
+
|
|
218
|
+
mock_kill.assert_called_once_with(1000, signal.SIGTERM)
|
|
219
|
+
mock_session.reset.assert_called_once_with("ubuntu")
|
|
220
|
+
mock_log.assert_any_call("Container 'ubuntu' successfully killed and unmounted.")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
224
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
225
|
+
@patch("os.path.isdir", return_value=True)
|
|
226
|
+
@patch("chroot_distro.commands.kill.ContainerLock")
|
|
227
|
+
@patch("chroot_distro.commands.kill.session")
|
|
228
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
229
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
230
|
+
@patch("subprocess.run")
|
|
231
|
+
@patch("chroot_distro.commands.kill.crit_error")
|
|
232
|
+
def test_kill_forceful_failure_diagnostic(
|
|
233
|
+
mock_crit_error, mock_run, mock_log, mock_mount, mock_session, mock_lock, mock_isdir, mock_rootfs, *_mocks
|
|
234
|
+
):
|
|
235
|
+
"""If forceful unmount also fails, we output detailed diagnostics."""
|
|
236
|
+
args = MagicMock()
|
|
237
|
+
args.container_name = "ubuntu"
|
|
238
|
+
|
|
239
|
+
mock_session.get_active_chroot_pids.return_value = []
|
|
240
|
+
# get_active_mounts always returns a mount
|
|
241
|
+
mock_mount.get_active_mounts.return_value = ["/mock/containers/ubuntu/rootfs/proc"]
|
|
242
|
+
mock_mount._resolve_umount.return_value = "/bin/umount"
|
|
243
|
+
|
|
244
|
+
mock_lock_instance = MagicMock()
|
|
245
|
+
mock_lock_instance.acquire.return_value = True
|
|
246
|
+
mock_lock.return_value = mock_lock_instance
|
|
247
|
+
|
|
248
|
+
# All umount commands fail
|
|
249
|
+
mock_run.return_value = MagicMock(returncode=1)
|
|
250
|
+
|
|
251
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
252
|
+
command_kill(args)
|
|
253
|
+
|
|
254
|
+
assert exc_info.value.code == 1
|
|
255
|
+
mock_crit_error.assert_called_once_with(
|
|
256
|
+
"Failed to kill and unmount container 'ubuntu'.\n"
|
|
257
|
+
"Remaining active mounts:\n"
|
|
258
|
+
" - /mock/containers/ubuntu/rootfs/proc\n"
|
|
259
|
+
"Remaining active process PIDs: None"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@patch("chroot_distro.commands.kill.namespace.get_live_holder", return_value=None)
|
|
264
|
+
@patch("chroot_distro.commands.kill.container_rootfs", return_value="/mock/containers/ubuntu/rootfs")
|
|
265
|
+
@patch("os.path.isdir", return_value=True)
|
|
266
|
+
@patch("chroot_distro.commands.kill.ContainerLock")
|
|
267
|
+
@patch("chroot_distro.commands.kill.session")
|
|
268
|
+
@patch("chroot_distro.commands.kill.mount_manager")
|
|
269
|
+
@patch("chroot_distro.commands.kill.log_info")
|
|
270
|
+
@patch("subprocess.run")
|
|
271
|
+
@patch("chroot_distro.commands.kill.os.kill")
|
|
272
|
+
@patch("chroot_distro.commands.kill.time.sleep")
|
|
273
|
+
@patch("chroot_distro.commands.kill.time.time")
|
|
274
|
+
def test_kill_lock_conflict_bypass(
|
|
275
|
+
mock_time,
|
|
276
|
+
mock_sleep,
|
|
277
|
+
mock_kill,
|
|
278
|
+
mock_run,
|
|
279
|
+
mock_log,
|
|
280
|
+
mock_mount,
|
|
281
|
+
mock_session,
|
|
282
|
+
mock_lock,
|
|
283
|
+
mock_isdir,
|
|
284
|
+
mock_rootfs,
|
|
285
|
+
*_mocks,
|
|
286
|
+
):
|
|
287
|
+
"""When container lock is busy initially, we bypass it, kill processes, and acquire it afterward."""
|
|
288
|
+
args = MagicMock()
|
|
289
|
+
args.container_name = "ubuntu"
|
|
290
|
+
|
|
291
|
+
mock_session.get_active_chroot_pids.side_effect = [[1000], [1000], []]
|
|
292
|
+
mock_mount.get_active_mounts.side_effect = [
|
|
293
|
+
["/mock/containers/ubuntu/rootfs/proc"], # initial check
|
|
294
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 1 standard
|
|
295
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 2 lazy
|
|
296
|
+
["/mock/containers/ubuntu/rootfs/proc"], # Step 3 post-kill standard
|
|
297
|
+
[], # Step 3 post-kill lazy
|
|
298
|
+
[], # Step 4 check
|
|
299
|
+
]
|
|
300
|
+
mock_mount._resolve_umount.return_value = "/bin/umount"
|
|
301
|
+
|
|
302
|
+
mock_lock_instance = MagicMock()
|
|
303
|
+
# First acquire fails, second acquire (after processes killed) succeeds
|
|
304
|
+
mock_lock_instance.acquire.side_effect = [False, True]
|
|
305
|
+
mock_lock.return_value = mock_lock_instance
|
|
306
|
+
|
|
307
|
+
mock_time.side_effect = [0, 0.1, 0.2, 0.3]
|
|
308
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
309
|
+
|
|
310
|
+
command_kill(args)
|
|
311
|
+
|
|
312
|
+
# We logged a warning/info about lock conflict
|
|
313
|
+
mock_log.assert_any_call("Container 'ubuntu' is busy (active sessions exist). Forcing cleanup...")
|
|
314
|
+
# Two calls to acquire
|
|
315
|
+
assert mock_lock_instance.acquire.call_count == 2
|
|
316
|
+
# Lock released at the end
|
|
317
|
+
mock_lock_instance.release.assert_called_once()
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import contextlib
|
|
2
|
-
import os
|
|
3
|
-
import signal
|
|
4
|
-
import sys
|
|
5
|
-
import time
|
|
6
|
-
|
|
7
|
-
import chroot_distro.helpers.mount_manager as mount_manager
|
|
8
|
-
import chroot_distro.helpers.namespace as namespace
|
|
9
|
-
import chroot_distro.helpers.session as session
|
|
10
|
-
from chroot_distro.locking import ContainerLock
|
|
11
|
-
from chroot_distro.message import crit_error, log_info, warn
|
|
12
|
-
from chroot_distro.names import require_valid_name
|
|
13
|
-
from chroot_distro.paths import container_rootfs
|
|
14
|
-
|
|
15
|
-
_SIGTERM_GRACE_SECS = 1.0
|
|
16
|
-
_SIGKILL_WAIT_SECS = 2.0
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _wait_until_gone(container_name: str, timeout: float) -> list[int]:
|
|
20
|
-
"""Poll for active chroot PIDs until none remain or *timeout* elapses."""
|
|
21
|
-
deadline = time.time() + timeout
|
|
22
|
-
while time.time() < deadline:
|
|
23
|
-
remaining = session.get_active_chroot_pids(container_name)
|
|
24
|
-
if not remaining:
|
|
25
|
-
return []
|
|
26
|
-
time.sleep(0.1)
|
|
27
|
-
return session.get_active_chroot_pids(container_name)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def command_kill(args) -> None:
|
|
31
|
-
"""Forcibly stop all processes in a container and tear it down.
|
|
32
|
-
|
|
33
|
-
The abrupt counterpart to ``unmount``: send SIGTERM, then SIGKILL after a
|
|
34
|
-
short grace period, then unmount and release the namespace holder.
|
|
35
|
-
"""
|
|
36
|
-
container_name = args.container_name
|
|
37
|
-
require_valid_name(container_name)
|
|
38
|
-
|
|
39
|
-
rootfs_dir = container_rootfs(container_name)
|
|
40
|
-
if not os.path.isdir(rootfs_dir):
|
|
41
|
-
crit_error(f"container '{container_name}' is not installed.")
|
|
42
|
-
sys.exit(1)
|
|
43
|
-
|
|
44
|
-
with ContainerLock(container_name, exclusive=True, command="kill"):
|
|
45
|
-
active_pids = session.get_active_chroot_pids(container_name)
|
|
46
|
-
holder = namespace.get_live_holder(container_name)
|
|
47
|
-
|
|
48
|
-
if not active_pids and holder is None and not mount_manager.get_active_mounts(rootfs_dir):
|
|
49
|
-
log_info(f"Container '{container_name}' is not running.")
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
if active_pids:
|
|
53
|
-
log_info(f"Killing {len(active_pids)} process(es) in container '{container_name}' (PIDs: {active_pids})...")
|
|
54
|
-
for pid in active_pids:
|
|
55
|
-
with contextlib.suppress(OSError):
|
|
56
|
-
os.kill(pid, signal.SIGTERM)
|
|
57
|
-
|
|
58
|
-
remaining = _wait_until_gone(container_name, _SIGTERM_GRACE_SECS)
|
|
59
|
-
if remaining:
|
|
60
|
-
log_info(f"Processes {remaining} did not exit; sending SIGKILL...")
|
|
61
|
-
for pid in remaining:
|
|
62
|
-
with contextlib.suppress(OSError):
|
|
63
|
-
os.kill(pid, signal.SIGKILL)
|
|
64
|
-
remaining = _wait_until_gone(container_name, _SIGKILL_WAIT_SECS)
|
|
65
|
-
if remaining:
|
|
66
|
-
warn(f"Some processes could not be killed: {remaining}")
|
|
67
|
-
|
|
68
|
-
session.reset(container_name)
|
|
69
|
-
|
|
70
|
-
log_info("Unmounting active mount points under rootfs...")
|
|
71
|
-
try:
|
|
72
|
-
mount_manager.unmount_all(rootfs_dir, holder=holder)
|
|
73
|
-
except Exception as e:
|
|
74
|
-
crit_error(f"Failed to unmount: {e}")
|
|
75
|
-
sys.exit(1)
|
|
76
|
-
|
|
77
|
-
if holder is not None:
|
|
78
|
-
namespace.release_holder(container_name)
|
|
79
|
-
namespace.clear_isolation_mode(container_name)
|
|
80
|
-
|
|
81
|
-
remaining_mounts = mount_manager.get_active_mounts(rootfs_dir)
|
|
82
|
-
if remaining_mounts:
|
|
83
|
-
warn(f"Some active mounts remain: {remaining_mounts}")
|
|
84
|
-
else:
|
|
85
|
-
log_info(f"Container '{container_name}' killed and unmounted.")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
__all__ = ("command_kill",)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/completions/chroot-distro.bash
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/completions/chroot-distro.fish
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/__init__.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/constants.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/copy_step.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/dockerignore.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/engine.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/errors.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/handlers.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/parsing.py
RENAMED
|
File without changes
|
{chroot_distro-2.2.0 → chroot_distro-2.2.1}/src/chroot_distro/helpers/build_engine/run_step.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|