veyron/lib/modules: introduce sh.StartExternalCommand

This CL splits up the shell.Start function into shell.Start and
shell.StartExternalCommand. This allows users (such as the e2e tests) to
run commands that have not been pre-registered. The shell will not
attempt to run these commands in the envelope.

Change-Id: I920b6172e6750aa3e6de4c031d5dd5e3fef95936
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index c80aae7..c6cd898 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -37,6 +37,7 @@
 package modules
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -213,6 +214,14 @@
 	return registry.help(command)
 }
 
+func (sh *Shell) StartExternalCommand(env []string, args ...string) (Handle, error) {
+	if len(args) == 0 {
+		return nil, errors.New("no arguments specified to StartExternalCommand")
+	}
+	c := newExecHandleForExternalCommand(args[0])
+	return sh.startCommand(c, env, args...)
+}
+
 // Start starts the specified command, it returns a Handle which can be
 // used for interacting with that command.
 //
@@ -236,6 +245,26 @@
 // Commands must have already been registered using RegisterFunction
 // or RegisterChild.
 func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) {
+	cmd := registry.getCommand(name)
+	if cmd == nil {
+		return nil, fmt.Errorf("%s: not registered", name)
+	}
+	expanded := append([]string{name}, sh.expand(args...)...)
+	c := cmd.factory()
+	h, err := sh.startCommand(c, env, expanded...)
+	if err != nil {
+		// If the error is a timeout, then h can be used to recover
+		// any output from the process.
+		return h, err
+	}
+
+	if err := h.WaitForReady(sh.waitTimeout); err != nil {
+		return h, err
+	}
+	return h, nil
+}
+
+func (sh *Shell) startCommand(c command, env []string, args ...string) (Handle, error) {
 	cenv, err := sh.setupCommandEnv(env)
 	if err != nil {
 		return nil, err
@@ -244,16 +273,10 @@
 	if err != nil {
 		return nil, err
 	}
-	cmd := registry.getCommand(name)
-	if cmd == nil {
-		return nil, fmt.Errorf("%s: not registered", name)
-	}
-	expanded := append([]string{name}, sh.expand(args...)...)
-	h, err := cmd.factory().start(sh, p, cenv, expanded...)
+
+	h, err := c.start(sh, p, cenv, args...)
 	if err != nil {
-		// If the error is a timeout, then h can be used to recover
-		// any output from the process.
-		return h, err
+		return nil, err
 	}
 	sh.mu.Lock()
 	sh.handles[h] = struct{}{}
@@ -459,6 +482,11 @@
 
 	// Pid returns the pid of the process running the command
 	Pid() int
+
+	// WaitForReady waits until the child process signals to us that it is
+	// ready. If this does not occur within the given timeout duration, a
+	// timeout error is returned.
+	WaitForReady(timeout time.Duration) error
 }
 
 // command is used to abstract the implementations of inprocess and subprocess