summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-containers/vcontainer')
-rwxr-xr-xrecipes-containers/vcontainer/files/vdkr.sh26
-rwxr-xr-xrecipes-containers/vcontainer/files/vpdmn.sh26
-rwxr-xr-xrecipes-containers/vcontainer/files/vrunner.sh7
-rw-r--r--recipes-containers/vcontainer/files/vxn-oci-runtime148
4 files changed, 167 insertions, 40 deletions
diff --git a/recipes-containers/vcontainer/files/vdkr.sh b/recipes-containers/vcontainer/files/vdkr.sh
index 818c831e..29f08877 100755
--- a/recipes-containers/vcontainer/files/vdkr.sh
+++ b/recipes-containers/vcontainer/files/vdkr.sh
@@ -21,5 +21,27 @@ VCONTAINER_STATE_FILE="docker-state.img"
21VCONTAINER_OTHER_PREFIX="VPDMN" 21VCONTAINER_OTHER_PREFIX="VPDMN"
22VCONTAINER_VERSION="3.4.0" 22VCONTAINER_VERSION="3.4.0"
23 23
24# Source common implementation 24# Auto-detect Xen if not explicitly set
25source "$(dirname "${BASH_SOURCE[0]}")/vcontainer-common.sh" "$@" 25if [ -z "${VCONTAINER_HYPERVISOR:-}" ]; then
26 if command -v xl >/dev/null 2>&1; then
27 export VCONTAINER_HYPERVISOR="xen"
28 fi
29fi
30
31# Fall back to vxn blob dir on Dom0
32if [ -z "${VDKR_BLOB_DIR:-}" ] && [ -d "/usr/share/vxn" ]; then
33 export VDKR_BLOB_DIR="/usr/share/vxn"
34fi
35
36# Two-phase lib lookup: script dir (dev), then /usr/lib/vxn (target)
37SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
38if [ -f "${SCRIPT_DIR}/vcontainer-common.sh" ]; then
39 export VCONTAINER_LIBDIR="${SCRIPT_DIR}"
40 source "${SCRIPT_DIR}/vcontainer-common.sh" "$@"
41elif [ -f "/usr/lib/vxn/vcontainer-common.sh" ]; then
42 export VCONTAINER_LIBDIR="/usr/lib/vxn"
43 source "/usr/lib/vxn/vcontainer-common.sh" "$@"
44else
45 echo "Error: vcontainer-common.sh not found" >&2
46 exit 1
47fi
diff --git a/recipes-containers/vcontainer/files/vpdmn.sh b/recipes-containers/vcontainer/files/vpdmn.sh
index 30775d35..6f0f56d8 100755
--- a/recipes-containers/vcontainer/files/vpdmn.sh
+++ b/recipes-containers/vcontainer/files/vpdmn.sh
@@ -21,5 +21,27 @@ VCONTAINER_STATE_FILE="podman-state.img"
21VCONTAINER_OTHER_PREFIX="VDKR" 21VCONTAINER_OTHER_PREFIX="VDKR"
22VCONTAINER_VERSION="1.2.0" 22VCONTAINER_VERSION="1.2.0"
23 23
24# Source common implementation 24# Auto-detect Xen if not explicitly set
25source "$(dirname "${BASH_SOURCE[0]}")/vcontainer-common.sh" "$@" 25if [ -z "${VCONTAINER_HYPERVISOR:-}" ]; then
26 if command -v xl >/dev/null 2>&1; then
27 export VCONTAINER_HYPERVISOR="xen"
28 fi
29fi
30
31# Fall back to vxn blob dir on Dom0
32if [ -z "${VPDMN_BLOB_DIR:-}" ] && [ -d "/usr/share/vxn" ]; then
33 export VPDMN_BLOB_DIR="/usr/share/vxn"
34fi
35
36# Two-phase lib lookup: script dir (dev), then /usr/lib/vxn (target)
37SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
38if [ -f "${SCRIPT_DIR}/vcontainer-common.sh" ]; then
39 export VCONTAINER_LIBDIR="${SCRIPT_DIR}"
40 source "${SCRIPT_DIR}/vcontainer-common.sh" "$@"
41elif [ -f "/usr/lib/vxn/vcontainer-common.sh" ]; then
42 export VCONTAINER_LIBDIR="/usr/lib/vxn"
43 source "/usr/lib/vxn/vcontainer-common.sh" "$@"
44else
45 echo "Error: vcontainer-common.sh not found" >&2
46 exit 1
47fi
diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh
index aaaaeb61..3f0b3448 100755
--- a/recipes-containers/vcontainer/files/vrunner.sh
+++ b/recipes-containers/vcontainer/files/vrunner.sh
@@ -515,6 +515,13 @@ if [ ! -f "$HV_BACKEND" ]; then
515fi 515fi
516source "$HV_BACKEND" 516source "$HV_BACKEND"
517 517
518# Xen backend uses vxn-init.sh which is a unified init (no Docker/Podman
519# daemon in guest). It always parses docker_* kernel parameters regardless
520# of which frontend (vdkr/vpdmn) invoked us.
521if [ "$VCONTAINER_HYPERVISOR" = "xen" ]; then
522 CMDLINE_PREFIX="docker"
523fi
524
518# Daemon mode handling 525# Daemon mode handling
519# Set default socket directory based on architecture 526# Set default socket directory based on architecture
520# If --state-dir was provided, use it for daemon files too 527# If --state-dir was provided, use it for daemon files too
diff --git a/recipes-containers/vcontainer/files/vxn-oci-runtime b/recipes-containers/vcontainer/files/vxn-oci-runtime
index 57144aea..02b94fb5 100644
--- a/recipes-containers/vcontainer/files/vxn-oci-runtime
+++ b/recipes-containers/vcontainer/files/vxn-oci-runtime
@@ -37,18 +37,32 @@ BLOB_DIR="/usr/share/vxn"
37LOG_FILE="/var/log/vxn-oci-runtime.log" 37LOG_FILE="/var/log/vxn-oci-runtime.log"
38VXN_LOG="/var/log/vxn-oci-runtime.log" 38VXN_LOG="/var/log/vxn-oci-runtime.log"
39 39
40# Write a JSON log entry to the shim's --log file (runc-compatible format).
41# containerd-shim-runc-v2 parses this to extract error messages on failure.
42_log_json() {
43 local level="$1" msg="$2" dest="$3"
44 local ts
45 ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "1970-01-01T00:00:00Z")
46 printf '{"level":"%s","msg":"%s","time":"%s"}\n' "$level" "$msg" "$ts" >> "$dest" 2>/dev/null || true
47}
48
40log() { 49log() {
41 local ts 50 local ts
42 ts=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "-") 51 ts=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "-")
43 # Always write to our own log (shim overrides LOG_FILE via --log) 52 # Always write plain text to our own log for human debugging
44 echo "[$ts] $*" >> "$VXN_LOG" 2>/dev/null || true 53 echo "[$ts] $*" >> "$VXN_LOG" 2>/dev/null || true
54 # Write JSON to shim's log file (if different from our log)
45 if [ "$LOG_FILE" != "$VXN_LOG" ]; then 55 if [ "$LOG_FILE" != "$VXN_LOG" ]; then
46 echo "[$ts] $*" >> "$LOG_FILE" 2>/dev/null || true 56 _log_json "info" "$*" "$LOG_FILE"
47 fi 57 fi
48} 58}
49 59
50die() { 60die() {
51 log "FATAL: $*" 61 log "FATAL: $*"
62 # Write JSON error to shim log so Docker can extract the message
63 if [ "$LOG_FILE" != "$VXN_LOG" ]; then
64 _log_json "error" "$*" "$LOG_FILE"
65 fi
52 echo "vxn-oci-runtime: $*" >&2 66 echo "vxn-oci-runtime: $*" >&2
53 exit 1 67 exit 1
54} 68}
@@ -200,10 +214,26 @@ cmd_create() {
200 214
201 log " entrypoint='$entrypoint' cwd='$cwd' terminal=$terminal" 215 log " entrypoint='$entrypoint' cwd='$cwd' terminal=$terminal"
202 216
203 # Create ext4 disk image from bundle/rootfs/ 217 # Read rootfs path from config.json (OCI spec: root.path)
204 local rootfs_dir="$bundle/rootfs" 218 local rootfs_path=""
219 if command -v jq >/dev/null 2>&1; then
220 rootfs_path=$(jq -r '.root.path // "rootfs"' "$config" 2>/dev/null)
221 else
222 rootfs_path=$(grep -o '"path"[[:space:]]*:[[:space:]]*"[^"]*"' "$config" 2>/dev/null | \
223 head -1 | sed 's/.*"path"[[:space:]]*:[[:space:]]*"//;s/"$//')
224 [ -z "$rootfs_path" ] && rootfs_path="rootfs"
225 fi
226 # Resolve relative paths against bundle directory
227 case "$rootfs_path" in
228 /*) ;;
229 *) rootfs_path="$bundle/$rootfs_path" ;;
230 esac
231
232 local rootfs_dir="$rootfs_path"
205 local input_img="$dir/input.img" 233 local input_img="$dir/input.img"
206 234
235 log " rootfs_dir=$rootfs_dir"
236
207 if [ -d "$rootfs_dir" ] && [ -n "$(ls -A "$rootfs_dir" 2>/dev/null)" ]; then 237 if [ -d "$rootfs_dir" ] && [ -n "$(ls -A "$rootfs_dir" 2>/dev/null)" ]; then
208 # Calculate size: rootfs size + 50% headroom, minimum 64MB 238 # Calculate size: rootfs size + 50% headroom, minimum 64MB
209 local rootfs_size_kb 239 local rootfs_size_kb
@@ -213,8 +243,14 @@ cmd_create() {
213 243
214 log " Creating ext4 image: ${img_size_kb}KB from $rootfs_dir" 244 log " Creating ext4 image: ${img_size_kb}KB from $rootfs_dir"
215 mke2fs -t ext4 -d "$rootfs_dir" -b 4096 "$input_img" "${img_size_kb}K" \ 245 mke2fs -t ext4 -d "$rootfs_dir" -b 4096 "$input_img" "${img_size_kb}K" \
216 >> "$LOG_FILE" 2>&1 || die "create: failed to create ext4 image" 246 >> "$VXN_LOG" 2>&1 || die "create: failed to create ext4 image"
217 else 247 else
248 # Diagnostics: log what we actually see
249 log " DIAG: bundle contents: $(ls -la "$bundle/" 2>&1)"
250 log " DIAG: rootfs_dir exists=$([ -d "$rootfs_dir" ] && echo yes || echo no)"
251 log " DIAG: rootfs_dir contents: $(ls -la "$rootfs_dir" 2>&1)"
252 log " DIAG: mounts at bundle: $(mount 2>/dev/null | grep "$(dirname "$bundle")" || echo none)"
253 log " DIAG: config.json root: $(grep -o '"root"[^}]*}' "$config" 2>/dev/null)"
218 die "create: $rootfs_dir is empty or does not exist" 254 die "create: $rootfs_dir is empty or does not exist"
219 fi 255 fi
220 256
@@ -271,7 +307,7 @@ XENEOF
271 log " Xen config written to $config_cfg" 307 log " Xen config written to $config_cfg"
272 308
273 # Create domain in paused state (OCI spec: create does not start) 309 # Create domain in paused state (OCI spec: create does not start)
274 xl create -p "$config_cfg" >> "$LOG_FILE" 2>&1 || die "create: xl create -p failed" 310 xl create -p "$config_cfg" >> "$VXN_LOG" 2>&1 || die "create: xl create -p failed"
275 311
276 log " Domain $domname created (paused)" 312 log " Domain $domname created (paused)"
277 313
@@ -327,33 +363,54 @@ DBGEOF
327 363
328 # Monitor process: tracks domain lifecycle and captures output. 364 # Monitor process: tracks domain lifecycle and captures output.
329 # 365 #
330 # Non-terminal mode: xl console captures the domain's serial output. 366 # The shim monitors the PID written to --pid-file. The monitor MUST stay
331 # When the domain dies, xl console exits (PTY closes). We immediately 367 # alive through the full create→start→run→exit lifecycle. If the monitor
332 # extract content between OUTPUT_START/END markers and write to stdout. 368 # dies before start is called, the shim skips start and goes to cleanup.
333 # stdout is the shim's pipe → containerd copies to client FIFO → ctr. 369 #
370 # Non-terminal mode: we poll xl list to wait for the domain to be
371 # unpaused and to run to completion. Once the domain dies, we attach
372 # xl console to read the console ring buffer, extract OUTPUT_START/END
373 # markers, and relay the output to stdout (the shim's pipe).
334 # 374 #
335 # CRITICAL: We use "wait" on xl console instead of polling xl list. 375 # IMPORTANT: We cannot run xl console on a paused domain — it exits
336 # Polling with sleep 5 was too slow — the shim detected "stopped" and 376 # immediately with no output. Instead we wait for the domain to finish,
337 # killed the monitor before it had a chance to output. Using wait gives 377 # then read the console ring buffer post-mortem via xl console -r (dmesg).
338 # us instant reaction when the domain dies. 378 # However, xl console on a destroyed domain also fails. So we use a
379 # two-phase approach: poll for domain to start running, then attach
380 # xl console which will block until the domain dies.
339 # 381 #
340 # Terminal mode (console-socket): the shim owns the PTY exclusively. 382 # Terminal mode (console-socket): the shim owns the PTY exclusively.
341 # We just wait for the domain to exit without capturing console. 383 # We just wait for the domain to exit without capturing console.
342 local _dn="$domname" _logdir="$logdir" _csock="$console_socket" 384 local _dn="$domname" _logdir="$logdir" _csock="$console_socket"
343 ( 385 (
344 if [ -z "$_csock" ]; then 386 if [ -z "$_csock" ]; then
345 # Non-terminal: capture console to persistent log dir 387 # Non-terminal: stay alive until domain finishes, then capture output.
346 xl console "$_dn" > "$_logdir/console.log" 2>&1 & 388 #
347 _cpid=$! 389 # Phase 1: Wait for domain to exist and be unpaused (start called).
348 390 # The domain is created paused — xl console would exit immediately.
349 # Wait for xl console to exit — domain death closes the PTY, 391 # Poll until it transitions from 'p' (paused) to running, or dies.
350 # which causes xl console to exit immediately. No polling delay. 392 while xl list "$_dn" >/dev/null 2>&1; do
351 wait $_cpid 2>/dev/null 393 # Check if domain is still paused
352 394 local _state
353 # Extract output between markers and write to stdout. 395 _state=$(xl list "$_dn" 2>/dev/null | awk -v dn="$_dn" '$1 == dn {print $5}')
354 # stdout IS the shim's pipe (confirmed: fd1=pipe). The shim's 396 # States: r=running, b=blocked, p=paused, s=shutdown, c=crashed, d=dying
355 # io.Copy goroutine reads from this pipe and writes to the 397 case "$_state" in
356 # containerd client FIFO. ctr reads from the FIFO. 398 p) sleep 0.2; continue ;; # Still paused, keep waiting
399 *) break ;; # Running/blocked/other — proceed
400 esac
401 done
402
403 # Phase 2: Domain is running (or already dead). Attach xl console
404 # to capture serial output. xl console blocks until PTY closes
405 # (domain death), then exits.
406 if xl list "$_dn" >/dev/null 2>&1; then
407 xl console "$_dn" > "$_logdir/console.log" 2>&1 || true
408 fi
409
410 # Phase 3: Extract output between markers and write to stdout.
411 # stdout IS the shim's pipe (fd1=pipe). The shim's io.Copy
412 # goroutine reads from this pipe and writes to the containerd
413 # client FIFO. ctr reads from the FIFO.
357 if [ -f "$_logdir/console.log" ]; then 414 if [ -f "$_logdir/console.log" ]; then
358 _relay=false 415 _relay=false
359 while IFS= read -r _line; do 416 while IFS= read -r _line; do
@@ -409,7 +466,7 @@ cmd_start() {
409 xl list "$domname" >/dev/null 2>&1 || die "start: domain $domname not found" 466 xl list "$domname" >/dev/null 2>&1 || die "start: domain $domname not found"
410 467
411 # Unpause the domain 468 # Unpause the domain
412 xl unpause "$domname" >> "$LOG_FILE" 2>&1 || die "start: xl unpause failed" 469 xl unpause "$domname" >> "$VXN_LOG" 2>&1 || die "start: xl unpause failed"
413 470
414 # Update state 471 # Update state
415 local pid bundle created 472 local pid bundle created
@@ -462,11 +519,30 @@ EOF
462} 519}
463 520
464cmd_kill() { 521cmd_kill() {
465 local container_id="$1" 522 local container_id=""
466 local signal="${2:-SIGTERM}" 523 local signal="SIGTERM"
524 local kill_all=false
525
526 # Parse arguments: runc accepts `kill [flags] <container-id> [signal]`
527 # Docker sends: kill --all <container-id> <signal>
528 while [ $# -gt 0 ]; do
529 case "$1" in
530 --all|-a) kill_all=true; shift ;;
531 -*) shift ;; # skip unknown flags
532 *)
533 if [ -z "$container_id" ]; then
534 container_id="$1"
535 else
536 signal="$1"
537 fi
538 shift
539 ;;
540 esac
541 done
542
467 [ -n "$container_id" ] || die "kill: container ID required" 543 [ -n "$container_id" ] || die "kill: container ID required"
468 544
469 log "KILL: id=$container_id signal=$signal" 545 log "KILL: id=$container_id signal=$signal all=$kill_all"
470 load_state "$container_id" 546 load_state "$container_id"
471 547
472 local dir 548 local dir
@@ -477,24 +553,24 @@ cmd_kill() {
477 # Normalize signal: accept both numeric and symbolic forms 553 # Normalize signal: accept both numeric and symbolic forms
478 case "$signal" in 554 case "$signal" in
479 9|SIGKILL|KILL) 555 9|SIGKILL|KILL)
480 xl destroy "$domname" >> "$LOG_FILE" 2>&1 || true 556 xl destroy "$domname" >> "$VXN_LOG" 2>&1 || true
481 ;; 557 ;;
482 2|SIGINT|INT) 558 2|SIGINT|INT)
483 xl destroy "$domname" >> "$LOG_FILE" 2>&1 || true 559 xl destroy "$domname" >> "$VXN_LOG" 2>&1 || true
484 ;; 560 ;;
485 15|SIGTERM|TERM|"") 561 15|SIGTERM|TERM|"")
486 xl shutdown "$domname" >> "$LOG_FILE" 2>&1 || true 562 xl shutdown "$domname" >> "$VXN_LOG" 2>&1 || true
487 # Wait briefly for graceful shutdown, then force destroy 563 # Wait briefly for graceful shutdown, then force destroy
488 local i 564 local i
489 for i in 1 2 3 4 5 6 7 8 9 10; do 565 for i in 1 2 3 4 5 6 7 8 9 10; do
490 xl list "$domname" >/dev/null 2>&1 || break 566 xl list "$domname" >/dev/null 2>&1 || break
491 sleep 1 567 sleep 1
492 done 568 done
493 xl destroy "$domname" >> "$LOG_FILE" 2>&1 || true 569 xl destroy "$domname" >> "$VXN_LOG" 2>&1 || true
494 ;; 570 ;;
495 *) 571 *)
496 # Unknown signal — treat as SIGTERM 572 # Unknown signal — treat as SIGTERM
497 xl shutdown "$domname" >> "$LOG_FILE" 2>&1 || true 573 xl shutdown "$domname" >> "$VXN_LOG" 2>&1 || true
498 ;; 574 ;;
499 esac 575 esac
500 576
@@ -542,7 +618,7 @@ cmd_delete() {
542 local domname 618 local domname
543 domname=$(cat "$dir/domname") 619 domname=$(cat "$dir/domname")
544 if xl list "$domname" >/dev/null 2>&1; then 620 if xl list "$domname" >/dev/null 2>&1; then
545 xl destroy "$domname" >> "$LOG_FILE" 2>&1 || true 621 xl destroy "$domname" >> "$VXN_LOG" 2>&1 || true
546 fi 622 fi
547 fi 623 fi
548 624