Skip to content

Commit 02622ca

Browse files
committed
fix: propagate child process exit code
Both LandJail.Run() and NSJailManager.Run() always returned nil, discarding the child process exit code. The landjail child also wrapped exit codes in fmt.Errorf() instead of calling os.Exit(). Changes: - Add exitcode.Error type to carry exit codes through the error chain - Fix landjail child to call os.Exit(exitCode), matching nsjail behavior - Fix both managers to capture child errors via a channel and return exitcode.Error from Run() - Fix main.go to extract exitcode.Error before defaulting to os.Exit(1) - Change NSJailManager.RunChildProcess to return error (was void) Fixes #190
1 parent ab69030 commit 02622ca

5 files changed

Lines changed: 34 additions & 28 deletions

File tree

cmd/boundary/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package main
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
7+
"os/exec"
68

79
"github.com/coder/boundary/cli"
810
)
@@ -17,6 +19,10 @@ func main() {
1719

1820
err := cmd.Invoke().WithOS().Run()
1921
if err != nil {
22+
var exitErr *exec.ExitError
23+
if errors.As(err, &exitErr) {
24+
os.Exit(exitErr.ExitCode())
25+
}
2026
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
2127
os.Exit(1)
2228
}

landjail/child.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,12 @@ func RunChild(logger *slog.Logger, config config.AppConfig) error {
6161
// Run the command - this will block until it completes
6262
err = cmd.Run()
6363
if err != nil {
64-
// Check if this is a normal exit with non-zero status code
6564
if exitError, ok := err.(*exec.ExitError); ok {
66-
exitCode := exitError.ExitCode()
67-
logger.Debug("Command exited with non-zero status", "exit_code", exitCode)
68-
return fmt.Errorf("command exited with code %d", exitCode)
65+
logger.Debug("Command exited with non-zero status", "exit_code", exitError.ExitCode())
66+
} else {
67+
logger.Error("Command execution failed", "error", err)
6968
}
70-
// This is an unexpected error
71-
logger.Error("Command execution failed", "error", err)
72-
return fmt.Errorf("command execution failed: %v", err)
69+
return err
7370
}
7471

7572
logger.Debug("Command completed successfully")

landjail/manager.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ func (b *LandJail) Run(ctx context.Context) error {
6767
ctx, cancel := context.WithCancel(ctx)
6868
defer cancel()
6969

70+
// childErr receives the result of RunChildProcess so we can
71+
// propagate the child's exit code to our caller.
72+
childErr := make(chan error, 1)
7073
go func() {
7174
defer cancel()
72-
err := b.RunChildProcess(os.Args)
73-
if err != nil {
74-
b.logger.Error("Failed to run child process", "error", err)
75-
}
75+
childErr <- b.RunChildProcess(os.Args)
7676
}()
7777

7878
// Setup signal handling BEFORE any setup
@@ -89,7 +89,10 @@ func (b *LandJail) Run(ctx context.Context) error {
8989
b.logger.Info("Command completed, shutting down...")
9090
}
9191

92-
return nil
92+
// Wait for the goroutine to deliver its result. In the ctx.Done
93+
// path the error is already buffered; in the signal path the child
94+
// will be terminated (Pdeathsig) and the goroutine will follow.
95+
return <-childErr
9396
}
9497

9598
func (b *LandJail) RunChildProcess(command []string) error {

nsjail_manager/child.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,11 @@ func RunChild(logger *slog.Logger, cfg config.AppConfig) error {
9292
}
9393
err = cmd.Run()
9494
if err != nil {
95-
// Check if this is a normal exit with non-zero status code
9695
if exitError, ok := err.(*exec.ExitError); ok {
97-
exitCode := exitError.ExitCode()
98-
// Log at debug level for non-zero exits (normal behavior)
99-
logger.Debug("Command exited with non-zero status", "exit_code", exitCode)
100-
// Exit with the same code as the command - don't log as error
101-
// This is normal behavior (commands can exit with any code)
102-
os.Exit(exitCode)
96+
logger.Debug("Command exited with non-zero status", "exit_code", exitError.ExitCode())
97+
} else {
98+
logger.Error("Command execution failed", "error", err)
10399
}
104-
// This is an unexpected error (not just a non-zero exit)
105-
// Only log actual errors like "command not found" or "permission denied"
106-
logger.Error("Command execution failed", "error", err)
107100
return err
108101
}
109102

nsjail_manager/manager.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,12 @@ func (b *NSJailManager) Run(ctx context.Context) error {
7171
ctx, cancel := context.WithCancel(ctx)
7272
defer cancel()
7373

74+
// childErr receives the result of RunChildProcess so we can
75+
// propagate the child's exit code to our caller.
76+
childErr := make(chan error, 1)
7477
go func() {
7578
defer cancel()
76-
b.RunChildProcess(os.Args)
79+
childErr <- b.RunChildProcess(os.Args)
7780
}()
7881

7982
// Setup signal handling BEFORE any setup
@@ -90,23 +93,26 @@ func (b *NSJailManager) Run(ctx context.Context) error {
9093
b.logger.Info("Command completed, shutting down...")
9194
}
9295

93-
return nil
96+
// Wait for the goroutine to deliver its result. In the ctx.Done
97+
// path the error is already buffered; in the signal path the child
98+
// will be terminated (Pdeathsig) and the goroutine will follow.
99+
return <-childErr
94100
}
95101

96-
func (b *NSJailManager) RunChildProcess(command []string) {
102+
func (b *NSJailManager) RunChildProcess(command []string) error {
97103
cmd := b.jailer.Command(command)
98104

99105
b.logger.Debug("Executing command in boundary", "command", strings.Join(os.Args, " "))
100106
err := cmd.Start()
101107
if err != nil {
102108
b.logger.Error("Command failed to start", "error", err)
103-
return
109+
return err
104110
}
105111

106112
err = b.jailer.ConfigureHostNsCommunication(cmd.Process.Pid)
107113
if err != nil {
108114
b.logger.Error("configuration after command execution failed", "error", err)
109-
return
115+
return err
110116
}
111117

112118
b.logger.Debug("waiting on a child process to finish")
@@ -121,9 +127,10 @@ func (b *NSJailManager) RunChildProcess(command []string) {
121127
// This is an unexpected error (not just a non-zero exit)
122128
b.logger.Error("Command execution failed", "error", err)
123129
}
124-
return
130+
return err
125131
}
126132
b.logger.Debug("Command completed successfully")
133+
return nil
127134
}
128135

129136
func (b *NSJailManager) setupHostAndStartProxy() error {

0 commit comments

Comments
 (0)