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  }