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