Merge "veyron/runtimes/google/rt: Use the new security model by default."
diff --git a/lib/flags/doc.go b/lib/flags/doc.go
index 49a7360..b752f0a 100644
--- a/lib/flags/doc.go
+++ b/lib/flags/doc.go
@@ -1,17 +1,18 @@
-// Package flags provides definitions for commonly used flags and where
-// appropriate, implementations of the flag.Value interface for those flags
-// to ensure that only valid values of those flags are supplied. Some of these
-// flags may also be specified using environment variables directly for
-// are documented accordingly; in these cases the command line value takes
+// Package flags provides definitions for commonly used flags and, where
+// appropriate, implementations of the flag.Value interface for those flags to
+// ensure that only valid values of those flags are supplied. Some of these
+// flags may also be specified using environment variables directly and are
+// documented accordingly; in these cases the command line value takes
 // precedence over the environment variable.
-// Flags are defined as 'groups' of related flags so that caller may choose
-// which ones to use without having to be burdened with the full set. The
-// groups may be used directly or via the Flags type that aggregates multiple
-// groups. In all cases, the flags are registered with a supplied
-// flag.FlagSet and hence are not forced onto the command line
-// unless the caller passes in flag.CommandLine as the flag.FlagSet to use.
 //
-// In general, this package will be used by veyron profiles and the
-// runtime implementations, but can also be used by any application
-// that wants access to the flags and environment variables it supports.
+// Flags are defined as 'groups' of related flags so that the caller may choose
+// which ones to use without having to be burdened with the full set. The groups
+// may be used directly or via the Flags type that aggregates multiple
+// groups. In all cases, the flags are registered with a supplied flag.FlagSet
+// and hence are not forced onto the command line unless the caller passes in
+// flag.CommandLine as the flag.FlagSet to use.
+//
+// In general, this package will be used by veyron profiles and the runtime
+// implementations, but can also be used by any application that wants access to
+// the flags and environment variables it supports.
 package flags
diff --git a/lib/modules/core/core.go b/lib/modules/core/core.go
index bf4e6d4..add670f 100644
--- a/lib/modules/core/core.go
+++ b/lib/modules/core/core.go
@@ -67,6 +67,7 @@
 	MTCommand          = "mt"
 	LSExternalCommand  = "lse"
 	ProxyServerCommand = "proxyd"
+	WSPRCommand        = "wsprd"
 	ShellCommand       = "sh"
 )
 
@@ -93,7 +94,11 @@
 		run a glob command as an external subprocess`)
 	shell.AddSubprocess(ProxyServerCommand, `<name>...
 		run a proxy server mounted at the specified names`)
-	//	shell.AddSubprocess(ShellCommand, subshell, "")
+	// TODO(sadovsky): It's unfortunate that we must duplicate help strings
+	// between RegisterChild and AddSubprocess. Will be fixed by my proposed
+	// refactoring.
+	shell.AddSubprocess(WSPRCommand, usageWSPR())
+	//shell.AddSubprocess(ShellCommand, subshell, "")
 
 	shell.AddFunction(LSCommand, ls, `<glob>...
 	issues glob requests using the current processes namespace library`)
diff --git a/lib/modules/core/echo.go b/lib/modules/core/echo.go
index 3f6ff42..f3adb9f 100644
--- a/lib/modules/core/echo.go
+++ b/lib/modules/core/echo.go
@@ -40,7 +40,7 @@
 func echoServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	fl, args, err := parseListenFlags(args)
 	if err != nil {
-		return fmt.Errorf("failed parsing args: %s", err)
+		return fmt.Errorf("failed to parse args: %s", err)
 	}
 	if err := checkArgs(args, 2, "<message> <name>"); err != nil {
 		return err
diff --git a/lib/modules/core/mounttable.go b/lib/modules/core/mounttable.go
index 9c4fec0..9326030 100644
--- a/lib/modules/core/mounttable.go
+++ b/lib/modules/core/mounttable.go
@@ -36,9 +36,8 @@
 	r := rt.R()
 	fl, args, err := parseListenFlags(args)
 	if err != nil {
-		return fmt.Errorf("failed parsing args: %s", err)
+		return fmt.Errorf("failed to parse args: %s", err)
 	}
-	//	args = fl.Args()
 	lspec := initListenSpec(fl)
 	server, err := r.NewServer(options.ServesMountTable(true))
 	if err != nil {
@@ -72,7 +71,7 @@
 
 func ls(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	details := false
-	args = args[1:] // skip over comamnd name
+	args = args[1:] // skip over command name
 	if len(args) > 0 && args[0] == "-l" {
 		details = true
 		args = args[1:]
diff --git a/lib/modules/core/proxy.go b/lib/modules/core/proxy.go
index 7290543..3cc63ea 100644
--- a/lib/modules/core/proxy.go
+++ b/lib/modules/core/proxy.go
@@ -20,8 +20,10 @@
 func proxyServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	fl, args, err := parseListenFlags(args)
 	if err != nil {
-		return fmt.Errorf("failed parsing args: %s", err)
-	} //	args = fl.Args()
+		return fmt.Errorf("failed to parse args: %s", err)
+	}
+	// TODO(sadovsky): Why does this require >=1 arg? Seems 0 should be fine.
+	// Also note, we have no way to specify ">=0".
 	if err := checkArgs(args, -1, ""); err != nil {
 		return err
 	}
@@ -63,7 +65,6 @@
 	for _, p := range pub.Published() {
 		fmt.Fprintf(stdout, "PUBLISHED_PROXY_NAME=%s\n", p)
 	}
-	fmt.Fprintf(stdout, "READY")
 	modules.WaitForEOF(stdin)
 	return nil
 }
diff --git a/lib/modules/core/util.go b/lib/modules/core/util.go
index 81b6101..a429651 100644
--- a/lib/modules/core/util.go
+++ b/lib/modules/core/util.go
@@ -9,13 +9,19 @@
 	"veyron.io/veyron/veyron/lib/flags"
 )
 
+func parseFlags(fl *flags.Flags, args []string) error {
+	if len(args) == 0 {
+		return nil
+	}
+	return fl.Parse(args[1:])
+}
+
+// parseListenFlags parses the given args using just the flags and env vars
+// defined in the veyron/lib/flags package.
 func parseListenFlags(args []string) (*flags.Flags, []string, error) {
 	fs := flag.NewFlagSet("modules/core", flag.ContinueOnError)
 	fl := flags.CreateAndRegister(fs, flags.Listen)
-	if len(args) == 0 {
-		return fl, []string{}, nil
-	}
-	err := fl.Parse(args[1:])
+	err := parseFlags(fl, args)
 	return fl, fl.Args(), err
 }
 
@@ -28,7 +34,7 @@
 	}
 }
 
-// checkArgs checks for the expected number of args in args. A -ve
+// checkArgs checks for the expected number of args in args. A negative
 // value means at least that number of args are expected.
 func checkArgs(args []string, expected int, usage string) error {
 	got := len(args)
diff --git a/lib/modules/core/wspr.go b/lib/modules/core/wspr.go
new file mode 100644
index 0000000..2c242e8
--- /dev/null
+++ b/lib/modules/core/wspr.go
@@ -0,0 +1,72 @@
+package core
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"strings"
+
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/options"
+
+	"veyron.io/veyron/veyron/lib/flags"
+	"veyron.io/veyron/veyron/lib/modules"
+	"veyron.io/wspr/veyron/services/wsprd/wspr"
+)
+
+var (
+	// TODO(sadovsky): We should restructure things so that we can avoid
+	// duplicating code between subprocess command impls and actual main()'s.
+	fs *flag.FlagSet = flag.NewFlagSet("wspr", flag.ContinueOnError)
+
+	port   *int    = fs.Int("port", 0, "Port to listen on.")
+	identd *string = fs.String("identd", "", "identd server name. Must be set.")
+	// TODO(ataly, ashankar, bjornick): Remove this flag once the old security
+	// model is killed.
+	newSecurityModel *bool = fs.Bool("new_security_model", false, "Use the new security model.")
+
+	fl *flags.Flags = flags.CreateAndRegister(fs, flags.Listen)
+)
+
+func usageWSPR() string {
+	res := []string{}
+	fs.VisitAll(func(f *flag.Flag) {
+		format := "  -%s=%s: %s"
+		if getter, ok := f.Value.(flag.Getter); ok {
+			if _, ok := getter.Get().(string); ok {
+				// put quotes on the value
+				format = "  -%s=%q: %s"
+			}
+		}
+		res = append(res, fmt.Sprintf(format, f.Name, f.DefValue, f.Usage))
+	})
+	return strings.Join(res, "\n") + "\n"
+}
+
+func init() {
+	modules.RegisterChild(WSPRCommand, usageWSPR(), startWSPR)
+}
+
+func startWSPR(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	if err := parseFlags(fl, args); err != nil {
+		return fmt.Errorf("failed to parse args: %s", err)
+	}
+	args = fl.Args()
+
+	var opts []veyron2.ROpt
+	if *newSecurityModel {
+		opts = append(opts, options.ForceNewSecurityModel{})
+	}
+
+	proxy := wspr.NewWSPR(*port, initListenSpec(fl), *identd)
+	defer proxy.Shutdown()
+
+	addr := proxy.Listen()
+	go func() {
+		proxy.Serve()
+	}()
+
+	fmt.Fprintf(stdout, "WSPR_ADDR=%s\n", addr.String())
+	modules.WaitForEOF(stdin)
+	return nil
+}
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 135100c..b2bd087 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -511,7 +511,7 @@
 func (fc *flowClient) Finish(resultptrs ...interface{}) error {
 	defer vlog.LogCall()()
 	err := fc.finish(resultptrs...)
-	vtrace.FromContext(fc.ctx).Annotate("Finished")
+	vtrace.FromContext(fc.ctx).Finish()
 	return err
 }
 
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index fd213bc..dba781d 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -709,6 +709,8 @@
 
 	results, err := fs.processRequest()
 
+	ivtrace.FromContext(fs).Finish()
+
 	var traceResponse vtrace.Response
 	if fs.allowDebug {
 		traceResponse = ivtrace.Response(fs)
@@ -769,6 +771,9 @@
 
 	req, verr := fs.readIPCRequest()
 	if verr != nil {
+		// We don't know what the ipc call was supposed to be, but we'll create
+		// a placeholder span so we can capture annotations.
+		fs.T, _ = ivtrace.WithNewSpan(fs, "Failed IPC Call")
 		return nil, verr
 	}
 	fs.method = req.Method
diff --git a/runtimes/google/vtrace/collector.go b/runtimes/google/vtrace/collector.go
index 956b2d2..3313384 100644
--- a/runtimes/google/vtrace/collector.go
+++ b/runtimes/google/vtrace/collector.go
@@ -9,6 +9,17 @@
 	"veyron.io/veyron/veyron2/vtrace"
 )
 
+func copySpanRecord(in *vtrace.SpanRecord) *vtrace.SpanRecord {
+	return &vtrace.SpanRecord{
+		ID:          in.ID,
+		Parent:      in.Parent,
+		Name:        in.Name,
+		Start:       in.Start,
+		End:         in.End,
+		Annotations: append([]vtrace.Annotation{}, in.Annotations...),
+	}
+}
+
 // collectors collect spans and annotations for output or analysis.
 // collectors are safe to use from multiple goroutines simultaneously.
 // TODO(mattr): collector should support log-based collection
@@ -44,18 +55,53 @@
 	}
 }
 
-// annotate adds a span annotation to the collection.
-func (c *collector) annotate(s vtrace.Span, msg string) {
+func (c *collector) spanRecordLocked(s *span) *vtrace.SpanRecord {
+	sid := s.ID()
+	record, ok := c.spans[sid]
+	if !ok {
+		record = &vtrace.SpanRecord{
+			ID:     sid,
+			Parent: s.parent,
+			Name:   s.name,
+			Start:  s.start.UnixNano(),
+		}
+		c.spans[sid] = record
+	}
+	return record
+}
+
+// start records the fact that a given span has begun.
+func (c *collector) start(s *span) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
 
 	if c.method == vtrace.InMemory {
-		sid := s.ID()
-		record, ok := c.spans[sid]
-		if !ok {
-			record = &vtrace.SpanRecord{ID: sid, Parent: s.Parent(), Name: s.Name()}
-			c.spans[sid] = record
-		}
+		// Note that simply fetching the record is enough since
+		// if the record does not exist we will created it according
+		// to the start time in s.
+		c.spanRecordLocked(s)
+	}
+}
+
+// finish records the time that a span finished.
+func (c *collector) finish(s *span) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	if c.method == vtrace.InMemory {
+		record := c.spanRecordLocked(s)
+		// TODO(mattr): Perhaps we should log an error if we have already been finished?
+		record.End = time.Now().UnixNano()
+	}
+}
+
+// annotate adds a span annotation to the collection.
+func (c *collector) annotate(s *span, msg string) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	if c.method == vtrace.InMemory {
+		record := c.spanRecordLocked(s)
 		record.Annotations = append(record.Annotations, vtrace.Annotation{
 			When:    time.Now().UnixNano(),
 			Message: msg,
@@ -69,7 +115,7 @@
 	defer c.mu.Unlock()
 	return vtrace.Response{
 		Method: c.method,
-		Trace:  c.recordLocked(),
+		Trace:  c.traceRecordLocked(),
 	}
 }
 
@@ -78,18 +124,13 @@
 func (c *collector) Record() vtrace.TraceRecord {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	return c.recordLocked()
+	return c.traceRecordLocked()
 }
 
-func (c *collector) recordLocked() vtrace.TraceRecord {
+func (c *collector) traceRecordLocked() vtrace.TraceRecord {
 	spans := make([]vtrace.SpanRecord, 0, len(c.spans))
 	for _, span := range c.spans {
-		spans = append(spans, vtrace.SpanRecord{
-			ID:          span.ID,
-			Parent:      span.Parent,
-			Name:        span.Name,
-			Annotations: append([]vtrace.Annotation{}, span.Annotations...),
-		})
+		spans = append(spans, *copySpanRecord(span))
 	}
 	return vtrace.TraceRecord{
 		ID:    c.traceID,
@@ -110,12 +151,7 @@
 	// by assuming that children of parent need to start after parent
 	// and end before now.
 	for _, span := range t.Trace.Spans {
-		c.spans[span.ID] = &vtrace.SpanRecord{
-			ID:          span.ID,
-			Parent:      span.Parent,
-			Name:        span.Name,
-			Annotations: append([]vtrace.Annotation{}, span.Annotations...),
-		}
+		c.spans[span.ID] = copySpanRecord(&span)
 	}
 }
 
diff --git a/runtimes/google/vtrace/vtrace.go b/runtimes/google/vtrace/vtrace.go
index 0600677..3a41e41 100644
--- a/runtimes/google/vtrace/vtrace.go
+++ b/runtimes/google/vtrace/vtrace.go
@@ -4,6 +4,8 @@
 package vtrace
 
 import (
+	"time"
+
 	"veyron.io/veyron/veyron2/context"
 	"veyron.io/veyron/veyron2/uniqueid"
 	"veyron.io/veyron/veyron2/vlog"
@@ -16,6 +18,7 @@
 	parent    uniqueid.ID
 	name      string
 	collector *collector
+	start     time.Time
 }
 
 func newSpan(parent uniqueid.ID, name string, collector *collector) *span {
@@ -28,8 +31,9 @@
 		parent:    parent,
 		name:      name,
 		collector: collector,
+		start:     time.Now(),
 	}
-	s.Annotate("Started")
+	collector.start(s)
 	return s
 }
 
@@ -38,6 +42,7 @@
 func (c *span) Name() string        { return c.name }
 func (c *span) Trace() vtrace.Trace { return c.collector }
 func (c *span) Annotate(msg string) { c.collector.annotate(c, msg) }
+func (c *span) Finish()             { c.collector.finish(c) }
 
 // Request generates a vtrace.Request from the active Span.
 func Request(ctx context.T) vtrace.Request {
diff --git a/runtimes/google/vtrace/vtrace_test.go b/runtimes/google/vtrace/vtrace_test.go
index 44b7d53..66c8bd7 100644
--- a/runtimes/google/vtrace/vtrace_test.go
+++ b/runtimes/google/vtrace/vtrace_test.go
@@ -122,11 +122,15 @@
 }
 
 func summary(span *vtrace.SpanRecord) string {
-	msgs := []string{}
-	for _, annotation := range span.Annotations {
-		msgs = append(msgs, annotation.Message)
+	summary := span.Name
+	if len(span.Annotations) > 0 {
+		msgs := []string{}
+		for _, annotation := range span.Annotations {
+			msgs = append(msgs, annotation.Message)
+		}
+		summary += ": " + strings.Join(msgs, ", ")
 	}
-	return span.Name + ": " + strings.Join(msgs, ",")
+	return summary
 }
 
 func expectSequence(t *testing.T, trace vtrace.TraceRecord, expectedSpans []string) {
@@ -135,16 +139,31 @@
 	}
 
 	spans := map[string]*vtrace.SpanRecord{}
+	summaries := []string{}
 	for i := range trace.Spans {
 		span := &trace.Spans[i]
+
+		// All spans should have a start.
+		if span.Start == 0 {
+			t.Errorf("span missing start: %#v", span)
+		}
+		// All spans except the root should have an end.
+		if span.Name != "" && span.End == 0 {
+			t.Errorf("span missing end: %#v", span)
+			if span.Start >= span.End {
+				t.Errorf("span end should be after start: %#v", span)
+			}
+		}
+
 		summary := summary(span)
+		summaries = append(summaries, summary)
 		spans[summary] = span
 	}
 
 	for i := range expectedSpans {
 		child, ok := spans[expectedSpans[i]]
 		if !ok {
-			t.Errorf("expected span not found: %s", expectedSpans[i])
+			t.Errorf("expected span %s not found in %#v", expectedSpans[i], summaries)
 			continue
 		}
 		if i == 0 {
@@ -152,7 +171,7 @@
 		}
 		parent, ok := spans[expectedSpans[i-1]]
 		if !ok {
-			t.Errorf("expected span not found: %s", expectedSpans[i-1])
+			t.Errorf("expected span %s not found in %#v", expectedSpans[i-1], summaries)
 			continue
 		}
 		if child.Parent != parent.ID {
@@ -207,11 +226,11 @@
 	span.Annotate("c0-end")
 
 	expectedSpans := []string{
-		": c0-begin,c0-end",
-		"Client Call: c1.Run: Started,Finished",
-		"Server Call: .Run: c1-begin,c1-end",
-		"Client Call: c2.Run: Started,Finished",
-		"Server Call: .Run: c2-begin,c2-end",
+		": c0-begin, c0-end",
+		"Client Call: c1.Run",
+		"Server Call: .Run: c1-begin, c1-end",
+		"Client Call: c2.Run",
+		"Server Call: .Run: c2-begin, c2-end",
 	}
 	expectSequence(t, span.Trace().Record(), expectedSpans)
 }
@@ -228,10 +247,10 @@
 
 	expectedSpans := []string{
 		": c0-end",
-		"Client Call: c1.Run: Finished",
+		"Client Call: c1.Run",
 		"Server Call: .Run: c1-end",
-		"Client Call: c2.Run: Finished",
-		"Server Call: .Run: c2-begin,c2-end",
+		"Client Call: c2.Run",
+		"Server Call: .Run: c2-begin, c2-end",
 	}
 	expectSequence(t, span.Trace().Record(), expectedSpans)
 }
diff --git a/tools/naming/simulator/commands.go b/tools/naming/simulator/commands.go
index 3efbd5b..cf82ed2 100644
--- a/tools/naming/simulator/commands.go
+++ b/tools/naming/simulator/commands.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -21,18 +22,20 @@
 	needsHandle bool
 	fn          builtinCmd
 }{
-	"print":   {-1, "print <args>...", false, print},
-	"help":    {-1, "help", false, nil},
-	"set":     {-1, "set <var>=<val>...", false, set},
-	"splitEP": {-1, "splitEP", false, splitEP},
-	"assert":  {2, "val1 val2", false, assert},
-	"read":    {-1, "read <handle> [var]", true, read},
-	"eval":    {1, "eval <handle>", true, eval},
-	"wait":    {1, "wait <handle>", true, wait},
-	"stop":    {1, "stop <handle>", true, stop},
-	"stderr":  {1, "stderr <handle>", true, stderr},
-	"list":    {0, "list", false, list},
-	"quit":    {0, "quit", false, quit},
+	"print":      {-1, "print <args>...", false, print},
+	"help":       {-1, "help", false, nil},
+	"set":        {-1, "set <var>=<val>...", false, set},
+	"json_set":   {-1, "<var>...", false, json_set},
+	"json_print": {0, "", false, json_print},
+	"splitEP":    {-1, "splitEP", false, splitEP},
+	"assert":     {2, "val1 val2", false, assert},
+	"read":       {-1, "read <handle> [var]", true, read},
+	"eval":       {1, "eval <handle>", true, eval},
+	"wait":       {1, "wait <handle>", true, wait},
+	"stop":       {1, "stop <handle>", true, stop},
+	"stderr":     {1, "stderr <handle>", true, stderr},
+	"list":       {0, "list", false, list},
+	"quit":       {0, "quit", false, quit},
 }
 
 func init() {
@@ -222,3 +225,22 @@
 	l := handle.Session.ReadLine()
 	return l, handle.Session.Error()
 }
+
+func json_set(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+	for _, k := range args {
+		if v, present := sh.GetVar(k); present {
+			jsonDict[k] = v
+		} else {
+			return "", fmt.Errorf("unrecognised variable: %q", k)
+		}
+	}
+	return "", nil
+}
+
+func json_print(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+	bytes, err := json.Marshal(jsonDict)
+	if err != nil {
+		return "", err
+	}
+	return string(bytes), nil
+}
diff --git a/tools/naming/simulator/driver.go b/tools/naming/simulator/driver.go
index 31e6038..c6abba2 100644
--- a/tools/naming/simulator/driver.go
+++ b/tools/naming/simulator/driver.go
@@ -33,11 +33,13 @@
 var (
 	interactive bool
 	handles     map[string]*cmdState
+	jsonDict    map[string]string
 )
 
 func init() {
 	flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
 	handles = make(map[string]*cmdState)
+	jsonDict = make(map[string]string)
 	flag.Usage = usage
 }
 
@@ -126,7 +128,9 @@
 			}
 			if err := process(shell, line, lineno); err != nil {
 				fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
-				os.Exit(1)
+				if !interactive {
+					os.Exit(1)
+				}
 			}
 		}
 		shell.SetVar("_", strconv.Itoa(lineno))
@@ -194,11 +198,8 @@
 			expect.NewSession(nil, handle.Stdout(), time.Minute),
 			line,
 		}
-		if !interactive {
-			fmt.Printf("%d> %s\n", lineno, line)
-		}
+		output(lineno, line)
 	}
-
 	return nil
 }
 
diff --git a/tools/naming/simulator/json_example.scr b/tools/naming/simulator/json_example.scr
new file mode 100644
index 0000000..1fe1e3e
--- /dev/null
+++ b/tools/naming/simulator/json_example.scr
@@ -0,0 +1,29 @@
+
+cache off
+
+set localaddr=--veyron.tcp.address=127.0.0.1:0
+
+root -- $localaddr
+set root=$_
+eval $root
+set ROOT_NAME=$MT_NAME
+read $root
+eval $root
+set ROOT_PID=$PID
+json_set ROOT_NAME ROOT_PID
+
+set PROXY_MOUNTPOINT=$ROOT_NAME/proxy/mp
+proxyd -- $localaddr $PROXY_MOUNTPOINT
+set proxyd=$_
+read $proxyd
+eval $proxyd
+set PROXYD_NAME=$PROXY_NAME
+splitEP $PROXY_NAME
+set PROXY_HOST_ADDR=$P2
+json_set PROXY_MOUNTPOINT PROXY_NAME PROXY_HOST_ADDR
+
+
+json_print
+
+# uncomment wait $root to have the script leave all of the processes running
+#wait $root
diff --git a/tools/servicerunner/main.go b/tools/servicerunner/main.go
new file mode 100644
index 0000000..366e8f4
--- /dev/null
+++ b/tools/servicerunner/main.go
@@ -0,0 +1,105 @@
+// This binary starts several services (mount table, proxy, wspr), then prints a
+// JSON map with their vars to stdout (as a single line), then waits forever.
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"veyron.io/veyron/veyron2/rt"
+
+	"veyron.io/veyron/veyron/lib/expect"
+	"veyron.io/veyron/veyron/lib/modules"
+	"veyron.io/veyron/veyron/lib/modules/core"
+	_ "veyron.io/veyron/veyron/profiles"
+)
+
+func panicOnError(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+// updateVars captures the vars from the given Handle's stdout and adds them to
+// the given vars map, overwriting existing entries.
+func updateVars(h modules.Handle, vars map[string]string, varNames ...string) error {
+	varsToAdd := map[string]bool{}
+	for _, v := range varNames {
+		varsToAdd[v] = true
+	}
+	numLeft := len(varsToAdd)
+
+	s := expect.NewSession(nil, h.Stdout(), 10*time.Second)
+	for {
+		l := s.ReadLine()
+		if err := s.OriginalError(); err != nil {
+			return err // EOF or otherwise
+		}
+		parts := strings.Split(l, "=")
+		if len(parts) != 2 {
+			return fmt.Errorf("Unexpected line: %s", l)
+		}
+		if _, ok := varsToAdd[parts[0]]; ok {
+			numLeft--
+			vars[parts[0]] = parts[1]
+			if numLeft == 0 {
+				break
+			}
+		}
+	}
+	return nil
+}
+
+func main() {
+	rt.Init()
+
+	// TODO(sadovsky): It would be better if Dispatch() itself performed the env
+	// check.
+	if os.Getenv(modules.ShellEntryPoint) != "" {
+		panicOnError(modules.Dispatch())
+		return
+	}
+
+	sh := modules.NewShell()
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	// TODO(sadovsky): Shell only does this for tests. It would be better if it
+	// either always did it or never did it.
+	if os.Getenv("VEYRON_CREDENTIALS") == "" {
+		panicOnError(sh.CreateAndUseNewCredentials())
+	}
+	// TODO(sadovsky): The following line will not be needed if the modules
+	// library is restructured per my proposal.
+	core.Install(sh)
+
+	vars := map[string]string{}
+
+	h, err := sh.Start("root", "--", "--veyron.tcp.address=127.0.0.1:0")
+	panicOnError(err)
+	updateVars(h, vars, "MT_NAME")
+
+	// Set NAMESPACE_ROOT env var, consumed downstream by proxyd among others.
+	// NOTE(sadovsky): If this is not set, proxyd takes several seconds to start;
+	// if it is set, proxyd starts instantly. Fun!
+	sh.SetVar("NAMESPACE_ROOT", vars["MT_NAME"])
+
+	// NOTE(sadovsky): The proxyd binary requires --protocol and --address flags
+	// while the proxyd command instead uses ListenSpec flags.
+	h, err = sh.Start("proxyd", "--", "--veyron.tcp.address=127.0.0.1:0", "p")
+	panicOnError(err)
+	updateVars(h, vars, "PROXY_ADDR")
+
+	// TODO(sadovsky): Which identd should we be using?
+	h, err = sh.Start("wsprd", "--", "--veyron.proxy="+vars["PROXY_ADDR"], "--identd=/proxy.envyor.com:8101/identity/veyron-test/google")
+	panicOnError(err)
+	updateVars(h, vars, "WSPR_ADDR")
+
+	bytes, err := json.Marshal(vars)
+	panicOnError(err)
+	fmt.Println(string(bytes))
+
+	// Wait to be killed.
+	select {}
+}