veyron/lib/modules/core: add some initial core modules for naming.

Change-Id: I37edd9686b8fb52deafb835bd4b2f8ec7ada4565
diff --git a/lib/modules/core/core.go b/lib/modules/core/core.go
new file mode 100644
index 0000000..40da4c7
--- /dev/null
+++ b/lib/modules/core/core.go
@@ -0,0 +1,25 @@
+// Package core provides modules.Shell instances with commands preinstalled for
+// common core services such as naming, security etc.
+package core
+
+import "veyron/lib/modules"
+
+const (
+	RootMTCommand     = "root"
+	MTCommand         = "mt"
+	LSCommand         = "ls"
+	LSExternalCommand = "lse"
+	MountCommand      = "mount"
+)
+
+func NewShell() *modules.Shell {
+	shell := modules.NewShell()
+	shell.AddSubprocess(RootMTCommand, "")
+	shell.AddSubprocess(MTCommand, `<mount point>
+	reads NAMESPACE_ROOT from its environment and mounts a new mount table at <mount point>`)
+	shell.AddFunction(LSCommand, ls, `<glob>...
+	issues glob requests using the current processes namespace library`)
+	shell.AddSubprocess(LSExternalCommand, `<glob>...
+	runs a subprocess to issue glob requests using the subprocesses namespace library`)
+	return shell
+}
diff --git a/lib/modules/core/core_test.go b/lib/modules/core/core_test.go
new file mode 100644
index 0000000..161890a
--- /dev/null
+++ b/lib/modules/core/core_test.go
@@ -0,0 +1,151 @@
+package core_test
+
+import (
+	"os"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"veyron2/rt"
+
+	"veyron/lib/expect"
+	"veyron/lib/modules"
+	"veyron/lib/modules/core"
+)
+
+func TestCommands(t *testing.T) {
+	shell := core.NewShell()
+	defer shell.Cleanup(os.Stderr)
+	for _, c := range []string{core.RootMTCommand, core.MTCommand} {
+		if len(shell.Help(c)) == 0 {
+			t.Fatalf("missing command %q", c)
+		}
+	}
+}
+
+func init() {
+	rt.Init()
+}
+
+func TestRoot(t *testing.T) {
+	shell := core.NewShell()
+	defer shell.Cleanup(os.Stderr)
+
+	root, err := shell.Start(core.RootMTCommand)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, root.Stdout(), time.Second)
+	s.ExpectVar("MT_NAME")
+	s.ExpectVar("PID")
+	root.Stdin().Close()
+	s.Expect("done")
+}
+
+func getMatchingMountpoint(r [][]string) string {
+	if len(r) != 1 {
+		return ""
+	}
+	shortest := ""
+	for _, p := range r[0][1:] {
+		if len(p) > 0 {
+			if len(shortest) == 0 {
+				shortest = p
+			}
+			if len(shortest) > 0 && len(p) < len(shortest) {
+				shortest = p
+			}
+		}
+	}
+	return shortest
+}
+
+func TestMountTableAndGlob(t *testing.T) {
+	shell := core.NewShell()
+	if testing.Verbose() {
+		defer shell.Cleanup(os.Stderr)
+	} else {
+		defer shell.Cleanup(nil)
+	}
+
+	// Start root mount table
+	root, err := shell.Start(core.RootMTCommand)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rootSession := expect.NewSession(t, root.Stdout(), time.Second)
+	rootName := rootSession.ExpectVar("MT_NAME")
+	shell.SetVar("NAMESPACE_ROOT", rootName)
+
+	if t.Failed() {
+		return
+	}
+	mountPoints := []string{"a", "b", "c"}
+
+	// Start 3 mount tables
+	for _, mp := range mountPoints {
+		_, err := shell.Start(core.MTCommand, mp)
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+	}
+
+	ls, err := shell.Start(core.LSCommand, rootName+"/...")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	lsSession := expect.NewSession(t, ls.Stdout(), time.Second)
+	lsSession.Expect(rootName)
+
+	// Look for names that correspond to the mountpoints above (i.e, a, b or c)
+	pattern := ""
+	for _, n := range mountPoints {
+		pattern = pattern + "(" + rootName + "/(" + n + ")$)|"
+	}
+	pattern = pattern[:len(pattern)-1]
+
+	found := []string{}
+	found = append(found, getMatchingMountpoint(lsSession.ExpectRE(pattern, 1)))
+	found = append(found, getMatchingMountpoint(lsSession.ExpectRE(pattern, 1)))
+	found = append(found, getMatchingMountpoint(lsSession.ExpectRE(pattern, 1)))
+	sort.Strings(found)
+	sort.Strings(mountPoints)
+	if got, want := found, mountPoints; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Run the ls command in a subprocess, with NAMESPACE_ROOT as set above.
+	lse, err := shell.Start(core.LSExternalCommand, "...")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	lseSession := expect.NewSession(t, lse.Stdout(), time.Second)
+
+	pattern = ""
+	for _, n := range mountPoints {
+		// Since the LSExternalCommand runs in a subprocess with NAMESPACE_ROOT
+		// set to the name of the root mount table it sees the relative name
+		// format of the mounted mount tables.
+		pattern = pattern + "(^" + n + "$)|"
+	}
+	pattern = pattern[:len(pattern)-1]
+	found = []string{}
+	found = append(found, getMatchingMountpoint(lseSession.ExpectRE(pattern, 1)))
+	found = append(found, getMatchingMountpoint(lseSession.ExpectRE(pattern, 1)))
+	found = append(found, getMatchingMountpoint(lseSession.ExpectRE(pattern, 1)))
+	sort.Strings(found)
+	sort.Strings(mountPoints)
+	if got, want := found, mountPoints; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestHelperProcess(t *testing.T) {
+	if !modules.IsTestHelperProcess() {
+		return
+	}
+	if err := modules.Dispatch(); err != nil {
+		t.Fatalf("failed: %v", err)
+	}
+}
diff --git a/lib/modules/core/mounttable.go b/lib/modules/core/mounttable.go
new file mode 100644
index 0000000..b7862f5
--- /dev/null
+++ b/lib/modules/core/mounttable.go
@@ -0,0 +1,98 @@
+package core
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"veyron2"
+	"veyron2/naming"
+	"veyron2/rt"
+
+	"veyron/lib/modules"
+	mounttable "veyron/services/mounttable/lib"
+)
+
+func init() {
+	modules.RegisterChild(RootMTCommand, rootMountTable)
+	modules.RegisterChild(MTCommand, mountTable)
+	modules.RegisterChild(LSExternalCommand, ls)
+}
+
+func mountTable(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	if len(args) != 1 {
+		return fmt.Errorf("expected exactly one argument: <mount point>")
+	}
+	return runMT(false, stdin, stdout, stderr, env, args...)
+}
+
+func rootMountTable(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	if len(args) != 0 {
+		return fmt.Errorf("expected no arguments")
+	}
+	return runMT(true, stdin, stdout, stderr, env, args...)
+}
+
+func runMT(root bool, stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	r := rt.Init()
+	server, err := r.NewServer(veyron2.ServesMountTableOpt(true))
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	mp := ""
+	if !root {
+		mp = args[0]
+	}
+	mt, err := mounttable.NewMountTable("")
+	if err != nil {
+		return fmt.Errorf("mounttable.NewMountTable failed: %s", err)
+	}
+	ep, err := server.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return fmt.Errorf("server.Listen failed: %s", err)
+	}
+	if err := server.Serve(mp, mt); err != nil {
+		return fmt.Errorf("root failed: %s", err)
+	}
+	name := naming.JoinAddressName(ep.String(), "")
+	fmt.Fprintf(os.Stderr, "Serving MountTable on %q", name)
+
+	fmt.Printf("MT_NAME=%s\n", name)
+	fmt.Printf("PID=%d\n", os.Getpid())
+	modules.WaitForEOF(stdin)
+	fmt.Println("done\n")
+	return nil
+}
+
+func ls(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	details := false
+	if len(args) > 0 && args[0] == "-l" {
+		details = true
+		args = args[1:]
+	}
+
+	ns := rt.R().Namespace()
+	for _, pattern := range args {
+		ch, err := ns.Glob(rt.R().NewContext(), pattern)
+		if err != nil {
+			return err
+		}
+		for n := range ch {
+			if details {
+				fmt.Fprintf(stdout, "%s [", n.Name)
+				t := ""
+				for _, s := range n.Servers {
+					t += fmt.Sprintf("%s:%ss, ", s.Server, s.TTL)
+				}
+				t = strings.TrimSuffix(t, ", ")
+				fmt.Fprintf(stdout, "%s]\n", t)
+			} else {
+				if len(n.Name) > 0 {
+					fmt.Fprintf(stdout, "%s\n", n.Name)
+				}
+			}
+		}
+	}
+	return nil
+}
diff --git a/lib/modules/modules_test.go b/lib/modules/modules_test.go
index 81739b7..7414358 100644
--- a/lib/modules/modules_test.go
+++ b/lib/modules/modules_test.go
@@ -23,8 +23,7 @@
 			fmt.Fprintf(stderr, "missing %s\n", a)
 		}
 	}
-	buf := [1]byte{0x0}
-	stdin.Read(buf[:])
+	modules.WaitForEOF(stdin)
 	fmt.Fprintf(stdout, "done\n")
 	return nil
 }
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 09941b8..4df120b 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -39,6 +39,7 @@
 	"bufio"
 	"fmt"
 	"io"
+	"strings"
 	"sync"
 
 	"veyron2/vlog"
@@ -104,10 +105,10 @@
 	sh.mu.Lock()
 	defer sh.mu.Unlock()
 	h := ""
-	for _, c := range sh.cmds {
-		h += c.help
+	for n, _ := range sh.cmds {
+		h += n + ", "
 	}
-	return h
+	return strings.TrimRight(h, ", ")
 }
 
 // Help returns the help message for the specified command.
@@ -115,7 +116,7 @@
 	sh.mu.Lock()
 	defer sh.mu.Unlock()
 	if c := sh.cmds[command]; c != nil {
-		return c.help
+		return command + ": " + c.help
 	}
 	return ""
 }
@@ -236,3 +237,12 @@
 type command interface {
 	start(sh *Shell, args ...string) (Handle, error)
 }
+
+func WaitForEOF(stdin io.Reader) {
+	buf := [1024]byte{}
+	for {
+		if _, err := stdin.Read(buf[:]); err == io.EOF {
+			return
+		}
+	}
+}