github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/rush/rush.go (about) 1 // Copyright 2012-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Rush is an interactive shell similar to sh. 6 // 7 // Description: 8 // Prompt is '% '. 9 package main 10 11 import ( 12 "bufio" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "os" 17 "os/exec" 18 "path" 19 "path/filepath" 20 ) 21 22 type builtin func(c *Command) error 23 24 // TODO: probably have one builtin map and use it for both types? 25 var ( 26 urpath = "/go/bin:/ubin:/buildbin:/bbin:/bin:/usr/local/bin:" 27 builtins = make(map[string]builtin) 28 29 // the environment dir is INTENDED to be per-user and bound in 30 // a private name space at /env. 31 envDir = "/env" 32 ) 33 34 func addBuiltIn(name string, f builtin) error { 35 if _, ok := builtins[name]; ok { 36 return fmt.Errorf("%v already a builtin", name) 37 } 38 builtins[name] = f 39 return nil 40 } 41 42 func wire(cmds []*Command) error { 43 for i, c := range cmds { 44 // IO defaults. 45 var err error 46 if c.Stdin == nil { 47 if c.Stdin, err = openRead(c, os.Stdin, 0); err != nil { 48 return err 49 } 50 } 51 if c.link != "|" { 52 if c.Stdout, err = openWrite(c, os.Stdout, 1); err != nil { 53 return err 54 } 55 } 56 if c.Stderr, err = openWrite(c, os.Stderr, 2); err != nil { 57 return err 58 } 59 // The validation is such that "|" is not set on the last one. 60 // Also, there won't be redirects and "|" inappropriately. 61 if c.link != "|" { 62 continue 63 } 64 w, err := cmds[i+1].StdinPipe() 65 if err != nil { 66 return err 67 } 68 r, err := cmds[i].StdoutPipe() 69 if err != nil { 70 return err 71 } 72 // Oh, yuck. 73 // There seems to be no way to do the classic 74 // inherited pipes thing in Go. Hard to believe. 75 go func() { 76 io.Copy(w, r) 77 w.Close() 78 }() 79 } 80 return nil 81 } 82 83 func runit(c *Command) error { 84 defer func() { 85 for fd, f := range c.files { 86 f.Close() 87 delete(c.files, fd) 88 } 89 }() 90 if b, ok := builtins[c.cmd]; ok { 91 if err := b(c); err != nil { 92 return err 93 } 94 } else { 95 forkAttr(c) 96 if err := c.Start(); err != nil { 97 return fmt.Errorf("%v: Path %v", err, os.Getenv("PATH")) 98 } 99 if err := c.Wait(); err != nil { 100 return fmt.Errorf("wait: %v", err) 101 } 102 } 103 return nil 104 } 105 106 func openRead(c *Command, r io.Reader, fd int) (io.Reader, error) { 107 if c.fdmap[fd] != "" { 108 f, err := os.Open(c.fdmap[fd]) 109 c.files[fd] = f 110 return f, err 111 } 112 return r, nil 113 } 114 115 func openWrite(c *Command, w io.Writer, fd int) (io.Writer, error) { 116 if c.fdmap[fd] != "" { 117 f, err := os.OpenFile(c.fdmap[fd], os.O_CREATE|os.O_WRONLY, 0666) 118 c.files[fd] = f 119 return f, err 120 } 121 return w, nil 122 } 123 124 func doArgs(cmds []*Command) error { 125 for _, c := range cmds { 126 globargv := []string{} 127 for _, v := range c.args { 128 if v.mod == "ENV" { 129 e := v.val 130 if !path.IsAbs(v.val) { 131 e = filepath.Join(envDir, e) 132 } 133 b, err := ioutil.ReadFile(e) 134 if err != nil { 135 return err 136 } 137 // It goes in as one argument. Not sure if this is what we want 138 // but it gets very weird to start splitting it on spaces. Or maybe not? 139 globargv = append(globargv, string(b)) 140 } else if globs, err := filepath.Glob(v.val); err == nil && len(globs) > 0 { 141 globargv = append(globargv, globs...) 142 } else { 143 globargv = append(globargv, v.val) 144 } 145 } 146 147 c.cmd = globargv[0] 148 c.argv = globargv[1:] 149 } 150 return nil 151 } 152 153 // There seems to be no harm in creating a Cmd struct 154 // even for builtins, so for now, we do. 155 // It will, however, do a path lookup, which we really don't need, 156 // and we may change it later. 157 func commands(cmds []*Command) error { 158 for _, c := range cmds { 159 c.Cmd = exec.Command(c.cmd, c.argv[:]...) 160 // this is a Very Special Case related to a Go issue. 161 // we're not able to unshare correctly in builtin. 162 // Not sure of the issue but this hack will have to do until 163 // we understand it. Barf. 164 if c.cmd == "builtin" { 165 builtinAttr(c) 166 } 167 } 168 return nil 169 } 170 171 func command(c *Command) error { 172 // for now, bg will just happen in background. 173 if c.bg { 174 go func() { 175 if err := runit(c); err != nil { 176 fmt.Fprintf(os.Stderr, "%v", err) 177 } 178 }() 179 } else { 180 err := runit(c) 181 return err 182 } 183 return nil 184 } 185 186 func main() { 187 if len(os.Args) != 1 { 188 fmt.Println("no scripts/args yet") 189 os.Exit(1) 190 } 191 192 b := bufio.NewReader(os.Stdin) 193 tty() 194 fmt.Printf("%% ") 195 for { 196 foreground() 197 cmds, status, err := getCommand(b) 198 if err != nil { 199 fmt.Fprintf(os.Stderr, "%v\n", err) 200 } 201 if err := doArgs(cmds); err != nil { 202 fmt.Fprintf(os.Stderr, "args problem: %v\n", err) 203 continue 204 } 205 if err := commands(cmds); err != nil { 206 fmt.Fprintf(os.Stderr, "%v\n", err) 207 continue 208 } 209 if err := wire(cmds); err != nil { 210 fmt.Fprintf(os.Stderr, "%v\n", err) 211 continue 212 } 213 for _, c := range cmds { 214 if err := command(c); err != nil { 215 fmt.Fprintf(os.Stderr, "%v\n", err) 216 if c.link == "||" { 217 continue 218 } 219 // yes, not needed, but useful so you know 220 // what goes on here. 221 if c.link == "&&" { 222 break 223 } 224 break 225 } else { 226 if c.link == "||" { 227 break 228 } 229 } 230 } 231 if status == "EOF" { 232 break 233 } 234 fmt.Printf("%% ") 235 } 236 }