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  }