veyron/lib/modules: add support for per-command environment and logging.

Change-Id: Ie54dd05c161b06303cee7b878772d5e58ba31e17
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index 44ef680..35d6512 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -11,15 +11,15 @@
 	"sync"
 	"time"
 
-	"veyron.io/veyron/veyron2/vlog"
-
 	vexec "veyron.io/veyron/veyron/lib/exec"
+	"veyron.io/veyron/veyron2/vlog"
 )
 
 // execHandle implements both the command and Handle interfaces.
 type execHandle struct {
 	mu         sync.Mutex
 	cmd        *exec.Cmd
+	name       string
 	entryPoint string
 	handle     *vexec.ParentHandle
 	sh         *Shell
@@ -63,8 +63,8 @@
 	return runFlag.Value.String() == "TestHelperProcess"
 }
 
-func newExecHandle(entryPoint string) command {
-	return &execHandle{entryPoint: entryPoint}
+func newExecHandle(name string) command {
+	return &execHandle{name: name, entryPoint: ShellEntryPoint + "=" + name}
 }
 
 func (eh *execHandle) Stdout() io.Reader {
@@ -91,40 +91,14 @@
 	eh.mu.Unlock()
 }
 
-func osEnvironMap() map[string]string {
-	m := make(map[string]string)
-	for _, osv := range os.Environ() {
-		if len(osv) == 0 {
-			continue
-		}
-		parts := strings.SplitN(osv, "=", 2)
-		key := parts[0]
-		if len(parts) == 2 {
-			m[key] = parts[1]
-		} else {
-			m[key] = ""
-		}
-	}
-	return m
-}
-func (sh *Shell) mergeOSEnv() map[string]string {
-	merged := osEnvironMap()
-	sh.mu.Lock()
-	for k, v := range sh.env {
-		merged[k] = v
-	}
-	sh.mu.Unlock()
-	return merged
-}
-
-func (eh *execHandle) envelope(sh *Shell, args ...string) ([]string, []string) {
+func (eh *execHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) {
 	newargs := []string{os.Args[0]}
 	newargs = append(newargs, testFlags()...)
 	newargs = append(newargs, args...)
-	return newargs, append(sh.MergedEnv(), eh.entryPoint)
+	return newargs, append(env, eh.entryPoint)
 }
 
-func (eh *execHandle) start(sh *Shell, args ...string) (Handle, error) {
+func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) {
 	eh.mu.Lock()
 	defer eh.mu.Unlock()
 	eh.sh = sh
@@ -133,7 +107,7 @@
 	// the flag package.
 	newargs := append(testFlags(), args[1:]...)
 	cmd := exec.Command(os.Args[0], newargs...)
-	cmd.Env = append(sh.MergedEnv(), eh.entryPoint)
+	cmd.Env = append(env, eh.entryPoint)
 	fname := strings.TrimPrefix(eh.entryPoint, ShellEntryPoint+"=")
 	stderr, err := newLogfile(strings.TrimLeft(fname, "-\n\t "))
 	if err != nil {
@@ -155,10 +129,13 @@
 	eh.stdin = stdin
 	eh.handle = handle
 	eh.cmd = cmd
+	vlog.VI(1).Infof("Start: %q args: %v", eh.name, cmd.Args)
+	vlog.VI(2).Infof("Start: %q env: %v", eh.name, cmd.Env)
 	if err := handle.Start(); err != nil {
 		return nil, err
 	}
-	err = handle.WaitForReady(10 * time.Second)
+	// TODO(cnicolaou): make this timeout configurable
+	err = handle.WaitForReady(time.Minute)
 	return eh, err
 }
 
@@ -169,6 +146,7 @@
 func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
 	eh.mu.Lock()
 	defer eh.mu.Unlock()
+	vlog.VI(1).Infof("Shutdown: %q", eh.name)
 	eh.stdin.Close()
 	logFile := eh.stderr.Name()
 	defer eh.sh.Forget(eh)
@@ -177,14 +155,19 @@
 		os.Remove(logFile)
 	}()
 
+	// TODO(cnicolaou): make this configurable
+	timeout := 10 * time.Second
 	if stdout == nil && stderr == nil {
-		return eh.cmd.Wait()
+		return eh.handle.Wait(timeout)
 	}
-	// Read from stdin before waiting for the child process to ensure
-	// that we get to read all of its output.
-	readTo(eh.stdout, stdout)
 
-	procErr := eh.cmd.Wait()
+	if stdout != nil {
+		// Read from stdin before waiting for the child process to ensure
+		// that we get to read all of its output.
+		readTo(eh.stdout, stdout)
+	}
+
+	procErr := eh.handle.Wait(timeout)
 
 	// Stderr is buffered to a file, so we can safely read it after we
 	// wait for the process.
@@ -192,7 +175,7 @@
 	if stderr != nil {
 		stderrFile, err := os.Open(logFile)
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "failed to open %q: %s\n", logFile, err)
+			vlog.VI(1).Infof("failed to open %q: %s\n", logFile, err)
 			return procErr
 		}
 		readTo(stderrFile, stderr)
@@ -216,8 +199,7 @@
 		return
 	}
 	if err := child.dispatch(); err != nil {
-		fmt.Fprintf(os.Stderr, "Failed: %s\n", err)
-		os.Exit(1)
+		vlog.Fatalf("Failed: %s", err)
 	}
 	os.Exit(0)
 }
@@ -273,15 +255,14 @@
 		for {
 			_, err := os.FindProcess(pid)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "Looks like our parent exited: %v", err)
-				os.Exit(1)
+				vlog.Fatalf("Looks like our parent exited: %v", err)
 			}
 			time.Sleep(time.Second)
 		}
 	}(os.Getppid())
 
 	args := append([]string{command}, flag.Args()...)
-	return m.fn(os.Stdin, os.Stdout, os.Stderr, osEnvironMap(), args...)
+	return m.fn(os.Stdin, os.Stdout, os.Stderr, envSliceToMap(os.Environ()), args...)
 }
 
 func (child *childRegistrar) addSubprocesses(sh *Shell, pattern string) error {
diff --git a/lib/modules/func.go b/lib/modules/func.go
index 68d103b..54ce341 100644
--- a/lib/modules/func.go
+++ b/lib/modules/func.go
@@ -5,6 +5,8 @@
 	"io"
 	"os"
 	"sync"
+
+	"veyron.io/veyron/veyron2/vlog"
 )
 
 type pipe struct {
@@ -12,6 +14,7 @@
 }
 type functionHandle struct {
 	mu            sync.Mutex
+	name          string
 	main          Main
 	stdin, stdout pipe
 	stderr        *os.File
@@ -20,8 +23,8 @@
 	wg            sync.WaitGroup
 }
 
-func newFunctionHandle(main Main) command {
-	return &functionHandle{main: main}
+func newFunctionHandle(name string, main Main) command {
+	return &functionHandle{name: name, main: main}
 }
 
 func (fh *functionHandle) Stdout() io.Reader {
@@ -48,11 +51,11 @@
 	fh.mu.Unlock()
 }
 
-func (fh *functionHandle) envelope(sh *Shell, args ...string) ([]string, []string) {
-	return args, sh.MergedEnv()
+func (fh *functionHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) {
+	return args, env
 }
 
-func (fh *functionHandle) start(sh *Shell, args ...string) (Handle, error) {
+func (fh *functionHandle) start(sh *Shell, env []string, args ...string) (Handle, error) {
 	fh.mu.Lock()
 	defer fh.mu.Unlock()
 	fh.sh = sh
@@ -69,7 +72,7 @@
 	fh.stderr = stderr
 	fh.wg.Add(1)
 
-	go func() {
+	go func(env []string) {
 		fh.mu.Lock()
 		stdin := fh.stdin.r
 		stdout := fh.stdout.w
@@ -77,7 +80,10 @@
 		main := fh.main
 		fh.mu.Unlock()
 
-		err := main(stdin, stdout, stderr, sh.mergeOSEnv(), args...)
+		cenv := envSliceToMap(env)
+		vlog.VI(1).Infof("Start: %q args: %v", fh.name, args)
+		vlog.VI(2).Infof("Start: %q env: %v", fh.name, cenv)
+		err := main(stdin, stdout, stderr, cenv, args...)
 		if err != nil {
 			fmt.Fprintf(stderr, "%s\n", err)
 		}
@@ -88,7 +94,7 @@
 		fh.err = err
 		fh.mu.Unlock()
 		fh.wg.Done()
-	}()
+	}(env)
 	return fh, nil
 }
 
@@ -98,6 +104,7 @@
 
 func (fh *functionHandle) Shutdown(stdout_w, stderr_w io.Writer) error {
 	fh.mu.Lock()
+	vlog.VI(1).Infof("Shutdown: %q", fh.name)
 	fh.stdin.w.Close()
 	stdout := fh.stdout.r
 	stderr := fh.stderr
diff --git a/lib/modules/modules_test.go b/lib/modules/modules_test.go
index 6869816..278edab 100644
--- a/lib/modules/modules_test.go
+++ b/lib/modules/modules_test.go
@@ -12,9 +12,11 @@
 
 	"veyron.io/veyron/veyron/lib/exec"
 	"veyron.io/veyron/veyron/lib/modules"
+	"veyron.io/veyron/veyron/lib/testutil"
 )
 
 func init() {
+	testutil.Init()
 	modules.RegisterChild("envtest", "envtest: <variables to print>...", PrintFromEnv)
 	modules.RegisterChild("printenv", "printenv", PrintEnv)
 	modules.RegisterChild("echo", "[args]*", Echo)
@@ -49,7 +51,7 @@
 		fmt.Fprintf(stdout, "%s%s\n", printEnvArgPrefix, a)
 	}
 	for k, v := range env {
-		fmt.Fprintln(stdout, k+"="+v)
+		fmt.Fprintf(stdout, k+"="+v+"\n")
 	}
 	return nil
 }
@@ -97,7 +99,7 @@
 	}
 	h.CloseStdin()
 	if !waitForInput(scanner) {
-		t.Errorf("timeout")
+		t.Fatalf("timeout")
 		return
 	}
 	if got, want := scanner.Text(), "done"; got != want {
@@ -144,12 +146,12 @@
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	if got, want := h.Shutdown(nil, nil), "exit status 1"; got == nil || got.Error() != want {
+	if got, want := h.Shutdown(nil, nil), "exit status 255"; got == nil || got.Error() != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
 }
 
-func testShutdown(t *testing.T, sh *modules.Shell) {
+func testShutdown(t *testing.T, sh *modules.Shell, isfunc bool) {
 	result := ""
 	args := []string{"a", "b c", "ddd"}
 	if _, err := sh.Start("echo", args...); err != nil {
@@ -166,6 +168,9 @@
 	if got, want := stdoutBuf.String(), stdoutOutput+result; got != want {
 		t.Errorf("got %q want %q", got, want)
 	}
+	if !isfunc {
+		stderrBuf.ReadString('\n') // Skip past the random # generator output
+	}
 	if got, want := stderrBuf.String(), stderrOutput; got != want {
 		t.Errorf("got %q want %q", got, want)
 	}
@@ -173,13 +178,13 @@
 
 func TestShutdownSubprocess(t *testing.T) {
 	sh := modules.NewShell("echo")
-	testShutdown(t, sh)
+	testShutdown(t, sh, false)
 }
 
 func TestShutdownFunction(t *testing.T) {
 	sh := modules.NewShell()
 	sh.AddFunction("echo", Echo, "[args]*")
-	testShutdown(t, sh)
+	testShutdown(t, sh, true)
 }
 
 func TestErrorFunc(t *testing.T) {
@@ -224,7 +229,7 @@
 			childEnv = append(childEnv, o)
 		}
 	}
-	shArgs, shEnv := sh.CommandEnvelope("printenv", args...)
+	shArgs, shEnv := sh.CommandEnvelope("printenv", nil, args...)
 	for _, want := range args {
 		if !find(want, childArgs) {
 			t.Errorf("failed to find %q in %s", want, childArgs)
@@ -254,7 +259,10 @@
 	defer sh.Cleanup(nil, nil)
 	sh.SetVar("a", "1")
 	os.Setenv("a", "wrong, should be 1")
-	h, err := sh.Start("printenv")
+	sh.SetVar("b", "2 also wrong")
+	os.Setenv("b", "wrong, should be 2")
+
+	h, err := sh.StartWithEnv("printenv", []string{"b=2"})
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -263,7 +271,12 @@
 		o := scanner.Text()
 		if strings.HasPrefix(o, "a=") {
 			if got, want := o, "a=1"; got != want {
-				t.Errorf("got: %s, want %s", got, want)
+				t.Errorf("got: %q, want %q", got, want)
+			}
+		}
+		if strings.HasPrefix(o, "b=") {
+			if got, want := o, "b=2"; got != want {
+				t.Errorf("got: %q, want %q", got, want)
 			}
 		}
 	}
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 69e633a..cb68856 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -108,6 +108,14 @@
 
 // CreateAndUseNewCredentials setups a new credentials directory and then
 // configures the shell and all of its children to use to it.
+//
+// TODO(cnicolaou): this should use the principal already setup
+// with the runtime if the runtime has been initialized, if not,
+// it should create a new principal. As of now, this approach only works
+// for child processes that talk to each other, but not to the parent
+// process that started them since it's running with a different set of
+// credentials setup elsewhere. When this change is made it should
+// be possible to remove creating credentials in many unit tests.
 func (sh *Shell) CreateAndUseNewCredentials() error {
 	dir, err := ioutil.TempDir("", "veyron_credentials")
 	if err != nil {
@@ -132,9 +140,8 @@
 }
 
 func (sh *Shell) addSubprocess(name string, help string) {
-	entryPoint := ShellEntryPoint + "=" + name
 	sh.mu.Lock()
-	sh.cmds[name] = &commandDesc{func() command { return newExecHandle(entryPoint) }, help}
+	sh.cmds[name] = &commandDesc{func() command { return newExecHandle(name) }, help}
 	sh.mu.Unlock()
 }
 
@@ -142,7 +149,7 @@
 // within the current process.
 func (sh *Shell) AddFunction(name string, main Main, help string) {
 	sh.mu.Lock()
-	sh.cmds[name] = &commandDesc{func() command { return newFunctionHandle(main) }, help}
+	sh.cmds[name] = &commandDesc{func() command { return newFunctionHandle(name, main) }, help}
 	sh.mu.Unlock()
 }
 
@@ -179,9 +186,16 @@
 // the Cleanup method. If the non-registered subprocess command does not
 // exist then the Start command will return an error.
 func (sh *Shell) Start(name string, args ...string) (Handle, error) {
+	return sh.StartWithEnv(name, nil, args...)
+}
+
+// StartWithEnv is like Start except with a set of environment variables
+// that override those in the Shell and the OS' environment.
+func (sh *Shell) StartWithEnv(name string, env []string, args ...string) (Handle, error) {
+	cenv := sh.MergedEnv(env)
 	cmd := sh.getCommand(name)
 	expanded := append([]string{name}, sh.expand(args...)...)
-	h, err := cmd.factory().start(sh, expanded...)
+	h, err := cmd.factory().start(sh, cenv, expanded...)
 	if err != nil {
 		return nil, err
 	}
@@ -196,8 +210,7 @@
 	cmd := sh.cmds[name]
 	sh.mu.Unlock()
 	if cmd == nil {
-		entryPoint := ShellEntryPoint + "=" + name
-		cmd = &commandDesc{func() command { return newExecHandle(entryPoint) }, ""}
+		cmd = &commandDesc{func() command { return newExecHandle(name) }, ""}
 	}
 	return cmd
 }
@@ -205,8 +218,8 @@
 // CommandEnvelope returns the command line and environment that would be
 // used for running the subprocess or function if it were started with the
 // specifed arguments.
-func (sh *Shell) CommandEnvelope(name string, args ...string) ([]string, []string) {
-	return sh.getCommand(name).factory().envelope(sh, args...)
+func (sh *Shell) CommandEnvelope(name string, env []string, args ...string) ([]string, []string) {
+	return sh.getCommand(name).factory().envelope(sh, sh.MergedEnv(env), args...)
 }
 
 // Forget tells the Shell to stop tracking the supplied Handle. This is
@@ -249,6 +262,13 @@
 	sh.env[key] = value
 }
 
+// ClearVar removes the speficied variable from the Shell's environment
+func (sh *Shell) ClearVar(key string) {
+	sh.mu.Lock()
+	defer sh.mu.Unlock()
+	delete(sh.env, key)
+}
+
 // Env returns the entire set of environment variables associated with this
 // Shell as a string slice.
 func (sh *Shell) Env() []string {
@@ -263,8 +283,10 @@
 
 // Cleanup calls Shutdown on all of the Handles currently being tracked
 // by the Shell and writes to stdout and stderr as per the Shutdown
-// method in the Handle interface.
-func (sh *Shell) Cleanup(stdout, stderr io.Writer) {
+// method in the Handle interface. Cleanup returns the error from the
+// last Shutdown that returned a non-nil error. The order that the
+// Shutdown routines are executed is not defined.
+func (sh *Shell) Cleanup(stdout, stderr io.Writer) error {
 	sh.mu.Lock()
 	handles := make(map[Handle]struct{})
 	for k, v := range sh.handles {
@@ -272,24 +294,35 @@
 	}
 	sh.handles = make(map[Handle]struct{})
 	sh.mu.Unlock()
+	var err error
 	for k, _ := range handles {
-		k.Shutdown(stdout, stderr)
+		cerr := k.Shutdown(stdout, stderr)
+		if cerr != nil {
+			err = cerr
+		}
 	}
 	if len(sh.credDir) > 0 {
 		os.RemoveAll(sh.credDir)
 	}
+	return err
 }
 
 // MergedEnv returns a slice that contains the merged set of environment
-// variables from the OS environment and those in this Shell, preferring
-// values in the Shell environment over those found in the OS environment.
-func (sh *Shell) MergedEnv() []string {
-	merged := sh.mergeOSEnv()
-	env := []string{}
-	for k, v := range merged {
-		env = append(env, k+"="+v)
+// variables from the OS environment, those in this Shell and those provided
+// as a parameter to it. It prefers values from its parameter over those
+// from the Shell, over those from the OS.
+func (sh *Shell) MergedEnv(env []string) []string {
+	osmap := envSliceToMap(os.Environ())
+	evmap := envSliceToMap(env)
+	sh.mu.Lock()
+	m1 := mergeMaps(osmap, sh.env)
+	defer sh.mu.Unlock()
+	m2 := mergeMaps(m1, evmap)
+	r := []string{}
+	for k, v := range m2 {
+		r = append(r, k+"="+v)
 	}
-	return env
+	return r
 }
 
 // Handle represents a running command.
@@ -325,6 +358,6 @@
 // command is used to abstract the implementations of inprocess and subprocess
 // commands.
 type command interface {
-	envelope(sh *Shell, args ...string) ([]string, []string)
-	start(sh *Shell, args ...string) (Handle, error)
+	envelope(sh *Shell, env []string, args ...string) ([]string, []string)
+	start(sh *Shell, env []string, args ...string) (Handle, error)
 }
diff --git a/lib/modules/util.go b/lib/modules/util.go
index 21fc4c2..c35c74e 100644
--- a/lib/modules/util.go
+++ b/lib/modules/util.go
@@ -6,6 +6,7 @@
 	"io"
 	"io/ioutil"
 	"os"
+	"strings"
 )
 
 func newLogfile(prefix string) (*os.File, error) {
@@ -25,3 +26,40 @@
 		fmt.Fprintf(w, "%s\n", scanner.Text())
 	}
 }
+
+// envSliceToMap returns a map representation of a string slive
+// of environment variables.
+func envSliceToMap(env []string) map[string]string {
+	m := make(map[string]string)
+	if env == nil {
+		return m
+	}
+	for _, osv := range env {
+		if len(osv) == 0 {
+			continue
+		}
+		parts := strings.SplitN(osv, "=", 2)
+		key := parts[0]
+		if len(parts) == 2 {
+			m[key] = parts[1]
+		} else {
+			m[key] = ""
+		}
+	}
+	return m
+}
+
+// mergeMaps merges two maps, a & b, with b taking preference over a.
+func mergeMaps(a, b map[string]string) map[string]string {
+	if len(b) == 0 {
+		return a
+	}
+	merged := make(map[string]string)
+	for k, v := range a {
+		merged[k] = v
+	}
+	for k, v := range b {
+		merged[k] = v
+	}
+	return merged
+}