Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix null-pointer exception in error handling of RunAndWait #256

Merged
merged 1 commit into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,27 +76,31 @@ func RunAndWait(c *Command, fields log.Fields) (int, error) {
}
log.Debugf("%s.Cmd.Run", c.Name)
if err := c.Cmd.Start(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return status.ExitStatus(), err
}
}
// Should only happen if we misconfigure or there's some more
// serious problem with the underlying open/exec syscalls. But
// we'll let the lack of heartbeat tell us if something has gone
// wrong to that extent.
// the stdlib almost certainly won't include the underlying error
// code with this error (if any!). we can try to parse the various
// totally undocumented strings that come back but I'd rather do my
// own dentistry. a safe bet is that the end user has given us an
// invalid executable so we're going to return 127.
log.Errorln(err)
return 1, err
return 127, err
}
os.Setenv(
fmt.Sprintf("CONTAINERPILOT_%s_PID", strings.ToUpper(c.Name)),
fmt.Sprintf("%v", c.Cmd.Process.Pid),
)
state, err := c.Cmd.Process.Wait()
if err != nil || (state != nil && !state.Success()) {
if status, ok := state.Sys().(syscall.WaitStatus); ok {
waitStatus, err := c.Cmd.Process.Wait()
if waitStatus != nil && !waitStatus.Success() {
if status, ok := waitStatus.Sys().(syscall.WaitStatus); ok {
log.Debug(err)
return status.ExitStatus(), err
}
} else if err != nil {
if err.Error() == errNoChild {
log.Debugf(err.Error())
return 0, nil // process exited cleanly before we hit wait4
}
log.Errorf("%s exited with error: %v", c.Name, err)
return 1, err
}
log.Debugf("%s.RunAndWait end", c.Name)
return 0, nil
Expand Down Expand Up @@ -206,18 +210,17 @@ func (c *Command) waitForTimeout() error {
defer func() { quit <- 0 }()
}
log.Debugf("%s.run waiting for PID %d: ", c.Name, cmd.Process.Pid)
state, err := cmd.Process.Wait()
if err != nil {
waitStatus, err := cmd.Process.Wait()
if waitStatus != nil && !waitStatus.Success() {
return fmt.Errorf("%s exited with error", c.Name)
} else if err != nil {
if err.Error() == errNoChild {
log.Debugf(err.Error())
return nil // process exited cleanly before we hit wait4
}
log.Errorf("%s exited with error: %v", c.Name, err)
return err
}
if state != nil && !state.Success() {
return fmt.Errorf("%s exited with error", c.Name)
}

log.Debugf("%s.run complete", c.Name)
return nil
Expand Down
20 changes: 15 additions & 5 deletions commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ func BenchmarkRunAndWaitSuccess(b *testing.B) {
}

func TestRunAndWaitFailed(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Expected panic but did not.")
}
}()
cmd, _ := NewCommand("./testdata/test.sh failStuff --debug", "0")
if exitCode, _ := RunAndWait(cmd, nil); exitCode != 255 {
t.Errorf("Expected exit code 255 but got %d", exitCode)
}
}

func TestRunAndWaitInvalidCommand(t *testing.T) {
cmd, _ := NewCommand("./testdata/invalidCommand", "0")
if exitCode, _ := RunAndWait(cmd, nil); exitCode != 127 {
t.Errorf("Expected exit code 127 but got %d", exitCode)
}
}

func TestRunAndWaitForOutput(t *testing.T) {

cmd, _ := NewCommand("./testdata/test.sh doStuff --debug", "0")
Expand Down Expand Up @@ -110,6 +112,14 @@ func TestRunWithTimeoutFailed(t *testing.T) {
}
}

func TestRunWithTimeoutInvalidCommand(t *testing.T) {
cmd, _ := NewCommand("./testdata/invalidCommand", "100ms")
fields := log.Fields{"process": "test"}
if err := RunWithTimeout(cmd, fields); err == nil {
t.Errorf("Expected error but got nil")
}
}

func TestEmptyCommand(t *testing.T) {
if cmd, err := NewCommand("", "0"); cmd != nil || err == nil {
t.Errorf("Expected exit (nil, err) but got %s, %s", cmd, err)
Expand Down