diff --git a/.gitignore b/.gitignore index 2f2ab20..c6837b9 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ compel/include/asm include/common/asm include/common/config.h build/** +scripts/criu-move-mount diff --git a/scripts/criu-move-mount.c b/scripts/criu-move-mount.c new file mode 100644 index 0000000..63ea19c --- /dev/null +++ b/scripts/criu-move-mount.c @@ -0,0 +1,325 @@ +/* + * criu-move-mount — CRIU action-script helper: add bind mounts during restore. + * + * Reads mount rules from the CRIU_ADD_MOUNTS environment variable and + * mounts host directories into the container's mount namespace. + * + * This program is designed to be called (via a thin wrapper script) as a + * CRIU action-script. It only acts during the "pre-resume" phase — after + * the mount tree has been fully restored but before container processes + * are resumed. + * + * Environment variables (set by CRIU or the caller): + * CRTOOLS_SCRIPT_ACTION CRIU action phase; only "pre-resume" is handled. + * CRTOOLS_INIT_PID Container init process PID on the host. + * CRIU_ADD_MOUNTS Semicolon-separated mount rules. + * Rule format: src=,dst=[,options=rbind:ro] + * Example: + * src=/home/b,dst=/home/b,options=rbind:rw;src=/data,dst=/data,options=rbind:ro + * + * CRIU_MOVE_MOUNT_LOG (optional) Path to a debug log file. + * Default: /tmp/criu-move-mount.log + * Set to "" to disable file logging. + * + * Mount mechanism (requires Linux 5.2+): + * 1. open_tree(src, OPEN_TREE_CLONE) — clone the source mount in host ns + * 2. setns(container_mntns) — switch to the container's mnt ns + * 3. move_mount(treefd -> dst) — attach the clone inside the container + * 4. (optional) remount read-only + * + * Compile: gcc -O2 -o criu-move-mount criu-move-mount.c + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif + +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif + +#define DEFAULT_LOG_PATH "/tmp/criu-move-mount.log" + +static FILE *g_logfp; + +static void log_open(void) +{ + const char *path = getenv("CRIU_MOVE_MOUNT_LOG"); + + if (!path) + path = DEFAULT_LOG_PATH; + if (!*path) + return; + + g_logfp = fopen(path, "a"); +} + +static void log_close(void) +{ + if (g_logfp) + fclose(g_logfp); +} + +__attribute__((format(printf, 1, 2))) +static void logmsg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (g_logfp) { + va_start(ap, fmt); + vfprintf(g_logfp, fmt, ap); + va_end(ap); + fflush(g_logfp); + } +} + +/* Recursively create directories (like mkdir -p). */ +static int mkdir_p(const char *path, mode_t mode) +{ + char tmp[4096], *p; + + snprintf(tmp, sizeof(tmp), "%s", path); + for (p = tmp + 1; *p; p++) { + if (*p != '/') + continue; + *p = '\0'; + if (mkdir(tmp, mode) && errno != EEXIST) + return -1; + *p = '/'; + } + return (mkdir(tmp, mode) && errno != EEXIST) ? -1 : 0; +} + +/* + * Perform a single cross-namespace bind mount. + * + * The caller must be in the host mount namespace on entry. + * On return the process is left in the container mount namespace. + */ +static int apply_mount(int host_nsfd, int container_nsfd, + const char *src, const char *dst, + int rbind, int ro) +{ + unsigned int flags; + int treefd; + + logmsg("add-mounts: mounting %s -> %s (rbind=%d, ro=%d)\n", + src, dst, rbind, ro); + + /* Make sure we are in the host namespace for open_tree(). */ + if (setns(host_nsfd, CLONE_NEWNS) < 0) { + logmsg("add-mounts: setns host: %s\n", strerror(errno)); + return -1; + } + + flags = AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE; + if (rbind) + flags |= AT_RECURSIVE; + + treefd = syscall(__NR_open_tree, AT_FDCWD, src, flags); + if (treefd < 0) { + logmsg("add-mounts: open_tree(%s): %s\n", src, strerror(errno)); + return -1; + } + + /* Switch to the container namespace. */ + if (setns(container_nsfd, CLONE_NEWNS) < 0) { + logmsg("add-mounts: setns container: %s\n", strerror(errno)); + close(treefd); + return -1; + } + + if (mkdir_p(dst, 0755) < 0) { + logmsg("add-mounts: mkdir_p(%s): %s\n", dst, strerror(errno)); + close(treefd); + return -1; + } + + if (syscall(__NR_move_mount, treefd, "", AT_FDCWD, dst, + MOVE_MOUNT_F_EMPTY_PATH) < 0) { + logmsg("add-mounts: move_mount(%s): %s\n", dst, strerror(errno)); + close(treefd); + return -1; + } + + if (ro && mount(NULL, dst, NULL, + MS_REMOUNT | MS_BIND | MS_RDONLY, NULL) < 0) { + logmsg("add-mounts: remount ro(%s): %s\n", dst, strerror(errno)); + close(treefd); + return -1; + } + + logmsg("add-mounts: OK %s -> %s\n", src, dst); + close(treefd); + return 0; +} + +/* + * Parse a single mount-rule string and call apply_mount(). + * Rule format: [type=bind,]src=,dst=[,options=rbind:rw] + * Returns 0 on success, -1 on failure. + */ +static int parse_and_apply(int host_nsfd, int container_nsfd, const char *spec) +{ + char *buf, *saveptr, *field; + char *src = NULL, *dst = NULL; + int rbind = 0, ro = 0; + + buf = strdup(spec); + if (!buf) { + perror("strdup"); + return -1; + } + + for (field = strtok_r(buf, ",", &saveptr); field; + field = strtok_r(NULL, ",", &saveptr)) { + if (strncmp(field, "src=", 4) == 0) { + src = field + 4; + } else if (strncmp(field, "dst=", 4) == 0) { + dst = field + 4; + } else if (strncmp(field, "type=", 5) == 0) { + /* ignored — only bind is supported */ + } else if (strncmp(field, "options=", 8) == 0) { + char *opts = field + 8; + char *osave, *opt; + + for (opt = strtok_r(opts, ":", &osave); opt; + opt = strtok_r(NULL, ":", &osave)) { + if (strcmp(opt, "rbind") == 0) + rbind = 1; + else if (strcmp(opt, "ro") == 0) + ro = 1; + } + } + } + + if (!src || !*src || !dst || !*dst) { + logmsg("add-mounts: bad spec (missing src or dst): %s\n", spec); + free(buf); + return -1; + } + + int rc = apply_mount(host_nsfd, container_nsfd, src, dst, rbind, ro); + + free(buf); + return rc; +} + +/* Strip leading and trailing whitespace in-place. */ +static char *trim(char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + if (*s) { + char *e = s + strlen(s) - 1; + while (e > s && (*e == ' ' || *e == '\t')) + *e-- = '\0'; + } + return s; +} + +int main(void) +{ + const char *action, *initpid_str, *mounts_env; + char ns_path[64]; + int host_nsfd, container_nsfd; + char *mounts, *saveptr, *entry; + int initpid; + + log_open(); + + action = getenv("CRTOOLS_SCRIPT_ACTION"); + logmsg("add-mounts: action=%s\n", action ? action : "(null)"); + + /* + * Phase filter — only run during pre-resume. + * Exit silently (success) for all other phases so CRIU continues + * the restore without error. + */ + if (!action || strcmp(action, "pre-resume") != 0) { + log_close(); + return 0; + } + + initpid_str = getenv("CRTOOLS_INIT_PID"); + if (!initpid_str || !*initpid_str) { + logmsg("add-mounts: CRTOOLS_INIT_PID not set\n"); + log_close(); + return 1; + } + initpid = atoi(initpid_str); + + mounts_env = getenv("CRIU_ADD_MOUNTS"); + logmsg("add-mounts: pid=%d, CRIU_ADD_MOUNTS=%s\n", + initpid, mounts_env ? mounts_env : "(null)"); + + if (!mounts_env || !*mounts_env) { + logmsg("add-mounts: no rules, nothing to do\n"); + log_close(); + return 0; + } + + /* Open the container's mount namespace fd. */ + snprintf(ns_path, sizeof(ns_path), "/proc/%d/ns/mnt", initpid); + container_nsfd = open(ns_path, O_RDONLY); + if (container_nsfd < 0) { + logmsg("add-mounts: open(%s): %s\n", ns_path, strerror(errno)); + log_close(); + return 1; + } + + /* Save the host mount namespace so we can switch back for each rule. */ + host_nsfd = open("/proc/self/ns/mnt", O_RDONLY); + if (host_nsfd < 0) { + logmsg("add-mounts: open(/proc/self/ns/mnt): %s\n", + strerror(errno)); + close(container_nsfd); + log_close(); + return 1; + } + + mounts = strdup(mounts_env); + if (!mounts) { + logmsg("add-mounts: strdup: %s\n", strerror(errno)); + close(container_nsfd); + close(host_nsfd); + log_close(); + return 1; + } + + /* + * Individual mount failures are logged but do not abort the whole + * process — matching the original shell script's "|| true" behaviour. + * This prevents one bad rule from blocking other valid mounts and + * avoids failing the entire CRIU restore. + */ + for (entry = strtok_r(mounts, ";", &saveptr); entry; + entry = strtok_r(NULL, ";", &saveptr)) { + entry = trim(entry); + if (!*entry) + continue; + parse_and_apply(host_nsfd, container_nsfd, entry); + } + + free(mounts); + close(container_nsfd); + close(host_nsfd); + log_close(); + return 0; +} diff --git a/scripts/runc-action-add-mounts.sh b/scripts/runc-action-add-mounts.sh new file mode 100755 index 0000000..b6a4a17 --- /dev/null +++ b/scripts/runc-action-add-mounts.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# runc-action-add-mounts.sh — CRIU action-script wrapper for criu-move-mount. +# +# Usage: +# criu restore --action-script /path/to/runc-action-add-mounts.sh +# +# This is a thin wrapper that locates and executes the criu-move-mount binary. +# All logic (phase filtering, mount-rule parsing, namespace operations) is +# handled by criu-move-mount itself. +# +# The binary is located in this order: +# 1. $CRIU_MOVE_MOUNT_BIN (explicit override) +# 2. Same directory as this script +# 3. /usr/local/bin/criu-move-mount +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +bin="${CRIU_MOVE_MOUNT_BIN:-}" + +if [ -z "$bin" ]; then + for p in "$SCRIPT_DIR/criu-move-mount" /usr/local/bin/criu-move-mount; do + [ -x "$p" ] && { bin="$p"; break; } + done +fi + +[ -x "$bin" ] || { echo "add-mounts: criu-move-mount not found" >&2; exit 1; } + +exec "$bin"