Change -e/--exec's short option to -x

-e was going to collide with the shape of other single-letter destructive-
ish operational flags; -x frees up the letter and reads fine as
--exec's short form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-29 10:02:31 +00:00
parent 8c4afb82e8
commit 28332ac5c2
7 changed files with 26 additions and 26 deletions
+9 -9
View File
@@ -35,12 +35,12 @@ Source layout (all under `src/`):
the top of `main()`: mode/`--volume` interaction (`-v` alone vs. combined
with `-r`, see below), `--group` requires `--user`, no leftover positional
args outside `-r`/`-e`, and (moved here from what used to be inline in the
`Mode::exec` dispatch arm) `-e/--exec <pid>`'s own pid parsing/validation
`Mode::exec` dispatch arm) `-x/--exec <pid>`'s own pid parsing/validation
(`ParsedArgs::exec_pid`, a positive integer or a hard error) and its
trailing-command requirement (`ParsedArgs::command`, required non-empty).
`-k/--kill <pid>` (`ParsedArgs::kill_pid`) shares that same positive-integer
parsing/validation via a small extracted `parse_pid_arg()` helper (`.cpp`-local)
rather than duplicating the `strtol` dance a second time — unlike `-e/--exec`,
rather than duplicating the `strtol` dance a second time — unlike `-x/--exec`,
it takes no trailing command, so it's simply not added to the leftover-args
exemption list (`Mode::run`/`Mode::exec` only). Returns an exit code `main()`
should return immediately (`0` for `-h`/`-V`,
@@ -283,9 +283,9 @@ Source layout (all under `src/`):
`find_priv_drop_helper()` and the `priv_drop::path`/`priv_drop::helper_name`
constants live in `bwrap.h` (not just internal to `bwrap.cpp`) specifically so
`exec_in_session()` (`exec_session.cpp`, see below) can reuse the exact same
already-bind-mounted helper for `-e/--exec`'s own `--user`/`--group` support,
already-bind-mounted helper for `-x/--exec`'s own `--user`/`--group` support,
instead of a second copy needing to be bind-mounted for it (which wouldn't even
be possible — `-e/--exec` joins an *already-running* session's mount namespace,
be possible — `-x/--exec` joins an *already-running* session's mount namespace,
it doesn't get to add bind mounts to it). `find_priv_drop_helper()` itself only
checks this binary's own host-side existence; it says nothing about whether a
given session actually has it bind-mounted (only true when that session's
@@ -503,7 +503,7 @@ Source layout (all under `src/`):
pattern for "there's only ever one of these per process" runtime state),
since `report_daemon_started()` is called later, from a different function,
not threaded explicitly through every call in between.
- `exec_session.{h,cpp}` — implements `-e/--exec <pid>`: joins an already-running
- `exec_session.{h,cpp}` — implements `-x/--exec <pid>`: joins an already-running
`-r/--run` session's namespaces via `nsenter` and runs a command inside it in
the foreground. `exec_in_session()` first confirms `pid` is a tracked, running
session via `list_sessions()` (`pid_file.h`) — same liveness check
@@ -546,12 +546,12 @@ Source layout (all under `src/`):
`user_spec.h`, see below); if unset, defaults to whatever uid/gid the session's
own sandboxed command is *already* running as (read from `/proc/<ns_pid>/status`),
rather than root/the caller — fixing a real bug (reported after this project's
own `-e/--exec` and priv-drop features had both shipped separately): without this,
`-e/--exec` always ran as whatever the *host* invocation was, ignoring any
own `-x/--exec` and priv-drop features had both shipped separately): without this,
`-x/--exec` always ran as whatever the *host* invocation was, ignoring any
`--user`/`--group` the session itself was started with. Either way, the resolved
identity is applied by running `command` through the session's already
bind-mounted `slocker-lite-priv-drop` helper (`priv_drop::path`, `bwrap.h`) —
reused as-is, not bind-mounted again (`-e/--exec` can't add bind mounts to an
reused as-is, not bind-mounted again (`-x/--exec` can't add bind mounts to an
already-running session's namespace anyway). Skipped entirely when the resolved
uid *and* gid are both 0: a session that was never given a resolvable user at
`-r/--run` time never got the helper bind-mounted at all, and dropping to 0:0
@@ -778,7 +778,7 @@ Build directory is `buildDir/` (already configured).
(see `priv_drop_helper.cpp` in "Project state")
- Run the executable: `./buildDir/slocker-lite -m <image.tar>` (see `--help` for the
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
`-l/--list-images`, `-i/--inspect`, `-e/--exec`, `-k/--kill`, `-n/--no-nsenter`, `-D/--daemonize`,
`-l/--list-images`, `-i/--inspect`, `-x/--exec`, `-k/--kill`, `-n/--no-nsenter`, `-D/--daemonize`,
`--user`, `--group`, `--hostname`, `--env`, `--env-file`, `-v/--volume`, `--list-volumes`, `--delete-volume`,
`--delete-volume-full`, `--list-processes`, `--clean-processes`, `-t/--test`, `--log-level`,
`-h/--help`, `-V/--version`)
+5 -5
View File
@@ -58,7 +58,7 @@ slocker-lite -u|--umount <layer-id>
slocker-lite -c|--cleanup <layer-id>
slocker-lite -l|--list-images <directory>
slocker-lite -i|--inspect <image.tar>
slocker-lite -e|--exec <pid> [-- <command> [args...]]
slocker-lite -x|--exec <pid> [-- <command> [args...]]
slocker-lite -k|--kill <pid>
slocker-lite -v|--volume <name> <directory>
slocker-lite --list-volumes
@@ -78,7 +78,7 @@ slocker-lite -V|--version
| `-u, --umount <layer-id>` | Unmount a previously mounted layer (the ID printed by `--mount`/`--run`, or from `containers-storage layers`). |
| `-c, --cleanup <layer-id>` | Delete a layer and its ancestor chain from local storage (unmount it first). |
| `-n, --no-nsenter` | With `--run`, bind the mount directly instead of `nsenter`-ing into `fuse-overlayfs`'s namespace. Automatic when running as root; use this to force it off otherwise. |
| `-D, --daemonize` | With `--run`, fork into the background: detaches from the controlling terminal (`setsid()`), ignores `SIGHUP`, and redirects stdin from `/dev/null` and stdout/stderr to a log file under `$XDG_STATE_HOME/slocker-lite/logs/`. Prints the session's pid and log path, then returns — the same pid `--list-processes`/`-e/--exec` use. |
| `-D, --daemonize` | With `--run`, fork into the background: detaches from the controlling terminal (`setsid()`), ignores `SIGHUP`, and redirects stdin from `/dev/null` and stdout/stderr to a log file under `$XDG_STATE_HOME/slocker-lite/logs/`. Prints the session's pid and log path, then returns — the same pid `--list-processes`/`-x/--exec` use. |
| `--user <user>` | With `--run`, run the command as this user (name or numeric uid) instead of the image's own declared user (or root, if it declares none). Resolved against the image's own `/etc/passwd`. Only takes effect when `--run` executes as root. |
| `--group <group>` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. |
| `--hostname <name>` | With `--run`, set the sandbox's hostname. Only takes effect if the running kernel supports `--unshare-uts`; ignored with a warning otherwise. |
@@ -86,7 +86,7 @@ slocker-lite -V|--version
| `--env-file <file>` | With `--run`, load environment variables from `<file>` — one `VAR=VALUE` per line; blank lines and `#`-comments are skipped. Repeatable. |
| `-l, --list-images <dir>` | List OCI Image Layout tars (`*.tar`, `*.tar.*`) found directly in `<dir>`, with their `name:tag`. |
| `-i, --inspect <image.tar>` | Print an image's declared user, exposed ports, env, volumes, and default command, without mounting or running it. |
| `-e, --exec <pid>` | Join an already-running `--run` session (`<pid>` must be one `--list-processes` shows as `running`) and run a command inside its container. Pass `-- <command> [args...]` to specify it. |
| `-x, --exec <pid>` | Join an already-running `--run` session (`<pid>` must be one `--list-processes` shows as `running`) and run a command inside its container. Pass `-- <command> [args...]` to specify it. |
| `-k, --kill <pid>` | Stop a running `--run` session (`<pid>` must be one `--list-processes` shows as `running`): sends `SIGTERM`, waits up to 10s, then forces it with `SIGKILL`. Reaches every process the session started — including daemonized/reparented ones a plain `kill <pid>` would leave behind — via a dedicated cgroup when available, or the sandboxed pid namespace's own collapse-on-kill guarantee when not, falling back to signaling the tracked pid alone if neither applies. |
| `-v, --volume <name> <dir>` | Create a named volume mapped to a host directory (created if missing), recorded in the config file's `volumes` section. Fails if the name or directory is already used by an existing volume. Volume names can't contain `/`. With `--run`, instead mounts a volume into the sandbox (repeatable): `<name>` is an existing named volume, or, if it contains `/`, a host directory path (created if missing); `<dir>` is the absolute path inside the container to mount it at. If the host directory is empty and the image already has content there, that content is copied in first, preserving numeric ownership/permissions/links and, where the host filesystem supports them, extended attributes/ACLs (skipped with a warning otherwise). |
| `--list-volumes` | List all named volumes (see `-v/--volume`) with their host directory. |
@@ -206,7 +206,7 @@ when its own session ends, but `--clean-processes` removes any stale ones left
behind (e.g. after a crash) using that same check, atomically per file, so it
never removes one that's still genuinely running.
`-e/--exec <pid>` joins a running session's namespaces with `nsenter` and runs a
`-x/--exec <pid>` joins a running session's namespaces with `nsenter` and runs a
command there. Because `bwrap` itself sets up the sandbox's mount/user namespaces
and then hands the actual sandboxed command off to a child process in fresh
pid/uts/ipc/cgroup namespaces, `-e` resolves that real child first (via `/proc`)
@@ -239,7 +239,7 @@ at once (`exec()` never changes session membership), and correctly scopes
`bwrap`'s own `--die-with-parent` to that child. The child ignores `SIGHUP` and
redirects output to a log file before doing anything else; the original,
still-foreground process waits only long enough to learn the real session pid
(the same one `--list-processes`/`-e/--exec` use) before printing it and
(the same one `--list-processes`/`-x/--exec` use) before printing it and
returning — the detached child is what runs the entire session afterward,
including the same unmount/cleanup that always ran once the sandboxed command
exits.
+1 -1
View File
@@ -31,7 +31,7 @@ namespace priv_drop {
// Hidden path inside the sandbox where the priv-drop helper binary is bind-mounted
// by build_bwrap_args() whenever a --user/--group was resolved for that session.
// Exported (not just internal to bwrap.cpp) so exec_in_session() (exec_session.cpp)
// can reuse that already-bind-mounted helper for -e/--exec's own --user/--group
// can reuse that already-bind-mounted helper for -x/--exec's own --user/--group
// support, rather than needing a second copy bind-mounted for it.
constexpr const char* path = "/.slocker-lite-priv-drop";
+8 -8
View File
@@ -65,7 +65,7 @@ constexpr std::array<struct option, 26> long_options = {{
{"delete-volume", required_argument, nullptr, options::delete_volume},
{"delete-volume-full", required_argument, nullptr, options::delete_volume_full},
{"inspect", required_argument, nullptr, 'i'},
{"exec", required_argument, nullptr, 'e'},
{"exec", required_argument, nullptr, 'x'},
{"kill", required_argument, nullptr, 'k'},
{"hostname", required_argument, nullptr, options::hostname},
{"list-processes", no_argument, nullptr, options::list_processes},
@@ -83,7 +83,7 @@ void print_usage(const char* prog) {
" {0} -c|--cleanup <layer-id>\n"
" {0} -l|--list-images <directory>\n"
" {0} -i|--inspect <image.tar>\n"
" {0} -e|--exec <pid> [-- <command> [args...]]\n"
" {0} -x|--exec <pid> [-- <command> [args...]]\n"
" {0} -k|--kill <pid>\n"
" {0} -v|--volume <name> <directory>\n"
" {0} --list-volumes\n"
@@ -119,7 +119,7 @@ void print_usage(const char* prog) {
" $XDG_STATE_HOME/slocker-lite/logs/. Prints the\n"
" session's pid and log path, then returns\n"
" immediately -- the same pid --list-processes/\n"
" -e/--exec use\n"
" -x/--exec use\n"
" --user <user> with --run, run the command as this user (name\n"
" or numeric uid) instead of the image's own\n"
" declared user (or root, if it declares none),\n"
@@ -149,7 +149,7 @@ void print_usage(const char* prog) {
" -i, --inspect <image.tar> print an image's declared user, exposed ports,\n"
" env, volumes, and default command, without\n"
" mounting or running it\n"
" -e, --exec <pid> join a running --run session (pid must be one\n"
" -x, --exec <pid> join a running --run session (pid must be one\n"
" shown by --list-processes as \"running\") and run\n"
" a command inside its container; pass\n"
" -- <command> [args...] to specify it\n"
@@ -205,7 +205,7 @@ void print_version() {
PACKAGE, VERSION);
}
// Shared by -e/--exec's exec_pid and -k/--kill's kill_pid -- both are a bare
// Shared by -x/--exec's exec_pid and -k/--kill's kill_pid -- both are a bare
// positive integer, nothing else.
std::optional<pid_t> parse_pid_arg(const std::string& text) {
char* end = nullptr;
@@ -234,7 +234,7 @@ bool apply_log_level(std::string_view name) {
std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
opterr = 0;
int opt;
while ((opt = getopt_long(argc, argv, ":hVtm:u:r:c:nl:v:i:e:Dk:", long_options.data(), nullptr)) != -1) {
while ((opt = getopt_long(argc, argv, ":hVtm:u:r:c:nl:v:i:x:Dk:", long_options.data(), nullptr)) != -1) {
switch (opt) {
case 'h':
print_usage(argv[0]);
@@ -249,7 +249,7 @@ std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
case 'c':
case 'l':
case 'i':
case 'e':
case 'x':
case 'k':
case options::list_volumes:
case options::delete_volume:
@@ -279,7 +279,7 @@ std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
case 'i':
requested = Mode::inspect;
break;
case 'e':
case 'x':
requested = Mode::exec;
break;
case 'k':
+1 -1
View File
@@ -58,7 +58,7 @@ struct ParsedArgs {
std::vector<std::pair<std::string, std::string>> volume_specs;
std::vector<EnvSpec> env_specs;
// Trailing argv (after getopt_long stops), populated only for
// Mode::run/Mode::exec -- the command to run, or to run under -e/--exec.
// Mode::run/Mode::exec -- the command to run, or to run under -x/--exec.
std::vector<std::string> command;
// Parsed and range-validated from mode_arg when mode == Mode::exec.
std::optional<pid_t> exec_pid;
+1 -1
View File
@@ -86,7 +86,7 @@ std::optional<std::string> read_container_file(const std::vector<std::string>& n
// Reads the real uid/gid `ns_pid` is already running as, from its own
// /proc/<pid>/status (globally visible by pid, no namespace-joining needed) --
// used as -e/--exec's default privilege when --user isn't given, matching
// used as -x/--exec's default privilege when --user isn't given, matching
// whatever uid/gid -r/--run itself resolved the session to run as (an explicit
// --user, the image's own declared user, or root).
std::optional<std::pair<int, int>> read_running_uid_gid(pid_t ns_pid) {
+1 -1
View File
@@ -19,7 +19,7 @@
#include <sys/types.h>
// Stops a tracked, running -r/--run session identified by `pid` (the same
// tracked pid --list-processes/-e/--exec use) -- see CLAUDE.md's
// tracked pid --list-processes/-x/--exec use) -- see CLAUDE.md's
// kill_session.{h,cpp} entry for the three strategies this picks between,
// depending on what's actually available for that session (a dedicated
// cgroup, an isolated pid namespace, or neither). Sends SIGTERM first, waits