blob: 31e60380fa3a19f782b31edd490269023a125731 [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"
22 "veyron.io/veyron/veyron/lib/modules"
23 "veyron.io/veyron/veyron/lib/modules/core"
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 Nicolaou7c659ac2014-06-09 22:47:04 -070036)
37
38func init() {
39 flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070040 handles = make(map[string]*cmdState)
41 flag.Usage = usage
42}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070043
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070044var usage = func() {
45 fmt.Println(
46 `Welcome to this simple shell that lets you run mount tables, a simple server
47and sundry other commands from an interactive command line or as scripts. Type
48'help' at the prompt to see a list of available commands, or 'help command' to
49get specific help about that command. The shell provides environment variables
50with expansion and intrinsic support for managing subprocess, but it does not
51provide any flow control commands.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070052
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070053All commands, except builtin ones (such as help, set, eval etc) are run
54asynchronously in background. That is, the prompt returns as soon as they are
55started and no output is displayed from them unless an error is encountered
56when they are being started. Each input line is numbered and that number is
57used to refer to the standard output of previous started commands. The variable
58_ always contains the number of the immediately preceeding line. It is
59possible to read the output of a command (using the 'read' builtin) and assign
60it that output to an environment variable. The 'eval' builtin parses output of
61the form <var>=<val>. In this way subproccess may be started, their output
62read and used to configure subsequent subprocesses. For example:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070063
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700641> time
652> read 1 t
663> print $t
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070067
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070068will print the first line of output from the time command, as will the
69following:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070070
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070071or:
72time
73read $_ t
74print $t
75
76The eval builtin is used to directly to assign to variables specified
77in the output of the command. For example, if the root command
78prints out MT_NAME=foo then eval will set MT_NAME to foo as follows:
79
80root
81eval $_
82print $MT_NAME
83
84will print the value of MT_NAME that is output by the root command.
85`)
86 flag.PrintDefaults()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070087}
88
89func prompt(lineno int) {
90 if interactive {
91 fmt.Printf("%d> ", lineno)
92 }
93}
94
95func main() {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070096 rt.Init()
97
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070098 // Subprocesses commands are run by fork/execing this binary
99 // so we must test to see if this instance is a subprocess or the
100 // the original command line instance.
101 if os.Getenv(modules.ShellEntryPoint) != "" {
102 // Subprocess, run the requested command.
103 if err := modules.Dispatch(); err != nil {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700104 fmt.Fprintf(os.Stderr, "failed: %v\n", err)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700105 os.Exit(1)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700106 }
107 return
108 }
109
110 shell := modules.NewShell()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700111 defer shell.Cleanup(os.Stderr, os.Stderr)
Cosmos Nicolaouf47699b2014-10-10 14:36:23 -0700112 if os.Getenv("VEYRON_CREDENTIALS") == "" {
113 shell.CreateAndUseNewCredentials()
Cosmos Nicolaoua09f26b2014-09-30 15:17:35 -0700114 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700115
116 core.Install(shell)
117
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)
129 os.Exit(1)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700130 }
131 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700132 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700133 lineno++
134 prompt(lineno)
135 }
136 if err := scanner.Err(); err != nil {
137 fmt.Printf("error reading input: %v\n", err)
138 }
139
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700140}
141
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700142func output(lineno int, line string) {
143 if len(line) > 0 {
144 if !interactive {
145 fmt.Printf("%d> ", lineno)
146 }
147 line = strings.TrimSuffix(line, "\n")
148 fmt.Printf("%s\n", line)
149 }
150}
151
152func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700153 fields, err := splitQuotedFields(line)
154 if err != nil {
155 return err
156 }
157 if len(fields) == 0 {
158 return fmt.Errorf("no input")
159 }
160 name := fields[0]
161
162 var args []string
163 if len(fields) > 1 {
164 args = fields[1:]
165 } else {
166 args = []string{}
167 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700168 sub, err := subVariables(sh, args)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700169 if err != nil {
170 return err
171 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700172 if cmd := builtins[name]; cmd != nil {
173 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
174 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
175 }
176 l := ""
177 var err error
178 if cmd.needsHandle {
179 l, err = handleWrapper(sh, cmd.fn, sub...)
180 } else {
181 l, err = cmd.fn(sh, nil, sub...)
182 }
183 if err != nil {
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700184 return fmt.Errorf("%s : %s", err, l)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700185 }
186 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700187 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700188 handle, err := sh.Start(name, sub...)
189 if err != nil {
190 return err
191 }
192 handles[strconv.Itoa(lineno)] = &cmdState{
193 handle,
194 expect.NewSession(nil, handle.Stdout(), time.Minute),
195 line,
196 }
197 if !interactive {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700198 fmt.Printf("%d> %s\n", lineno, line)
199 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700200 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700201
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700202 return nil
203}
204
205// splitQuotedFields a line into fields, allowing for quoted strings.
206func splitQuotedFields(line string) ([]string, error) {
207 fields := []string{}
208 inquote := false
209 var field []rune
210 for _, c := range line {
211 switch {
212 case c == '"':
213 if inquote {
214 fields = append(fields, string(field))
215 field = nil
216 inquote = false
217 } else {
218 inquote = true
219 }
220 case unicode.IsSpace(c):
221 if inquote {
222 field = append(field, c)
223 } else {
224 if len(field) > 0 {
225 fields = append(fields, string(field))
226 }
227 field = nil
228 }
229 default:
230 field = append(field, c)
231 }
232 }
233 if inquote {
234 return nil, fmt.Errorf("unterminated quoted input")
235 }
236
237 if len(field) > 0 {
238 fields = append(fields, string(field))
239 }
240 return fields, nil
241}
242
243// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700244// args with values from the Shell.
245func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700246 var results []string
247 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700248 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700249 return results, err
250 } else {
251 results = append(results, r)
252 }
253 }
254 return results, nil
255}
256
257// subVariablesInArgument substitutes variables that occur in the string
258// parameter with values from vars.
259//
260// A variable, is introduced by $, terminated by \t, space, / , : or !.
261// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
262// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700263func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700264 first := strings.Index(a, "$")
265 if first < 0 {
266 return a, nil
267 }
268 parts := strings.Split(a, "$")
269 result := parts[0]
270 vn := ""
271 rem := 0
272 for _, p := range parts[1:] {
273 start := 0
274 end := -1
275 if strings.HasPrefix(p, "{") {
276 start = 1
277 end = strings.Index(p, "}")
278 if end < 0 {
279 return "", fmt.Errorf("unterminated variable: %q", p)
280 }
281 rem = end + 1
282 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700283 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700284 if end < 0 {
285 end = len(p)
286 }
287 rem = end
288 }
289 vn = p[start:end]
290 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700291 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700292 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700293 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700294 }
295 result += v
296 result += r
297 }
298 return result, nil
299}