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"
 )