blob: 92428302d3cd64be1d8109717b68959c66b7413f [file] [log] [blame]
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -07001// This app provides a simple scripted environment for running common veyron
2// services as subprocesses and testing interactions between them. It is
3// structured as an interpreter, with global variables and variable
4// expansion, but no control flow. The command set that it supports is
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -07005// extendable by adding new 'commands' that implement the API defined
6// by veyron/lib/modules.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -07007package main
8
9import (
10 "bufio"
11 "flag"
12 "fmt"
13 "os"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070014 "strconv"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070015 "strings"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070016 "time"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070017 "unicode"
18
Jiri Simsa519c5072014-09-17 21:37:57 -070019 "veyron.io/veyron/veyron2/rt"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070020
21 "veyron.io/veyron/veyron/lib/expect"
Asim Shankar95910b62014-10-31 22:02:29 -070022 "veyron.io/veyron/veyron/lib/flags/consts"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070023 "veyron.io/veyron/veyron/lib/modules"
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -070024 _ "veyron.io/veyron/veyron/profiles"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070025)
26
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070027type cmdState struct {
28 modules.Handle
29 *expect.Session
30 line string
31}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070032
33var (
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070034 interactive bool
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070035 handles map[string]*cmdState
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -070036 jsonDict map[string]string
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070037)
38
39func init() {
40 flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070041 handles = make(map[string]*cmdState)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -070042 jsonDict = make(map[string]string)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070043 flag.Usage = usage
44}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070045
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070046var usage = func() {
47 fmt.Println(
48 `Welcome to this simple shell that lets you run mount tables, a simple server
49and sundry other commands from an interactive command line or as scripts. Type
50'help' at the prompt to see a list of available commands, or 'help command' to
51get specific help about that command. The shell provides environment variables
52with expansion and intrinsic support for managing subprocess, but it does not
53provide any flow control commands.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070054
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070055All commands, except builtin ones (such as help, set, eval etc) are run
56asynchronously in background. That is, the prompt returns as soon as they are
57started and no output is displayed from them unless an error is encountered
58when they are being started. Each input line is numbered and that number is
59used to refer to the standard output of previous started commands. The variable
60_ always contains the number of the immediately preceeding line. It is
61possible to read the output of a command (using the 'read' builtin) and assign
62it that output to an environment variable. The 'eval' builtin parses output of
63the form <var>=<val>. In this way subproccess may be started, their output
64read and used to configure subsequent subprocesses. For example:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070065
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700661> time
672> read 1 t
683> print $t
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070069
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070070will print the first line of output from the time command, as will the
71following:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070072
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070073or:
74time
75read $_ t
76print $t
77
78The eval builtin is used to directly to assign to variables specified
79in the output of the command. For example, if the root command
80prints out MT_NAME=foo then eval will set MT_NAME to foo as follows:
81
82root
83eval $_
84print $MT_NAME
85
86will print the value of MT_NAME that is output by the root command.
87`)
88 flag.PrintDefaults()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070089}
90
91func prompt(lineno int) {
92 if interactive {
93 fmt.Printf("%d> ", lineno)
94 }
95}
96
97func main() {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070098 rt.Init()
99
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700100 // Subprocesses commands are run by fork/execing this binary
101 // so we must test to see if this instance is a subprocess or the
102 // the original command line instance.
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800103 if modules.IsModulesProcess() {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700104 // Subprocess, run the requested command.
105 if err := modules.Dispatch(); err != nil {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700106 fmt.Fprintf(os.Stderr, "failed: %v\n", err)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700107 os.Exit(1)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700108 }
109 return
110 }
111
112 shell := modules.NewShell()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700113 defer shell.Cleanup(os.Stderr, os.Stderr)
Asim Shankar95910b62014-10-31 22:02:29 -0700114 if os.Getenv(consts.VeyronCredentials) == "" {
Cosmos Nicolaouf47699b2014-10-10 14:36:23 -0700115 shell.CreateAndUseNewCredentials()
Cosmos Nicolaoua09f26b2014-09-30 15:17:35 -0700116 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700117
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700118 scanner := bufio.NewScanner(os.Stdin)
119 lineno := 1
120 prompt(lineno)
121 for scanner.Scan() {
122 line := scanner.Text()
123 if !strings.HasPrefix(line, "#") && len(line) > 0 {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700124 if line == "eof" {
125 break
126 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700127 if err := process(shell, line, lineno); err != nil {
128 fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700129 if !interactive {
130 os.Exit(1)
131 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700132 }
133 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700134 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700135 lineno++
136 prompt(lineno)
137 }
138 if err := scanner.Err(); err != nil {
139 fmt.Printf("error reading input: %v\n", err)
140 }
141
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700142}
143
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700144func output(lineno int, line string) {
145 if len(line) > 0 {
146 if !interactive {
147 fmt.Printf("%d> ", lineno)
148 }
149 line = strings.TrimSuffix(line, "\n")
150 fmt.Printf("%s\n", line)
151 }
152}
153
154func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700155 fields, err := splitQuotedFields(line)
156 if err != nil {
157 return err
158 }
159 if len(fields) == 0 {
160 return fmt.Errorf("no input")
161 }
162 name := fields[0]
163
164 var args []string
165 if len(fields) > 1 {
166 args = fields[1:]
167 } else {
168 args = []string{}
169 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700170 sub, err := subVariables(sh, args)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700171 if err != nil {
172 return err
173 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700174 if cmd := builtins[name]; cmd != nil {
175 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
176 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
177 }
178 l := ""
179 var err error
180 if cmd.needsHandle {
181 l, err = handleWrapper(sh, cmd.fn, sub...)
182 } else {
183 l, err = cmd.fn(sh, nil, sub...)
184 }
185 if err != nil {
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700186 return fmt.Errorf("%s : %s", err, l)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700187 }
188 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700189 } else {
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700190 handle, err := sh.Start(name, nil, sub...)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700191 if err != nil {
192 return err
193 }
194 handles[strconv.Itoa(lineno)] = &cmdState{
195 handle,
196 expect.NewSession(nil, handle.Stdout(), time.Minute),
197 line,
198 }
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700199 output(lineno, line)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700200 }
201 return nil
202}
203
204// splitQuotedFields a line into fields, allowing for quoted strings.
205func splitQuotedFields(line string) ([]string, error) {
206 fields := []string{}
207 inquote := false
208 var field []rune
209 for _, c := range line {
210 switch {
211 case c == '"':
212 if inquote {
213 fields = append(fields, string(field))
214 field = nil
215 inquote = false
216 } else {
217 inquote = true
218 }
219 case unicode.IsSpace(c):
220 if inquote {
221 field = append(field, c)
222 } else {
223 if len(field) > 0 {
224 fields = append(fields, string(field))
225 }
226 field = nil
227 }
228 default:
229 field = append(field, c)
230 }
231 }
232 if inquote {
233 return nil, fmt.Errorf("unterminated quoted input")
234 }
235
236 if len(field) > 0 {
237 fields = append(fields, string(field))
238 }
239 return fields, nil
240}
241
242// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700243// args with values from the Shell.
244func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700245 var results []string
246 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700247 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700248 return results, err
249 } else {
250 results = append(results, r)
251 }
252 }
253 return results, nil
254}
255
256// subVariablesInArgument substitutes variables that occur in the string
257// parameter with values from vars.
258//
259// A variable, is introduced by $, terminated by \t, space, / , : or !.
260// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
261// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700262func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700263 first := strings.Index(a, "$")
264 if first < 0 {
265 return a, nil
266 }
267 parts := strings.Split(a, "$")
268 result := parts[0]
269 vn := ""
270 rem := 0
271 for _, p := range parts[1:] {
272 start := 0
273 end := -1
274 if strings.HasPrefix(p, "{") {
275 start = 1
276 end = strings.Index(p, "}")
277 if end < 0 {
278 return "", fmt.Errorf("unterminated variable: %q", p)
279 }
280 rem = end + 1
281 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700282 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700283 if end < 0 {
284 end = len(p)
285 }
286 rem = end
287 }
288 vn = p[start:end]
289 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700290 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700291 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700292 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700293 }
294 result += v
295 result += r
296 }
297 return result, nil
298}