github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/pogosh/exec.go (about) 1 // Copyright 2020 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 package pogosh 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "syscall" 14 ) 15 16 func (c *not) exec(s *State) { 17 c.cmd.exec(s) 18 if s.varExitStatus == 0 { 19 s.varExitStatus = 1 20 } else { 21 s.varExitStatus = 0 22 } 23 } 24 25 func (c *and) exec(s *State) { 26 c.cmd1.exec(s) 27 if s.varExitStatus == 0 { 28 c.cmd2.exec(s) 29 } 30 } 31 32 func (c *or) exec(s *State) { 33 c.cmd1.exec(s) 34 if s.varExitStatus != 0 { 35 c.cmd2.exec(s) 36 } 37 } 38 39 func (c *async) exec(s *State) { 40 // TODO 41 } 42 43 func (c *compoundList) exec(s *State) { 44 for _, cmd := range c.cmds { 45 cmd.exec(s) 46 } 47 } 48 49 func (c *pipeline) exec(s *State) { 50 // TODO: wire redirects 51 for _, cmd := range c.cmds { 52 cmd.exec(s) 53 } 54 } 55 56 func (c *subshell) exec(s *State) { 57 // TODO 58 // cmd *command 59 } 60 61 func (c *forClause) exec(s *State) { 62 // TODO 63 // name []byte 64 // wordlist [][]byte 65 // cmd *command 66 } 67 68 func (c *caseClause) exec(s *State) { 69 // TODO 70 // word []byte 71 // cases []caseItem 72 73 // func (c *caseItem) exec(s *State) { 74 // pattern []byte 75 // cmd *command 76 // } 77 } 78 79 func (c *ifClause) exec(s *State) { 80 // TODO 81 // cmdPred *command 82 // cmdThen *command 83 // cmdElse *command 84 } 85 86 func (c *whileClause) exec(s *State) { 87 // TODO 88 // cmdPred *command 89 // cmd *command 90 } 91 92 func (c *function) exec(s *State) { 93 // TODO 94 // name []byte 95 // cmd *command 96 } 97 98 func searchPath(env string, cmdName string) (string, error) { 99 if strings.Contains(cmdName, "/") { 100 return cmdName, nil 101 } 102 103 for _, prefix := range filepath.SplitList(env) { 104 if prefix == "" { 105 prefix = "." 106 } 107 if prefix[len(prefix)-1] != '/' { 108 prefix += "/" 109 } 110 path := prefix + cmdName 111 112 fi, err := os.Stat(path) 113 // TODO: could the permission check be more strick? 114 if err == nil && fi.Mode().IsRegular() && fi.Mode()&0111 != 0 { 115 return path, nil 116 } 117 } 118 return "", fmt.Errorf("Could not find command '%s'", cmdName) 119 } 120 121 // TODO: move to expansion.go file 122 func wordExpansion(s *State, word string) []string { 123 word = tildeExpansion(s, word) 124 word = recursiveExpansion(s, word) 125 words := fieldSplitting(s, word) 126 for i := range words { 127 words[i] = pathnameExpansion(s, words[i]) 128 words[i] = quoteRemoval(s, words[i]) 129 } 130 return words 131 } 132 133 // Contains: 134 // - Parameter substitution 135 // - Command substitution 136 // - Arithmetic substitution 137 func recursiveExpansion(s *State, word string) string { 138 for i := 0; i < len(word); i++ { 139 // TODO: check for EOF 140 switch { 141 case word[i:i+3] == "$((": 142 for j := i + 3; j < len(word)-1; j++ { 143 if word[j:j+2] == "))" { 144 inside := word[i+2 : j] 145 inside = recursiveExpansion(s, inside) 146 inside = arithmeticSubstitution(s, inside) 147 word = word[:i] + inside + word[j+2:] 148 i += len(inside) - 1 149 break 150 } 151 } 152 case word[i:i+2] == "$(": 153 for j := i + 2; j < len(word); j++ { 154 if word[j:j+2] == ")" { 155 inside := word[i+2 : j] 156 inside = recursiveExpansion(s, inside) 157 inside = commandSubstitution(s, inside) 158 word = word[:i] + inside + word[j+2:] 159 i += len(inside) - 1 160 break 161 } 162 } 163 case word[i] == '`': 164 for j := i + 1; j < len(word); j++ { 165 if word[j] == '`' { 166 inside := word[i+1 : j] 167 inside = recursiveExpansion(s, inside) 168 inside = commandSubstitution(s, inside) 169 word = word[:i] + inside + word[j+1:] 170 i += len(inside) - 1 171 break 172 } 173 } 174 case word[i:i+2] == "${": 175 for j := i; j < len(word); j++ { 176 177 } 178 default: 179 word = parameterExpansion(s, word) 180 } 181 } 182 return word 183 } 184 185 func tildeExpansion(s *State, word string) string { 186 // TODO: there are more rules than this 187 if len(word) > 0 && word[0] == '~' { 188 word = s.variables["HOME"].Value + word[1:] 189 } 190 return word 191 } 192 193 func parameterExpansion(s *State, word string) string { 194 return word // TODO 195 } 196 197 func commandSubstitution(s *State, word string) string { 198 return word // TODO 199 } 200 201 func arithmeticSubstitution(s *State, word string) string { 202 return word // TODO 203 } 204 205 func fieldSplitting(s *State, word string) []string { 206 return strings.Split(word, " ") // TODO 207 } 208 209 func pathnameExpansion(s *State, word string) string { 210 return word // TODO 211 } 212 213 func quoteRemoval(s *State, word string) string { 214 return word // TODO 215 } 216 217 func (c *simpleCommand) exec(s *State) { 218 // We are using the lower-level process API to have more control over file 219 // descriptors. 220 cmd := Cmd{ 221 ProcAttr: os.ProcAttr{ 222 Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, 223 }, 224 } 225 226 // Clear the exit status variable. 227 s.varExitStatus = 0 228 229 // Name 230 cmd.name = string(c.name) 231 232 // Arguments 233 for _, arg := range c.args { 234 cmd.argv = append(cmd.argv, string(arg)) 235 } 236 237 // Redirects 238 for _, redirect := range c.redirects { 239 switch string(redirect.ioOp) { 240 case "<": 241 // Redirect input 242 f, err := os.OpenFile(string(redirect.filename), os.O_RDONLY, 0666) 243 defer f.Close() 244 if err != nil { 245 panic(err) 246 } 247 cmd.Files[0] = f 248 case "<&": 249 // Duplicating an input file descriptor 250 fd, err := strconv.Atoi(string(redirect.filename)) 251 if err != nil { 252 panic(err) 253 } 254 cmd.Files[0] = os.NewFile(uintptr(fd), "TODO") // TODO: make part of state 255 // TODO: closing files with - 256 case ">": 257 // Redirect output 258 f, err := os.OpenFile(string(redirect.filename), os.O_CREATE|os.O_WRONLY, 0666) 259 defer f.Close() 260 if err != nil { 261 panic(err) 262 } 263 cmd.Files[1] = f 264 case ">&": 265 // Duplicating an output file descriptor 266 fd, err := strconv.Atoi(string(redirect.filename)) 267 if err != nil { 268 panic(err) 269 } 270 cmd.Files[1] = os.NewFile(uintptr(fd), "TODO") // TODO: make part of state 271 // TODO: closing files with - 272 case ">>": 273 // Appending redirected output 274 f, err := os.OpenFile(string(redirect.filename), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) 275 defer f.Close() 276 if err != nil { 277 panic(err) 278 } 279 cmd.Files[1] = f 280 case "<>": 281 // Open file descriptor for reading and writing 282 f, err := os.OpenFile(string(redirect.filename), os.O_CREATE|os.O_RDWR, 0666) 283 defer f.Close() 284 if err != nil { 285 panic(err) 286 } 287 cmd.Files[0] = f 288 case ">|": 289 // TODO 290 } 291 } 292 293 // First, resolve and execute builtins 294 if builtin, ok := s.Builtins[cmd.name]; ok { 295 builtin(s, &cmd) 296 return 297 } 298 299 // Second, resolve PATH 300 var err error 301 cmd.name, err = searchPath(os.Getenv("PATH"), string(c.name)) 302 if err != nil { 303 fmt.Fprintf(os.Stderr, "%v\n", err) // TODO: better error handling 304 s.varExitStatus = 127 305 return 306 } 307 308 // Finally, execute the command 309 proc, err := os.StartProcess(cmd.name, cmd.argv, &cmd.ProcAttr) 310 if err != nil { 311 // TODO: check other error types 312 fmt.Fprintf(os.Stderr, "Cannot find command %s, error: %s\n", cmd.name, err) 313 s.varExitStatus = 127 314 return 315 } 316 317 processState, err := proc.Wait() 318 if err != nil { 319 fmt.Fprintf(os.Stderr, "Error running command %s, error: %s\n", cmd.name, err) 320 s.varExitStatus = 127 321 return 322 } 323 324 // TODO: syscall.WaitStatus not same on all systems 325 s.varExitStatus = processState.Sys().(syscall.WaitStatus).ExitStatus() 326 }