veyron/lib/modules: fix a test -race problem.

Closing a pipe which has an outstanding read leads a -race failure.
This is unavoidable given the underlying UNIX API, so we work around
it by using sycall.Close on the file descriptor rather than the Close
method on the io.Closer interface.

Change-Id: If576b549a36c9e1fd4cd7daa0d270e7c21e43a38
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..0d5e42e
--- /dev/null
+++ b/lib/modules/core/core_test.go
@@ -0,0 +1,152 @@
+package core_test
+
+import (
+	"os"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"veyron2/rt"
+
+	"veyron/lib/expect"
+	"veyron/lib/modules"
+	"veyron/lib/modules/core"
+	_ "veyron/lib/testutil"
+)
+
+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/func.go b/lib/modules/func.go
index 5d6160c..e5fd73b 100644
--- a/lib/modules/func.go
+++ b/lib/modules/func.go
@@ -6,6 +6,7 @@
 	"io"
 	"os"
 	"sync"
+	"syscall"
 )
 
 type pipe struct {
@@ -55,9 +56,10 @@
 		if err != nil {
 			fmt.Fprintf(fh.stderr.w, "%s\n", err)
 		}
-		fh.stdin.r.Close()
-		fh.stdout.w.Close()
-		fh.stderr.w.Close()
+		// See the comment below in Shutdown.
+		syscall.Close(int(fh.stdin.r.Fd()))
+		syscall.Close(int(fh.stdout.w.Fd()))
+		syscall.Close(int(fh.stderr.w.Fd()))
 	}()
 	return fh, nil
 }
@@ -69,7 +71,10 @@
 	for scanner.Scan() {
 		fmt.Fprintf(output, "%s\n", scanner.Text())
 	}
-	fh.stdin.w.Close()
-	fh.stdout.r.Close()
-	fh.stderr.r.Close()
+	// We close these files using the Close system call since there
+	// may be an oustanding read on them that would otherwise trigger
+	// a test failure with go test -race
+	syscall.Close(int(fh.stdin.w.Fd()))
+	syscall.Close(int(fh.stdout.r.Fd()))
+	syscall.Close(int(fh.stderr.r.Fd()))
 }
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
+		}
+	}
+}