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