veyron/lib/exec: add support for signaling failure via the status channel.
Change-Id: Iecb3d22321a4c86d1503c065995bef4f25ae9da4
diff --git a/lib/exec/child.go b/lib/exec/child.go
index 72055e4..8beb45a 100644
--- a/lib/exec/child.go
+++ b/lib/exec/child.go
@@ -62,6 +62,13 @@
return err
}
+// SetFailed writes a 'failed' status to its parent.
+func (c *ChildHandle) SetFailed(oerr error) error {
+ _, err := c.statusPipe.Write([]byte(failedStatus + oerr.Error()))
+ c.statusPipe.Close()
+ return err
+}
+
// NewExtraFile creates a new file handle for the i-th file descriptor after
// discounting stdout, stderr, stdin and the files reserved by the framework for
// its own purposes.
diff --git a/lib/exec/exec_test.go b/lib/exec/exec_test.go
index 3056968..1e7662e 100644
--- a/lib/exec/exec_test.go
+++ b/lib/exec/exec_test.go
@@ -6,6 +6,7 @@
"log"
"os"
"os/exec"
+ "strings"
"sync"
"testing"
"time"
@@ -245,6 +246,16 @@
clean(t, ph)
}
+func TestToFail(t *testing.T) {
+ name := "testFail"
+ cmd := helperCommand(name, "failed", "to", "start")
+ ph := vexec.NewParentHandle(cmd)
+ err := waitForReady(t, cmd, name, 4, ph)
+ if err == nil || err.Error() != "failed to start" {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
+
func TestNeverReady(t *testing.T) {
name := "testNeverReady"
result := "never ready"
@@ -442,6 +453,15 @@
fmt.Fprintf(os.Stderr, "didn't get the expected error")
}
os.Exit(0)
+ case "testFail":
+ ch, err := vexec.GetChildHandle()
+ if err != nil {
+ log.Fatal(os.Stderr, "%s", err)
+ }
+ if err := ch.SetFailed(fmt.Errorf("%s", strings.Join(args, " "))); err != nil {
+
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ }
case "testReady":
ch, err := vexec.GetChildHandle()
if err != nil {
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
index 63d6c99..bfcba80 100644
--- a/lib/exec/parent.go
+++ b/lib/exec/parent.go
@@ -3,9 +3,11 @@
import (
"encoding/binary"
"errors"
+ "fmt"
"io"
"os"
"os/exec"
+ "strings"
"syscall"
"time"
@@ -15,7 +17,7 @@
)
var (
- ErrAuthTimeout = errors.New("timout in auth handshake")
+ ErrAuthTimeout = errors.New("timeout in auth handshake")
ErrTimeout = errors.New("timeout waiting for child")
ErrSecretTooLarge = errors.New("secret is too large")
)
@@ -159,11 +161,21 @@
for {
select {
case err := <-e:
- return err
+ if err != nil {
+ return err
+ }
+ // waitForStatus has closed the channel, but we may not
+ // have read the message from it yet.
case st := <-c:
if st == readyStatus {
return nil
}
+ if strings.HasPrefix(st, failedStatus) {
+ return fmt.Errorf("%s", strings.TrimPrefix(st, failedStatus))
+ }
+ if len(st) > 0 {
+ return fmt.Errorf("unrecognised status from subprocess: %q", st)
+ }
case <-p.tk.After(timeout):
// Make sure that the read in waitForStatus
// returns now.
diff --git a/lib/exec/shared.go b/lib/exec/shared.go
index 936d2bf..08c54ad 100644
--- a/lib/exec/shared.go
+++ b/lib/exec/shared.go
@@ -2,7 +2,8 @@
const (
version1 = "1.0.0"
- readyStatus = "ready"
+ readyStatus = "ready::"
+ failedStatus = "failed::"
initStatus = "init"
versionVariable = "VEYRON_EXEC_VERSION"
)