Reuse -v/--volume to mount named/host volumes into -r/--run

Volume names now reject '/', which lets a -v spec used with -r be told
apart as either an existing named volume or a host directory path. -v
becomes repeatable with -r, each mounting a volume at an absolute
container path; if the host directory is empty and the image already
has content there, it's copied in first (preserving numeric
ownership/permissions/links/xattrs-ACLs, degrading gracefully with a
warning if the host filesystem doesn't support xattrs). The
existence-check and copy run through the same nsenter-wrapped
namespace bwrap itself needs, since a rootless containers-storage
mount's content isn't otherwise visible to this process at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-21 17:08:16 +00:00
parent d69b408d14
commit 545762d6de
8 changed files with 397 additions and 57 deletions
+23 -5
View File
@@ -20,6 +20,8 @@
#include <string>
#include <vector>
#include "volume_mount.h"
// Probes the running kernel for which Linux namespace types can actually be
// unshared and returns the corresponding bwrap --unshare-xxx flags for the
// ones that are supported. Intended for kernels with partial namespace
@@ -27,6 +29,19 @@
// --unshare-xxx flag to bwrap would make it fail outright.
std::vector<std::string> detect_bwrap_unshare_args();
// Wraps `argv` so it runs inside the mount+user namespace that containers-storage's
// rootless fuse-overlayfs daemon created for `root` (the merged mount path from
// mount_layer()), via nsenter -- needed to reach anything under `root` at all from a
// plain process outside that namespace (containers-storage mount reexecs itself
// into a private namespace to get the privilege an unprivileged overlay mount
// needs; only nsenter targeting that live daemon's PID can rejoin it). If
// use_nsenter is false, returns argv unchanged (the mount is already directly
// visible, e.g. when running as root -- see run_container() in main.cpp). Returns
// nullopt (and logs) if the fuse-overlayfs process or nsenter itself can't be
// found.
std::optional<std::vector<std::string>> wrap_for_root_namespace(const std::string& root, bool use_nsenter,
const std::vector<std::string>& argv);
struct ResolvedUser {
int uid;
int gid;
@@ -34,12 +49,15 @@ struct ResolvedUser {
// Assembles the full bwrap argv (program name included) to run `command` with
// `root` bound as the sandbox's filesystem root, using whichever --unshare-xxx
// flags the kernel supports (see detect_bwrap_unshare_args()). If `user` is set,
// the command is wrapped so it drops to that uid/gid before running -- see
// run_bwrap() for how, since bwrap's own --uid/--gid require --unshare-user, which
// isn't requested when running as root (see detect_bwrap_unshare_args()).
// flags the kernel supports (see detect_bwrap_unshare_args()). Each entry in
// `volumes` is bound writably at its container_path (see resolve_volume_mount()
// in volume_mount.h). If `user` is set, the command is wrapped so it drops to that
// uid/gid before running -- see run_bwrap() for how, since bwrap's own --uid/--gid
// require --unshare-user, which isn't requested when running as root (see
// detect_bwrap_unshare_args()).
std::vector<std::string> build_bwrap_args(const std::string& root,
const std::vector<std::string>& command,
const std::vector<ResolvedVolumeMount>& volumes,
std::optional<ResolvedUser> user);
// Runs bwrap against `root` (the merged mount path from mount_layer()) in the
@@ -54,4 +72,4 @@ std::vector<std::string> build_bwrap_args(const std::string& root,
// used to drop privileges to that uid/gid before running `command` -- see
// build_bwrap_args(). Returns bwrap's exit code, or -1 on failure to launch.
int run_bwrap(const std::string& root, const std::vector<std::string>& command, bool use_nsenter,
std::optional<ResolvedUser> user);
const std::vector<ResolvedVolumeMount>& volumes, std::optional<ResolvedUser> user);