blob: 4f944ca7843c5591fe43783e7c19a4da66925ed9 [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 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.
103 if os.Getenv(modules.ShellEntryPoint) != "" {
104 // 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)
Cosmos Nicolaouf47699b2014-10-10 14:36:23 -0700114 if os.Getenv("VEYRON_CREDENTIALS") == "" {
115 shell.CreateAndUseNewCredentials()
Cosmos Nicolaoua09f26b2014-09-30 15:17:35 -0700116 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700117
118 core.Install(shell)
119
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700120 scanner := bufio.NewScanner(os.Stdin)
121 lineno := 1
122 prompt(lineno)
123 for scanner.Scan() {
124 line := scanner.Text()
125 if !strings.HasPrefix(line, "#") && len(line) > 0 {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700126 if line == "eof" {
127 break
128 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700129 if err := process(shell, line, lineno); err != nil {
130 fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700131 if !interactive {
132 os.Exit(1)
133 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700134 }
135 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700136 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700137 lineno++
138 prompt(lineno)
139 }
140 if err := scanner.Err(); err != nil {
141 fmt.Printf("error reading input: %v\n", err)
142 }
143
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700144}
145
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700146func output(lineno int, line string) {
147 if len(line) > 0 {
148 if !interactive {
149 fmt.Printf("%d> ", lineno)
150 }
151 line = strings.TrimSuffix(line, "\n")
152 fmt.Printf("%s\n", line)
153 }
154}
155
156func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700157 fields, err := splitQuotedFields(line)
158 if err != nil {
159 return err
160 }
161 if len(fields) == 0 {
162 return fmt.Errorf("no input")
163 }
164 name := fields[0]
165
166 var args []string
167 if len(fields) > 1 {
168 args = fields[1:]
169 } else {
170 args = []string{}
171 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700172 sub, err := subVariables(sh, args)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700173 if err != nil {
174 return err
175 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700176 if cmd := builtins[name]; cmd != nil {
177 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
178 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
179 }
180 l := ""
181 var err error
182 if cmd.needsHandle {
183 l, err = handleWrapper(sh, cmd.fn, sub...)
184 } else {
185 l, err = cmd.fn(sh, nil, sub...)
186 }
187 if err != nil {
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700188 return fmt.Errorf("%s : %s", err, l)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700189 }
190 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700191 } else {
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700192 handle, err := sh.Start(name, nil, sub...)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700193 if err != nil {
194 return err
195 }
196 handles[strconv.Itoa(lineno)] = &cmdState{
197 handle,
198 expect.NewSession(nil, handle.Stdout(), time.Minute),
199 line,
200 }
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700201 output(lineno, line)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700202 }
203 return nil
204}
205
206// splitQuotedFields a line into fields, allowing for quoted strings.
207func splitQuotedFields(line string) ([]string, error) {
208 fields := []string{}
209 inquote := false
210 var field []rune
211 for _, c := range line {
212 switch {
213 case c == '"':
214 if inquote {
215 fields = append(fields, string(field))
216 field = nil
217 inquote = false
218 } else {
219 inquote = true
220 }
221 case unicode.IsSpace(c):
222 if inquote {
223 field = append(field, c)
224 } else {
225 if len(field) > 0 {
226 fields = append(fields, string(field))
227 }
228 field = nil
229 }
230 default:
231 field = append(field, c)
232 }
233 }
234 if inquote {
235 return nil, fmt.Errorf("unterminated quoted input")
236 }
237
238 if len(field) > 0 {
239 fields = append(fields, string(field))
240 }
241 return fields, nil
242}
243
244// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700245// args with values from the Shell.
246func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700247 var results []string
248 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700249 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700250 return results, err
251 } else {
252 results = append(results, r)
253 }
254 }
255 return results, nil
256}
257
258// subVariablesInArgument substitutes variables that occur in the string
259// parameter with values from vars.
260//
261// A variable, is introduced by $, terminated by \t, space, / , : or !.
262// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
263// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700264func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700265 first := strings.Index(a, "$")
266 if first < 0 {
267 return a, nil
268 }
269 parts := strings.Split(a, "$")
270 result := parts[0]
271 vn := ""
272 rem := 0
273 for _, p := range parts[1:] {
274 start := 0
275 end := -1
276 if strings.HasPrefix(p, "{") {
277 start = 1
278 end = strings.Index(p, "}")
279 if end < 0 {
280 return "", fmt.Errorf("unterminated variable: %q", p)
281 }
282 rem = end + 1
283 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700284 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700285 if end < 0 {
286 end = len(p)
287 }
288 rem = end
289 }
290 vn = p[start:end]
291 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700292 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700293 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700294 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700295 }
296 result += v
297 result += r
298 }
299 return result, nil
300}