veyron/tools/naming/simulator: reworked to use new modules and to be more useful.
Change-Id: Ieef4412b995b289957326cc06f2c48990ba99112
diff --git a/lib/modules/core/core.go b/lib/modules/core/core.go
index 980313e..98a6656 100644
--- a/lib/modules/core/core.go
+++ b/lib/modules/core/core.go
@@ -53,6 +53,10 @@
ResolveMTCommand = "resolveMT"
EchoServerCommand = "echoServer"
EchoClientCommand = "echoClient"
+ SleepCommand = "sleep"
+ TimeCommand = "time"
+ MountCommand = "mount"
+ NamespaceCacheCommand = "cache"
)
// NewShell returns a new Shell instance with the core commands installed.
@@ -77,6 +81,14 @@
resolves name to obtain a mount table address`)
shell.AddFunction(SetNamespaceRootsCommand, setNamespaceRoots, `<name>...
set the in-process namespace roots to <name>...`)
+ shell.AddFunction(SleepCommand, sleep, `[duration]
+ sleep for a time (in go time.Duration format): defaults to 1s`)
+ shell.AddFunction(TimeCommand, now, `
+ prints the current time`)
+ shell.AddFunction(NamespaceCacheCommand, namespaceCache, `on|off
+ turns the namespace cache on or off`)
+ shell.AddFunction(MountCommand, mountServer, `<mountpoint> <server> <ttl>
+ invokes namespace.Mount(<mountpoint>, <server>, <ttl>)`)
shell.AddSubprocess(EchoClientCommand, `<name> <message>...
invokes name.Echo(message)`)
shell.AddSubprocess(EchoServerCommand, `<name> <text>
diff --git a/lib/modules/core/echo.go b/lib/modules/core/echo.go
index ae37372..55bc787 100644
--- a/lib/modules/core/echo.go
+++ b/lib/modules/core/echo.go
@@ -8,6 +8,7 @@
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron/lib/modules"
)
@@ -17,19 +18,29 @@
modules.RegisterChild(EchoClientCommand, echoClient)
}
+type treeDispatcher struct{ id string }
+
+func (d treeDispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
+ return ipc.ReflectInvoker(&echoServerObject{d.id, suffix}), nil, nil
+}
+
type echoServerObject struct {
- id string
+ id, suffix string
}
func (es *echoServerObject) Echo(call ipc.ServerCall, m string) (string, error) {
- return es.id + ": " + m + "\n", nil
+ if len(es.suffix) > 0 {
+ return fmt.Sprintf("%s.%s: %s\n", es.id, es.suffix, m), nil
+ }
+ return fmt.Sprintf("%s: %s\n", es.id, m), nil
}
func echoServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
if len(args) != 2 {
return fmt.Errorf("wrong # args")
}
- id, mountPoint := args[0], args[1]
+ id, mp := args[0], args[1]
+ disp := &treeDispatcher{id: id}
server, err := rt.R().NewServer()
if err != nil {
return err
@@ -39,7 +50,7 @@
if err != nil {
return err
}
- if err := server.Serve(mountPoint, ipc.LeafDispatcher(&echoServerObject{id: id}, nil)); err != nil {
+ if err := server.Serve(mp, disp); err != nil {
return err
}
fmt.Fprintf(stdout, "NAME=%s\n", naming.MakeTerminal(naming.JoinAddressName(ep.String(), "")))
@@ -50,7 +61,6 @@
}
func echoClient(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
-
if len(args) < 2 {
return fmt.Errorf("wrong # args")
}
diff --git a/lib/modules/core/misc.go b/lib/modules/core/misc.go
new file mode 100644
index 0000000..e0c1496
--- /dev/null
+++ b/lib/modules/core/misc.go
@@ -0,0 +1,64 @@
+package core
+
+import (
+ "fmt"
+ "io"
+ "time"
+
+ "veyron.io/veyron/veyron2/naming"
+ "veyron.io/veyron/veyron2/rt"
+)
+
+func sleep(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ d := time.Second
+ if len(args) > 0 {
+ var err error
+ if d, err = time.ParseDuration(args[0]); err != nil {
+ return err
+ }
+ }
+ fmt.Fprintf(stdout, "Sleeping for %s", d)
+ // TODO(cnicolaou): we should probably also listen for stdin closing
+ // and return before the sleep completes.
+ time.Sleep(d)
+ return nil
+}
+
+func now(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ fmt.Fprintf(stdout, "%s\n", time.Now())
+ return nil
+}
+
+func mountServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ if len(args) != 3 {
+ return fmt.Errorf("wrong # args")
+ }
+ mp, server, ttlstr := args[0], args[1], args[2]
+ ttl, err := time.ParseDuration(ttlstr)
+ if err != nil {
+ return fmt.Errorf("failed to parse time from %q", ttlstr)
+ }
+ ns := rt.R().Namespace()
+ if err := ns.Mount(rt.R().NewContext(), mp, server, ttl); err != nil {
+ return err
+ }
+ fmt.Fprintf(stdout, "Mount(%s, %s, %s)\n", mp, server, ttl)
+ return nil
+}
+
+func namespaceCache(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ if len(args) != 1 {
+ return fmt.Errorf("wrong # args")
+ }
+ disable := true
+ switch args[0] {
+ case "on":
+ disable = false
+ case "off":
+ disable = true
+ default:
+ return fmt.Errorf("arg must be 'on' or 'off'")
+ }
+ rt.R().Namespace().CacheCtl(naming.DisableCache(disable))
+ return nil
+}
diff --git a/lib/modules/core/mounttable.go b/lib/modules/core/mounttable.go
index 1c523e0..8d63006 100644
--- a/lib/modules/core/mounttable.go
+++ b/lib/modules/core/mounttable.go
@@ -58,6 +58,7 @@
}
name := naming.JoinAddressName(ep.String(), "")
fmt.Fprintf(stdout, "MT_NAME=%s\n", name)
+ fmt.Fprintf(stdout, "MT_ADDR=%s\n", ep.String())
fmt.Fprintf(stdout, "PID=%d\n", os.Getpid())
modules.WaitForEOF(stdin)
return nil
@@ -82,7 +83,7 @@
output += fmt.Sprintf("R%d=%s[", entry, n.Name)
t := ""
for _, s := range n.Servers {
- output += fmt.Sprintf("%s:%ss, ", s.Server, s.TTL)
+ t += fmt.Sprintf("%s:%s, ", s.Server, s.TTL)
}
t = strings.TrimSuffix(t, ", ")
output += fmt.Sprintf("%s]\n", t)
@@ -110,6 +111,7 @@
name := args[0]
servers, err := fn(rt.R().NewContext(), name)
if err != nil {
+ fmt.Fprintf(stdout, "RN=0\n")
return err
}
fmt.Fprintf(stdout, "RN=%d\n", len(servers))
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index 20137b1..e2e35db 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -54,8 +54,9 @@
return fl
}
-// IsTestSubprocess returns true if it is called in via -run=TestHelperProcess
-// which normally only ever happens for subprocess run from tests.
+// IsTestHelperProces returns true if it is called in via
+// -run=TestHelperProcess which normally only ever happens for subprocesses
+// run from tests.
func IsTestHelperProcess() bool {
runFlag := flag.Lookup("test.run")
if runFlag == nil {
@@ -188,7 +189,7 @@
return eh.cmd.Wait()
}
-const shellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
+const ShellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
func RegisterChild(name string, main Main) {
child.Lock()
@@ -208,9 +209,9 @@
}
func (child *childRegistrar) dispatch() error {
- command := os.Getenv(shellEntryPoint)
+ command := os.Getenv(ShellEntryPoint)
if len(command) == 0 {
- return fmt.Errorf("Failed to find entrypoint %q", shellEntryPoint)
+ return fmt.Errorf("Failed to find entrypoint %q", ShellEntryPoint)
}
child.Lock()
m := child.mains[command]
diff --git a/lib/modules/func.go b/lib/modules/func.go
index f44127b..319a673 100644
--- a/lib/modules/func.go
+++ b/lib/modules/func.go
@@ -70,6 +70,9 @@
err := main(stdin, stdout, stderr, sh.mergeOSEnv(), args...)
if err != nil {
+ // Print the error to stdout to ensure that anyone reading
+ // only stdout sees the error.
+ fmt.Fprintf(stdout, "%s\n", err)
fmt.Fprintf(stderr, "%s\n", err)
}
diff --git a/lib/modules/modules_internal_test.go b/lib/modules/modules_internal_test.go
index 920538d..000819b 100644
--- a/lib/modules/modules_internal_test.go
+++ b/lib/modules/modules_internal_test.go
@@ -31,6 +31,7 @@
func TestState(t *testing.T) {
sh := NewShell()
+
sh.AddSubprocess("echonotregistered", "[args]*")
sh.AddSubprocess("echos", "[args]*")
sh.AddFunction("echof", Echo, "[args]*")
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 2e35c97..561e3b2 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -129,7 +129,7 @@
if !child.hasCommand(name) {
vlog.Infof("Warning: %q is not registered with modules.Dispatcher", name)
}
- entryPoint := shellEntryPoint + "=" + name
+ entryPoint := ShellEntryPoint + "=" + name
sh.mu.Lock()
sh.cmds[name] = &commandDesc{func() command { return newExecHandle(entryPoint) }, help}
sh.mu.Unlock()
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 2dfecec..ebabe5a 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -493,7 +493,7 @@
return fc.close(verror.ConvertWithDefault(verror.Internal, fc.response.Error))
}
if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
- return fc.close(verror.BadProtocolf("ipc: server sent %d results, client expected %d", got, want))
+ return fc.close(verror.BadProtocolf("ipc: server sent %d results, client expected %d (%#v)", got, want, resultptrs))
}
for ix, r := range resultptrs {
if err := fc.dec.Decode(r); err != nil {
diff --git a/tools/naming/simulator/ambiguity.scr b/tools/naming/simulator/ambiguity.scr
new file mode 100644
index 0000000..2974986
--- /dev/null
+++ b/tools/naming/simulator/ambiguity.scr
@@ -0,0 +1,45 @@
+# This is p's 'ambiguity' example
+#
+# mountMT("/s1/a", "/s2/b")
+# mountMT("/s2/b", "/s3/c")
+# mount("/s3/c", "/s4/d")
+#
+# Bogdan points out that: 'we will actually have d == "" in the echo example
+# below (since Serve only mounts endpoints without suffixes)'
+#
+# I'm not using any local names because its easier to not get confused
+# this way.
+#
+# resolve("/s1/a") can now have 4 possible objects, the mount point in server1,
+# the mount point in server2, the mount point in server3, and the object d in
+# server4. When we make a call, like SetACL, how do we tell which one to
+# modify? If we glob("/s1/a") we get:
+#
+# "s1/a", ["/s2/b"(mountpoint)]
+# "s1/a", ["/s3/c"(mountpoint)]
+# "s1/a", ["/s4/d"(mountpoint)]
+# "s1/a", []
+
+root
+eval $_
+set s1=$MT_NAME
+
+root
+eval $_
+set s2=$MT_NAME
+
+root
+eval $_
+set s3=$MT_NAME
+
+mount $s1/a $s2/b 1h
+
+mount $s2/b $s3/c 1h
+
+echoServer "Echo" $s3/c
+
+ls $s1/...
+wait $_
+
+ls $s1/a
+wait $_
diff --git a/tools/naming/simulator/clock.scr b/tools/naming/simulator/clock.scr
deleted file mode 100644
index ac1dd39..0000000
--- a/tools/naming/simulator/clock.scr
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Simple example to show how names work and are used both without
-# and with a mount table.
-#
-
-# A 'stand-alone' server
-clockServer "" ""
-set STAND_ALONE_CLOCK_NAME=$NAME
-
-set N=$STAND_ALONE_CLOCK_NAME
-print "Stand alone clock server at" $N
-time $N "Using $N"
-set N=/$ADDR
-time $N "Using $N"
-
-# Run a root MountTable.
-rootMT
-set ROOT_ADDR=$MT_ADDR ROOT_NAME=$MT_NAME
-print ""
-print "Root MountTable at $ROOT_NAME"
-
-clockServer $MT_NAME clock
-set CLOCK_NAME=/$ADDR
-print "Running Clock Server at $CLOCK_NAME"
-
-# Still bypassing the MountTable
-time $CLOCK_NAME bar
-
-# Now, let's use the MountTable
-setLocalRoots $ROOT_NAME
-
-set N=clock
-resolve $N
-print $N -> $R0
-
-set N=/$ROOT_ADDR/clock
-resolve $N
-print $N -> $R0
-
-# ls * returns clock
-ls *
-# ls ... returns "" and clock - i.e two items. Is this a bug?
-ls ...
-
-# These all behave as above
-lsmt $ROOT_NAME *
-lsmt $ROOT_NAME ...
-
-# Conclusion: some of this behaviour seems a little awkward. In particular:
-#
-# The client neeeds to use a different form of the name depending on whether
-# a MountTable is used or not. If a MountTable is not used, then the internal
-# 'suffix' (/mt) in the examples above must be used. If a MountTable is used
-# then the internal suffix must not be included in the name.
-#
-# ls ... seems to always return an extra, zero length, string as entry
-#
diff --git a/tools/naming/simulator/commands.go b/tools/naming/simulator/commands.go
index 712e2e6..b05538a 100644
--- a/tools/naming/simulator/commands.go
+++ b/tools/naming/simulator/commands.go
@@ -2,142 +2,171 @@
import (
"fmt"
- "time"
+ "os"
+ "regexp"
+ "strings"
- "veyron.io/veyron/veyron/lib/testutil/modules"
-
- "veyron.io/veyron/veyron2/vlog"
+ "veyron.io/veyron/veyron/lib/modules"
)
-type tag int
+type builtinCmd func(sh *modules.Shell, state *cmdState, args ...string) (string, error)
-const (
- helpTag tag = iota
- getTag
- setTag
- printTag
- sleepTag
-)
+var varRE = regexp.MustCompile("(.*)=(.*)")
-type builtin struct{ tag }
-
-func helpF() modules.T {
- return &builtin{helpTag}
+var builtins = map[string]*struct {
+ nargs int
+ usage string
+ needsHandle bool
+ fn builtinCmd
+}{
+ "print": {-1, "print <args>...", false, print},
+ "help": {-1, "help", false, nil},
+ "set": {-1, "set <var>=<val>...", false, set},
+ "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},
+ "list": {0, "list", false, list},
+ "quit": {0, "quit", false, quit},
}
-func sleepF() modules.T {
- return &builtin{sleepTag}
+func init() {
+ builtins["help"].fn = help
}
-func getF() modules.T {
- return &builtin{getTag}
+func print(_ *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ r := strings.Join(args, " ")
+ return r, nil
}
-func setF() modules.T {
- return &builtin{setTag}
-}
-
-func printF() modules.T {
- return &builtin{printTag}
-}
-
-func (b *builtin) Help() string {
- switch b.tag {
- case helpTag:
- return "[command]"
- case sleepTag:
- return `[duration]
- sleep for a time (in go time.Duration format): defaults to 1s`
- case getTag:
- return "[<global variable name>]*"
- case setTag:
- return "[<var>=<val>]+"
- case printTag:
- return "[$<var>]*"
- default:
- return fmt.Sprintf("unrecognised tag for builtin: %d", b.tag)
- }
-}
-
-func (*builtin) Daemon() bool { return false }
-
-func (b *builtin) Run(args []string) (modules.Variables, []string, modules.Handle, error) {
- switch b.tag {
- case helpTag:
- return helpCmd(args)
- case sleepTag:
- return sleep(args)
- case getTag:
- return get(args)
- case setTag:
- return set(args)
- case printTag:
- return print(args)
- default:
- return nil, nil, nil, fmt.Errorf("unrecognised tag for builtin: %d",
- b.tag)
- }
-}
-
-func helpCmd([]string) (modules.Variables, []string, modules.Handle, error) {
- for k, v := range commands {
- if k == "help" {
- continue
- }
- h := v().Help()
- if len(h) > 0 {
- fmt.Printf("%s %s\n\n", k, h)
- } else {
- fmt.Println(k)
- }
- }
- return nil, nil, nil, nil
-}
-
-func sleep(args []string) (modules.Variables, []string, modules.Handle, error) {
+func help(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ r := ""
if len(args) == 0 {
- vlog.Infof("Sleeping for %s", time.Second)
- time.Sleep(time.Second)
- return nil, nil, nil, nil
- }
- if d, err := time.ParseDuration(args[0]); err != nil {
- return nil, nil, nil, err
- } else {
- vlog.Infof("Sleeping for %s", d)
- time.Sleep(d)
- }
- return nil, nil, nil, nil
-}
-
-func get(args []string) (modules.Variables, []string, modules.Handle, error) {
- var r []string
- if len(args) == 0 {
- for k, v := range globals {
- r = append(r, fmt.Sprintf("\t%q=%q\n", k, v))
+ for k, _ := range builtins {
+ if k == "help" {
+ continue
+ }
+ r += k + ", "
}
+ r += sh.String()
+ return r, nil
} else {
for _, a := range args {
- if v, present := globals[a]; present {
- r = append(r, fmt.Sprintf("\t%q=%q\n", a, v))
+ if v := builtins[a]; v != nil {
+ r += v.usage + "\n"
+ continue
+ }
+ h := sh.Help(a)
+ if len(h) == 0 {
+ return "", fmt.Errorf("unknown command: %q", a)
} else {
- return nil, nil, nil, fmt.Errorf("unknown variable %q", a)
+ r += h
}
}
}
- return nil, r, nil, nil
+ return r, nil
}
-func set(args []string) (modules.Variables, []string, modules.Handle, error) {
- for _, a := range args {
- globals.UpdateFromString(a)
+func parseVar(expr string) (string, string, error) {
+ m := varRE.FindAllStringSubmatch(expr, 1)
+ if len(m) != 1 || len(m[0]) != 3 {
+ return "", "", fmt.Errorf("%q is not an assignment statement", expr)
}
- return nil, nil, nil, nil
+ return m[0][1], m[0][2], nil
}
-func print(args []string) (modules.Variables, []string, modules.Handle, error) {
- var r []string
- for _, a := range args {
- r = append(r, a)
+func set(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ r := ""
+ if len(args) == 0 {
+ for _, v := range sh.Env() {
+ r += v + "\n"
+ }
+ return r, nil
}
- return nil, r, nil, nil
+ for _, a := range args {
+ k, v, err := parseVar(a)
+ if err != nil {
+ return "", err
+ }
+ sh.SetVar(k, v)
+ }
+ return "", nil
+}
+
+func assert(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ if args[0] != args[1] {
+ return "", fmt.Errorf("assertion failed: %q != %q", args[0], args[1])
+ }
+ return "", nil
+}
+
+func handleWrapper(sh *modules.Shell, fn builtinCmd, args ...string) (string, error) {
+ if len(args) < 1 {
+ return "", fmt.Errorf("missing handle argument")
+ }
+ state := handles[args[0]]
+ if state == nil {
+ return "", fmt.Errorf("invalid handle")
+ }
+ return fn(sh, state, args...)
+}
+
+func read(sh *modules.Shell, state *cmdState, args ...string) (string, error) {
+ l := state.Session.ReadLine()
+ for _, a := range args[1:] {
+ sh.SetVar(a, l)
+ }
+ err := state.Session.Error()
+ if err != nil && strings.HasSuffix(err.Error(), "EOF") {
+ return l, fmt.Errorf("EOF")
+ }
+ return l, state.Session.Error()
+}
+
+func eval(sh *modules.Shell, state *cmdState, args ...string) (string, error) {
+ l := state.Session.ReadLine()
+ k, v, err := parseVar(l)
+ if err != nil {
+ return "", err
+ }
+ sh.SetVar(k, v)
+ return l, nil
+}
+
+func wait(sh *modules.Shell, state *cmdState, args ...string) (string, error) {
+ state.Handle.CloseStdin()
+ r, err := state.Session.Finish(nil)
+ delete(handles, args[0])
+ return r, err
+}
+
+func list(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ r := ""
+ for h, v := range handles {
+ r += h + ": " + v.line + "\n"
+ }
+ return r, nil
+}
+
+func quit(sh *modules.Shell, _ *cmdState, args ...string) (string, error) {
+ r := ""
+ for k, h := range handles {
+ if err := h.Handle.Shutdown(os.Stdout); err != nil {
+ r += fmt.Sprintf("%s: %v\n", k, err)
+ } else {
+ r += fmt.Sprintf("%s: ok\n", k)
+ }
+ }
+ fmt.Fprintf(os.Stdout, r)
+ os.Exit(0)
+ panic("unreachable")
+}
+
+func getLine(sh *modules.Shell, args ...string) (string, error) {
+ handle := handles[args[0]]
+ if handle == nil {
+ return "", fmt.Errorf("invalid handle")
+ }
+ l := handle.Session.ReadLine()
+ return l, handle.Session.Error()
}
diff --git a/tools/naming/simulator/driver.go b/tools/naming/simulator/driver.go
index 207c626..3643eda 100644
--- a/tools/naming/simulator/driver.go
+++ b/tools/naming/simulator/driver.go
@@ -2,8 +2,8 @@
// services as subprocesses and testing interactions between them. It is
// structured as an interpreter, with global variables and variable
// expansion, but no control flow. The command set that it supports is
-// extendable by adding new 'modules' that implement the API defined
-// by veyron/lib/testutil/modules.
+// extendable by adding new 'commands' that implement the API defined
+// by veyron/lib/modules.
package main
import (
@@ -11,55 +11,78 @@
"flag"
"fmt"
"os"
+ "strconv"
"strings"
+ "time"
"unicode"
- "veyron.io/veyron/veyron/lib/testutil/modules"
-
"veyron.io/veyron/veyron2/rt"
+
+ "veyron.io/veyron/veyron/lib/expect"
+ "veyron.io/veyron/veyron/lib/modules"
+ "veyron.io/veyron/veyron/lib/modules/core"
)
-type commandFunc func() modules.T
+type cmdState struct {
+ modules.Handle
+ *expect.Session
+ line string
+}
var (
- commands map[string]commandFunc
- globals modules.Variables
- debug bool
interactive bool
+ handles map[string]*cmdState
)
func init() {
flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
- flag.BoolVar(&debug, "debug", false, "set debug mode")
+ handles = make(map[string]*cmdState)
+ flag.Usage = usage
+}
- commands = make(map[string]commandFunc)
+var usage = func() {
+ fmt.Println(
+ `Welcome to this simple shell that lets you run mount tables, a simple server
+and sundry other commands from an interactive command line or as scripts. Type
+'help' at the prompt to see a list of available commands, or 'help command' to
+get specific help about that command. The shell provides environment variables
+with expansion and intrinsic support for managing subprocess, but it does not
+provide any flow control commands.
- // We maintaing a single, global, dictionary for variables.
- globals = make(modules.Variables)
+All commands, except builtin ones (such as help, set, eval etc) are run
+asynchronously in background. That is, the prompt returns as soon as they are
+started and no output is displayed from them unless an error is encountered
+when they are being started. Each input line is numbered and that number is
+used to refer to the standard output of previous started commands. The variable
+_ always contains the number of the immediately preceeding line. It is
+possible to read the output of a command (using the 'read' builtin) and assign
+it that output to an environment variable. The 'eval' builtin parses output of
+the form <var>=<val>. In this way subproccess may be started, their output
+read and used to configure subsequent subprocesses. For example:
- // 'bultins'
- commands["help"] = helpF
- commands["get"] = getF
- commands["set"] = setF
- commands["print"] = printF
- commands["sleep"] = sleepF
+1> time
+2> read 1 t
+3> print $t
- // TODO(cnicolaou): add 'STOP' command to shutdown a running server,
- // need to return the handle and then call Stop on it.
+will print the first line of output from the time command, as will the
+following:
- // modules
- commands["rootMT"] = modules.NewRootMT
- commands["nodeMT"] = modules.NewNodeMT
- commands["setLocalRoots"] = modules.NewSetRoot
- commands["ls"] = modules.NewGlob
- commands["lsat"] = modules.NewGlobAt
- commands["lsmt"] = modules.NewGlobAtMT
- commands["resolve"] = modules.NewResolve
- commands["resolveMT"] = modules.NewResolveMT
- commands["echoServer"] = modules.NewEchoServer
- commands["echo"] = modules.NewEchoClient
- commands["clockServer"] = modules.NewClockServer
- commands["time"] = modules.NewClockClient
+or:
+time
+read $_ t
+print $t
+
+The eval builtin is used to directly to assign to variables specified
+in the output of the command. For example, if the root command
+prints out MT_NAME=foo then eval will set MT_NAME to foo as follows:
+
+root
+eval $_
+print $MT_NAME
+
+will print the value of MT_NAME that is output by the root command.
+`)
+ flag.PrintDefaults()
}
func prompt(lineno int) {
@@ -69,9 +92,26 @@
}
func main() {
- modules.InModule()
rt.Init()
+ // Subprocesses commands are run by fork/execing this binary
+ // so we must test to see if this instance is a subprocess or the
+ // the original command line instance.
+ if os.Getenv(modules.ShellEntryPoint) != "" {
+ // Subprocess, run the requested command.
+ if err := modules.Dispatch(); err != nil {
+ fmt.Fprintf(os.Stdout, "failed: %v\n", err)
+ fmt.Fprintf(os.Stderr, "failed: %v\n", err)
+ return
+ }
+ return
+ }
+
+ shell := modules.NewShell()
+ defer shell.Cleanup(os.Stderr)
+
+ core.Install(shell)
+
scanner := bufio.NewScanner(os.Stdin)
lineno := 1
prompt(lineno)
@@ -81,14 +121,12 @@
if line == "eof" {
break
}
- if err := process(line, lineno); err != nil {
- if debug {
- fmt.Printf("%d> %s: %v\n", lineno, line, err)
- } else {
- fmt.Printf("%d> %v\n", lineno, err)
- }
+ if err := process(shell, line, lineno); err != nil {
+ fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
+ os.Exit(1)
}
}
+ shell.SetVar("_", strconv.Itoa(lineno))
lineno++
prompt(lineno)
}
@@ -96,10 +134,19 @@
fmt.Printf("error reading input: %v\n", err)
}
- modules.Cleanup()
}
-func process(line string, lineno int) error {
+func output(lineno int, line string) {
+ if len(line) > 0 {
+ if !interactive {
+ fmt.Printf("%d> ", lineno)
+ }
+ line = strings.TrimSuffix(line, "\n")
+ fmt.Printf("%s\n", line)
+ }
+}
+
+func process(sh *modules.Shell, line string, lineno int) error {
fields, err := splitQuotedFields(line)
if err != nil {
return err
@@ -115,36 +162,37 @@
} else {
args = []string{}
}
-
- sub, err := subVariables(args, globals)
- if err != nil {
- return err
- }
-
- factory := commands[name]
- if factory == nil {
- return fmt.Errorf("unrecognised command %q", name)
- }
- if vars, output, _, err := factory().Run(sub); err != nil {
- return err
+ sub, err := subVariables(sh, args)
+ if cmd := builtins[name]; cmd != nil {
+ if cmd.nargs >= 0 && len(sub) != cmd.nargs {
+ return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
+ }
+ l := ""
+ var err error
+ if cmd.needsHandle {
+ l, err = handleWrapper(sh, cmd.fn, sub...)
+ } else {
+ l, err = cmd.fn(sh, nil, sub...)
+ }
+ if err != nil {
+ return err
+ }
+ output(lineno, l)
} else {
- if debug || interactive {
+ handle, err := sh.Start(name, sub...)
+ if err != nil {
+ return err
+ }
+ handles[strconv.Itoa(lineno)] = &cmdState{
+ handle,
+ expect.NewSession(nil, handle.Stdout(), time.Minute),
+ line,
+ }
+ if !interactive {
fmt.Printf("%d> %s\n", lineno, line)
}
- if len(output) > 0 {
- if !interactive {
- fmt.Printf("%d> ", lineno)
- }
- fmt.Printf("%s\n", strings.Join(output, " "))
- }
- if debug && len(vars) > 0 {
- for k, v := range vars {
- fmt.Printf("\t%s=%q .... \n", k, v)
- }
- fmt.Println()
- }
- globals.UpdateFromVariables(vars)
}
+
return nil
}
@@ -187,11 +235,11 @@
}
// subVariables substitutes variables that occur in the string slice
-// args with values from vars.
-func subVariables(args []string, vars modules.Variables) ([]string, error) {
+// args with values from the Shell.
+func subVariables(sh *modules.Shell, args []string) ([]string, error) {
var results []string
for _, a := range args {
- if r, err := subVariablesInArgument(a, vars); err != nil {
+ if r, err := subVariablesInArgument(sh, a); err != nil {
return results, err
} else {
results = append(results, r)
@@ -206,7 +254,7 @@
// A variable, is introduced by $, terminated by \t, space, / , : or !.
// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
// within strings.
-func subVariablesInArgument(a string, vars modules.Variables) (string, error) {
+func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
first := strings.Index(a, "$")
if first < 0 {
return a, nil
@@ -226,7 +274,7 @@
}
rem = end + 1
} else {
- end = strings.IndexAny(p, "\t/,:! ")
+ end = strings.IndexAny(p, "\t/,:!= ")
if end < 0 {
end = len(p)
}
@@ -234,9 +282,9 @@
}
vn = p[start:end]
r := p[rem:]
- v, present := vars[vn]
+ v, present := sh.GetVar(vn)
if !present {
- return "", fmt.Errorf("unknown variable: %q", vn)
+ return a, nil
}
result += v
result += r
diff --git a/tools/naming/simulator/driver_test.go b/tools/naming/simulator/driver_test.go
index ed6f168..b448103 100644
--- a/tools/naming/simulator/driver_test.go
+++ b/tools/naming/simulator/driver_test.go
@@ -4,6 +4,8 @@
"fmt"
"reflect"
"testing"
+
+ "veyron.io/veyron/veyron/lib/modules"
)
func TestFields(t *testing.T) {
@@ -38,7 +40,8 @@
}
func TestVariables(t *testing.T) {
- globals["foo"] = "bar"
+ sh := modules.NewShell()
+ sh.SetVar("foo", "bar")
cases := []struct {
input string
output []string
@@ -56,7 +59,7 @@
if err != nil {
t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
}
- got, err := subVariables(fields, globals)
+ got, err := subVariables(sh, fields)
if err != nil {
t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
}
@@ -73,7 +76,7 @@
{"${fo", fmt.Errorf("unterminated variable: %q", "{fo")},
}
for i, c := range errors {
- _, got := subVariables([]string{c.input}, globals)
+ _, got := subVariables(sh, []string{c.input})
if got.Error() != c.err.Error() {
t.Errorf("%d: %q: expected error: got %q, want %q", i, c.input, got, c.err)
}
diff --git a/tools/naming/simulator/echo.scr b/tools/naming/simulator/echo.scr
new file mode 100644
index 0000000..eb5cefc
--- /dev/null
+++ b/tools/naming/simulator/echo.scr
@@ -0,0 +1,55 @@
+# Simple example to show how names work both without and with a mount table
+# and the difference between resolve and resolveMT.
+
+# A 'stand-alone' server
+echoServer "text" ""
+set es=$_
+eval $es
+eval $es
+read $es
+set esName=$NAME
+set esAddr=$ADDR
+
+echoClient $esName "test"
+set ec=$_
+read $ec line
+assert $line "text: test"
+
+wait $es
+wait $ec
+
+# now use a nameserver.
+root
+eval $_
+set root=$MT_NAME
+
+set NAMESPACE_ROOT=$root
+echoServer "text2" "a/b"
+set es=$_
+eval $es
+set es_name=$NAME
+set es_addr=$ADDR
+read $es
+
+echoClient "a/b" "test 2"
+set ec=$_
+read $ec line
+assert $line "text2: test 2"
+
+# resolve will return the server's address
+setRoots $root
+resolve a/b
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $es_name//
+
+# resolveMT will return the mount table's address (the part before the //)
+resolveMT a/b
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $root//a/b
+
diff --git a/tools/naming/simulator/mt.scr b/tools/naming/simulator/mt.scr
deleted file mode 100644
index 6f89f70..0000000
--- a/tools/naming/simulator/mt.scr
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Example showing multiple mount tables, servers and globing
-#
-
-rootMT
-set ROOT_MT_NAME=$MT_NAME ROOT_MT_ADDR=$MT_ADDR
-set ROOT=$ROOT_MT_NAME
-
-nodeMT $ROOT usa
-set USA_MT=$MT_NAME
-
-nodeMT $USA_MT "palo alto"
-set PA_MT=$MT_NAME
-
-
-print "ROOT MT" $ROOT
-print "USA MT" $USA_MT
-print "Palo Alto MT" $PA_MT
-
-print "--------- Resolve ROOT, ROOT/usa, ROOT/usa/palo alto"
-print "--------- Each should return a different address"
-resolve $ROOT
-resolve $ROOT/usa
-resolve "$ROOT/usa/palo alto"
-
-print "--------- ResolveMT ROOT, ROOT/usa - should return the ROOT MT"
-resolveMT $ROOT
-resolveMT $ROOT/usa
-
-print "--------- ResolveMT ROOT/usa/palo alto/bar should return the palo alto MT"
-resolveMT "$ROOT/usa/palo alto/bar"
-
-# should return a complete hiearchy....
-setLocalRoots $ROOT
-print "--------- setLocalRoots to global root"
-print "--------- ls ..."
-ls ...
-print "--------- ls *"
-ls *
-
-print "--------- setLocalRoots to usa"
-setLocalRoots $USA_MT
-ls ...
-
-print "--------- setLocalRoots to palo alto"
-setLocalRoots $PA_MT
-ls ...
-
-nodeMT $ROOT uk
-set UK_MT=$MT_NAME
-
-nodeMT $UK_MT "cambridge"
-set CAM_MT=$MT_NAME
-
-setLocalRoots $ROOT
-ls ...
-setLocalRoots $UK_MT
-ls ...
-setLocalRoots $CAM_MT
-ls ...
-
-# Create a MountTable tree without using the internal 'mt' suffix as in the
-# examples above.
-nodeMT $ROOT "france"
-set FRANCE_MT=$MT_NAME
-nodeMT $FRANCE_MT "paris"
-setLocalRoots $ROOT
-ls ...
-
-# Conclusion: some of this behaviour seems a little awkward. In particular:
-#
-# ls using the local namespace on a rooted name doesn't seem to work either,
-# thus making it impossible to see the whole name space without setting the
-# local namespace's root which will clearly cause problems for concurrent
-# clients.
-#
-
-
-
-
diff --git a/tools/naming/simulator/mt_complex.scr b/tools/naming/simulator/mt_complex.scr
new file mode 100644
index 0000000..a16a1f1
--- /dev/null
+++ b/tools/naming/simulator/mt_complex.scr
@@ -0,0 +1,255 @@
+# Some more complex uses of mount tables and mounts
+#
+# TODO - list the examples and any issues.
+
+cache off
+
+root
+eval $_
+set root=$MT_NAME
+
+set NAMESPACE_ROOT=$root
+mt tl/a
+set m=$_
+eval $m
+eval $m
+set mt_a_name=$MT_NAME
+set mt_a_addr=$MT_ADDR
+
+mt tl/b
+set m=$_
+eval $m
+eval $m
+set mt_b_name=$MT_NAME
+set mt_b_addr=$MT_ADDR
+
+setRoots $root
+
+#
+# Using glob 'correctly' takes some care. There are (at least?) three
+# forms that you need to consider...
+#
+
+# ls ... finds all of the mount points, as relative names
+ls ...
+set l=$_
+eval $l
+assert $RN 3
+wait $l
+
+# ls /... does not.. - it just finds itself... seems a little weird.
+ls /...
+set l=$_
+eval $l
+assert $RN 1
+wait $l
+
+# a rooted glob finds all of the mount points, include an entry for the root
+# itself. It returns rooted names.
+ls $root/...
+set l=$_
+eval $l
+assert $RN 4
+wait $l
+
+resolve tl/a
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $mt_a_name
+wait $r
+
+#
+# Now, let's run some echo servers, invoke rpc's on them, see what
+# glob and resolve do.
+#
+
+# run an echo server on tl.
+echoServer "E1" tl
+set es_E1=$_
+read $es_E1
+eval $es_E1
+set es_E1_addr=$ADDR
+
+# the echo server above, obscures the mount tables below it.
+# each of the ls (i.e. glob) calls below will lead to 'ipc:unknown method'
+# errors generated by the echo server.
+ls ...
+set l=$_
+eval $l
+assert $RN 1
+
+ls $root/...
+set l=$_
+eval $l
+assert $RN 2
+
+echoClient tl test
+read $_ o
+assert $o "E1: test"
+
+# resolve will find the address of the echo server.
+resolve tl
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 /$es_E1_addr//
+
+# let's have the echo server shut down
+wait $es_E1
+
+# and now, we can see the mount tables again.
+ls ...
+set l=$_
+eval $l
+assert $RN 3
+wait $l
+
+resolve tl/a
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $mt_a_name
+
+# run an echo server on tl/a
+echoServer "E2" tl/a
+set es_E2=$_
+read $es_E2
+eval $es_E2
+read $es_E2
+set es_E2_addr=$ADDR
+
+# we can invoke the echo server 'E2' just fine, probably because
+# we just get lucky and get the most recently mounted address back first.
+echoClient "tl/a" bar
+read $_ o
+assert $o "E2: bar"
+
+# but, when we resolve it's name, we get back two servers, one for the
+# mount table and another for the server!
+resolve tl/a
+set r=$_
+eval $r
+assert $RN 2
+eval $r
+assert $R0 /$es_E2_addr//
+eval $r
+assert $R1 $mt_a_name
+
+# resolveMT correctly returns the root's address.
+resolveMT tl/a
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $root//tl/a
+
+#
+# NOTE: I propose to fix the above ambiguity by having resolve only
+# ever return non-mountpoint servers. To do so, requires that the mount table
+# can tell them apart, which requires the separate Mount and MountMT calls.
+#
+
+# Mount the same server somewhere else
+mount tl/a/c /$es_E2_addr// 1h
+wait $_
+
+ls ...
+wait $_
+
+# this leads to 1 call of ResolveStep for //c on the echo server.
+resolve tl/a/c
+wait $_
+
+# this leads to 2 calls of ResolveStep for //c on the echo server.
+echoClient tl/a/c baz
+read $_ o
+assert $o "E2: baz"
+
+#
+# Can the spurious calls to ResolveStep above be avoided??
+#
+
+# Mount the same server with a really long name.
+set long_name=tl/b/x/y/z/really/long
+mount $long_name /$es_E2_addr// 1h
+wait $_
+
+echoClient $long_name "long baz"
+read $_ o
+assert $o "E2: long baz"
+
+# This example just creates a 'pointer' into the Echo servers name space.
+# NOTE: do we really need this functionality?
+#
+# ResolveStep is again called on the server for //tl/b/x/y/z/really/long
+mount tl/b/short1 /$es_E2_addr/$long_name 1h
+wait $_
+
+echoClient tl/b/short1 "short baz"
+read $_ o
+assert $o E2.${long_name}": short baz"
+
+# Create a mount table with a 'long' name
+set long_name=tl/b/some/deep/name/that/is/a/mount/table
+mt $long_name
+set m=$_
+eval $m
+eval $m
+set mt_l_name=$MT_NAME
+set mt_l_addr=$MT_ADDR
+
+
+# Create a second mount table with a 'long' name
+set second_long_name=tl/a/some/deep/name/that/is/a/mount/table
+mt $second_long_name
+set m=$_
+eval $m
+eval $m
+set mt_l2_name=$MT_NAME
+set mt_l2_addr=$MT_ADDR
+
+# Run an echo server that uses that mount table
+echoServer "E3" $long_name/echo
+set es_E3=$_
+read $es_E3
+eval $es_E3
+set es_E3_addr=$ADDR
+
+echoClient $long_name/echo "long E3"
+read $_ o
+assert $o "E3: long E3"
+
+# make sure that the mount table is the one we expect.
+resolveMT $long_name/echo
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 /$mt_l_addr//echo
+
+# Now, use mount directly to create a 'symlink'
+set symlink_target=some/deep/name/that/is/a/mount
+mount tl/b/symlink /$mt_b_addr/$symlink_target 1h
+wait $_
+
+ls -l tl/b/symlink
+wait $_
+
+resolve tl/b/symlink
+set r=$_
+eval $r
+# returns nothing since symlink is an 'interior' node.
+assert $RN 0
+
+# resolveMT will return the original mount point
+resolveMT tl/b/symlink
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 /$mt_b_addr//$symlink_target
+
diff --git a/tools/naming/simulator/mt_simple.scr b/tools/naming/simulator/mt_simple.scr
new file mode 100644
index 0000000..c09e1eb
--- /dev/null
+++ b/tools/naming/simulator/mt_simple.scr
@@ -0,0 +1,72 @@
+# Simple example showing multiple mount tables, servers and globing
+
+root
+eval $_
+set root=$MT_NAME
+
+set NAMESPACE_ROOT=$root
+mt usa
+eval $_
+set usa_mt=$MT_NAME
+mt uk
+eval $_
+set uk_mt=$MT_NAME
+
+ls $root/...
+set l=$_
+eval $l
+assert $RN 3
+wait $l
+
+set NAMESPACE_ROOT=$usa_mt
+mt "palo alto"
+eval $_
+set pa_mt=$MT_NAME
+
+set NAMESPACE_ROOT=$uk_mt
+mt "cambridge"
+eval $_
+set cam_mt=$MT_NAME
+
+ls $root/...
+set l=$_
+eval $l
+assert $RN 5
+
+ls -l $root/...
+wait $_
+
+resolve $root/usa
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $usa_mt
+wait $r
+
+resolve "$root/usa/palo alto"
+set r=$_
+assert $RN 1
+eval $r
+# this resolves to the mount table hosting palo alto, not the mount table
+# that would host any objects mounted on .../palo alto/...
+# but the uk/cambridge example below seems to behave the opposite way?
+assert $R0 $usa_mt
+wait $r
+
+resolve $root/uk
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+assert $R0 $uk_mt
+wait $r
+
+resolve "$root/uk/cambridge"
+set r=$_
+eval $r
+assert $RN 1
+eval $r
+# this behaves differently to the usa/palo alto case?
+assert $R0 $cam_mt
+wait $r
\ No newline at end of file
diff --git a/tools/naming/simulator/t.scr b/tools/naming/simulator/t.scr
new file mode 100644
index 0000000..adf0571
--- /dev/null
+++ b/tools/naming/simulator/t.scr
@@ -0,0 +1,5 @@
+mount a b
+set w=$_
+read $w
+read $w
+wait $w