Add -r/--run and -c/--cleanup, fix nsenter under root
-r/--run mounts an image, runs a command under bwrap in the foreground (default /bin/sh, overridable via -- <command> [args...]), then unmounts and cleans up when it exits. -c/--cleanup deletes a layer and its ancestor chain from local storage (containers-storage delete-layer, walking parents via `layer --json`), since -u only ever unmounted. bwrap needs to see the merged mount from inside the private namespace containers-storage mount creates when running rootless; run_bwrap() locates the live fuse-overlayfs process and runs bwrap via nsenter into its namespaces. When running as root no such namespace exists (containers-storage doesn't need to reexec for privilege), so nsenter fails with EINVAL; detect geteuid() == 0 and skip it automatically there. -n/--no-nsenter forces it off manually for any other case. process.cpp gains run_process_foreground() (inherited stdio, for the interactive bwrap run) and the relocated find_in_path(), now shared with bwrap.cpp's nsenter lookup. Also: meson test only ran -m, leaking a layer on every run; it now runs tests/run_test.py, which drives mount -> umount -> cleanup and fails if any step does. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+130
@@ -17,13 +17,23 @@
|
||||
#include "bwrap.h"
|
||||
|
||||
#include <sched.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "process.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct NamespaceProbe {
|
||||
@@ -56,6 +66,49 @@ bool kernel_supports_namespace(int clone_flag) {
|
||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
}
|
||||
|
||||
// Scans /proc for a fuse-overlayfs process whose command line references
|
||||
// `merged_path`, mirroring `ps aux | grep fuse-overlayfs`. /proc is inherently racy
|
||||
// (processes come and go while it's being scanned), so filesystem errors from a
|
||||
// vanished entry are treated as "not this one" rather than propagated.
|
||||
std::optional<pid_t> find_fuse_overlayfs_pid(const std::string& merged_path) {
|
||||
std::error_code ec;
|
||||
auto it = std::filesystem::directory_iterator("/proc", ec);
|
||||
if (ec) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const auto& entry : it) {
|
||||
const std::string name = entry.path().filename().string();
|
||||
if (!std::all_of(name.begin(), name.end(),
|
||||
[](unsigned char c) { return std::isdigit(c); })) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::error_code exe_ec;
|
||||
auto exe_target = std::filesystem::read_symlink(entry.path() / "exe", exe_ec);
|
||||
if (exe_ec || exe_target.filename() != "fuse-overlayfs") {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::ifstream cmdline_file(entry.path() / "cmdline", std::ios::binary);
|
||||
std::string cmdline((std::istreambuf_iterator<char>(cmdline_file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
size_t start = 0;
|
||||
while (start < cmdline.size()) {
|
||||
size_t end = cmdline.find('\0', start);
|
||||
if (end == std::string::npos) {
|
||||
end = cmdline.size();
|
||||
}
|
||||
if (cmdline.compare(start, end - start, merged_path) == 0) {
|
||||
return static_cast<pid_t>(std::stoi(name));
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> detect_bwrap_unshare_args() {
|
||||
@@ -69,3 +122,80 @@ std::vector<std::string> detect_bwrap_unshare_args() {
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
std::vector<std::string> build_bwrap_args(const std::string& root,
|
||||
const std::vector<std::string>& command) {
|
||||
std::vector<std::string> args = {"bwrap", "--die-with-parent", "--new-session"};
|
||||
|
||||
auto unshare_args = detect_bwrap_unshare_args();
|
||||
bool has_pid_ns = false;
|
||||
for (const auto& arg : unshare_args) {
|
||||
args.push_back(arg);
|
||||
if (arg == "--unshare-pid") {
|
||||
has_pid_ns = true;
|
||||
}
|
||||
}
|
||||
if (has_pid_ns) {
|
||||
args.push_back("--as-pid-1");
|
||||
}
|
||||
|
||||
std::vector<std::string> filesystem_args = {
|
||||
"--bind",
|
||||
root,
|
||||
"/",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--perms",
|
||||
"01777",
|
||||
"--tmpfs",
|
||||
"/dev/shm",
|
||||
"--perms",
|
||||
"01777",
|
||||
"--tmpfs",
|
||||
"/tmp",
|
||||
"--chdir",
|
||||
"/",
|
||||
"--clearenv",
|
||||
"--setenv",
|
||||
"PATH",
|
||||
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"--setenv",
|
||||
"HOME",
|
||||
"/root",
|
||||
};
|
||||
args.insert(args.end(), filesystem_args.begin(), filesystem_args.end());
|
||||
|
||||
if (const char* term = std::getenv("TERM")) {
|
||||
args.insert(args.end(), {"--setenv", "TERM", term});
|
||||
}
|
||||
|
||||
args.push_back("--");
|
||||
args.insert(args.end(), command.begin(), command.end());
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
int run_bwrap(const std::string& root, const std::vector<std::string>& command, bool use_nsenter) {
|
||||
std::vector<std::string> argv;
|
||||
|
||||
if (use_nsenter) {
|
||||
auto pid = find_fuse_overlayfs_pid(root);
|
||||
if (!pid) {
|
||||
spdlog::error("could not find the fuse-overlayfs process serving {}", root);
|
||||
return -1;
|
||||
}
|
||||
if (!find_in_path("nsenter")) {
|
||||
spdlog::error("nsenter not found in PATH");
|
||||
return -1;
|
||||
}
|
||||
argv = {"nsenter", fmt::format("--user=/proc/{}/ns/user", *pid),
|
||||
fmt::format("--mount=/proc/{}/ns/mnt", *pid), "--"};
|
||||
}
|
||||
|
||||
auto bwrap_args = build_bwrap_args(root, command);
|
||||
argv.insert(argv.end(), bwrap_args.begin(), bwrap_args.end());
|
||||
|
||||
return run_process_foreground(argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user