summaryrefslogtreecommitdiff
path: root/internal/namespace
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-05-29 19:21:49 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-05-29 19:21:49 -0400
commit70096b533d42b684ab13651aaae884047e01e43d (patch)
tree2646cf017a7b903c6e1f3c1be981b1d21fa4a51b /internal/namespace
parent284ed362550e1fccc62ecd876dbd3f4c8fc721e2 (diff)
refactor: optimize file cleanups, propagate exit codes, and fix Makefile
- Unlink the temporary bootstrap launcher binary immediately after opening a read-only descriptor to it, then execute via `/proc/self/fd/<fd>` to ensure zero-disk footprint on execution. - Unlink temporary `/tmp/resolvconf*` files immediately after successful bind-mounting over `/etc/resolv.conf`. - Prune parent ephemeral profile directories when unpinning a namespace, leaving zero directories behind once empty. - Propagate the exact exit status of the wrapped command to the host process using `errors.As` and `*exec.ExitError` instead of defaulting to exit code 1. - Added E2E automated test `TestExitCodePropagation` to verify exit status delivery. - Added the `$(BINARY)` target to `.PHONY` in the Makefile to delegate dependency tracking to Go's compiler cache, ensuring modified Go files are rebuilt during `make test`.
Diffstat (limited to 'internal/namespace')
-rw-r--r--internal/namespace/namespace.go22
-rw-r--r--internal/namespace/pinning.go4
2 files changed, 24 insertions, 2 deletions
diff --git a/internal/namespace/namespace.go b/internal/namespace/namespace.go
index a1e7ad9..e2ef2f1 100644
--- a/internal/namespace/namespace.go
+++ b/internal/namespace/namespace.go
@@ -100,16 +100,36 @@ func Bootstrap() error {
// 2. Write the embedded launcher binary to the temp file.
if _, err := tmpFile.Write(launcherBytes); err != nil {
_ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
return fmt.Errorf("failed to write launcher binary: %w", err)
}
// Ensure the binary is executable (0700)
if err := tmpFile.Chmod(0700); err != nil {
_ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
return fmt.Errorf("failed to set launcher permissions: %w", err)
}
+
+ // 2b. Open a read-only fd of the launcher to exec
+ execFd, err := syscall.Open(launcherPath, syscall.O_RDONLY, 0)
+ if err != nil {
+ _ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
+ return fmt.Errorf("failed to open launcher for exec: %w", err)
+ }
+
+ // Close the write file descriptor (to avoid ETXTBSY)
_ = tmpFile.Close()
+ // Unlink the file from disk (makes it invisible and ensures it is deleted on exit)
+ _ = os.Remove(launcherPath)
+
+ // Clear close-on-exec so it remains open across syscall.Exec
+ if flags, err := unix.FcntlInt(uintptr(execFd), unix.F_GETFD, 0); err == nil {
+ _, _ = unix.FcntlInt(uintptr(execFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC)
+ }
+
// 3. Prepare arguments for the launcher.
// The launcher expects: launcher <command_to_run> [args...]
args := []string{self}
@@ -153,7 +173,7 @@ func Bootstrap() error {
}
}
- err = syscall.Exec(launcherPath, args, env)
+ err = syscall.Exec(fmt.Sprintf("/proc/self/fd/%d", execFd), args, env)
if err != nil {
return fmt.Errorf("launcher exec failed: %w", err)
}
diff --git a/internal/namespace/pinning.go b/internal/namespace/pinning.go
index 7976937..eb0a376 100644
--- a/internal/namespace/pinning.go
+++ b/internal/namespace/pinning.go
@@ -44,8 +44,10 @@ func UnpinNamespace(pm *paths.PathManager, profile string) error {
return fmt.Errorf("failed to unpin namespace %s: %w", nsPath, err)
}
- // Try to remove pids directory
+ // Try to remove pids directory and empty parent directories
_ = os.Remove(pidsDir)
+ _ = os.Remove(filepath.Dir(pidsDir))
+ _ = os.Remove(filepath.Dir(filepath.Dir(pidsDir)))
return nil
}