github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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  //
     9  //	Prompt is '% '.
    10  package main
    11  
    12  import (
    13  	"bufio"
    14  	"fmt"
    15  	"io"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  )
    20  
    21  type builtin func(c *Command) error
    22  
    23  // TODO: probably have one builtin map and use it for both types?
    24  var (
    25  	urpath = "/go/bin:/ubin:/buildbin:/bbin:/bin:/usr/local/bin:"
    26  
    27  	builtins map[string]builtin
    28  )
    29  
    30  func addBuiltIn(name string, f builtin) error {
    31  	if builtins == nil {
    32  		builtins = make(map[string]builtin)
    33  	}
    34  	if _, ok := builtins[name]; ok {
    35  		return fmt.Errorf("%v already a builtin", name)
    36  	}
    37  	builtins[name] = f
    38  	return nil
    39  }
    40  
    41  func wire(cmds []*Command) error {
    42  	for i, c := range cmds {
    43  		// IO defaults.
    44  		var err error
    45  		if c.Stdin == nil {
    46  			if c.Stdin, err = openRead(c, os.Stdin, 0); err != nil {
    47  				return err
    48  			}
    49  		}
    50  		if c.Link != "|" {
    51  			if c.Stdout, err = openWrite(c, os.Stdout, 1); err != nil {
    52  				return err
    53  			}
    54  		}
    55  		if c.Stderr, err = openWrite(c, os.Stderr, 2); err != nil {
    56  			return err
    57  		}
    58  		// The validation is such that "|" is not set on the last one.
    59  		// Also, there won't be redirects and "|" inappropriately.
    60  		if c.Link != "|" {
    61  			continue
    62  		}
    63  		w, err := cmds[i+1].StdinPipe()
    64  		if err != nil {
    65  			return err
    66  		}
    67  		r, err := cmds[i].StdoutPipe()
    68  		if err != nil {
    69  			return err
    70  		}
    71  		// Oh, yuck.
    72  		// There seems to be no way to do the classic
    73  		// inherited pipes thing in Go. Hard to believe.
    74  		go func() {
    75  			io.Copy(w, r)
    76  			w.Close()
    77  		}()
    78  	}
    79  	return nil
    80  }
    81  
    82  func runit(c *Command) error {
    83  	defer func() {
    84  		for fd, f := range c.files {
    85  			f.Close()
    86  			delete(c.files, fd)
    87  		}
    88  	}()
    89  	if b, ok := builtins[c.cmd]; ok {
    90  		if err := b(c); err != nil {
    91  			return err
    92  		}
    93  	} else {
    94  
    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, 0o666)
   118  		c.files[fd] = f
   119  		return f, err
   120  	}
   121  	return w, nil
   122  }
   123  
   124  func doArgs(cmds []*Command) {
   125  	for _, c := range cmds {
   126  		globargv := []string{}
   127  		for _, v := range c.Args {
   128  			if v.mod == "ENV" {
   129  				globargv = append(globargv, os.Getenv(v.val))
   130  			} else if globs, err := filepath.Glob(v.val); err == nil && len(globs) > 0 {
   131  				globargv = append(globargv, globs...)
   132  			} else {
   133  				globargv = append(globargv, v.val)
   134  			}
   135  		}
   136  
   137  		c.cmd = globargv[0]
   138  		c.argv = globargv[1:]
   139  	}
   140  }
   141  
   142  // There seems to be no harm in creating a Cmd struct
   143  // even for builtins, so for now, we do.
   144  // It will, however, do a path lookup, which we really don't need,
   145  // and we may change it later.
   146  func commands(cmds []*Command) error {
   147  	for _, c := range cmds {
   148  		c.Cmd = exec.Command(c.cmd, c.argv[:]...)
   149  		// this is a Very Special Case related to a Go issue.
   150  		// we're not able to unshare correctly in builtin.
   151  		// Not sure of the issue but this hack will have to do until
   152  		// we understand it. Barf.
   153  		if c.cmd == "builtin" {
   154  			builtinAttr(c)
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  func command(c *Command) error {
   161  	// for now, bg will just happen in background.
   162  	if c.BG {
   163  		go func() {
   164  			if err := runit(c); err != nil {
   165  				fmt.Fprintf(os.Stderr, "%v", err)
   166  			}
   167  		}()
   168  	}
   169  	return runit(c)
   170  }
   171  
   172  func main() {
   173  	if len(os.Args) != 1 {
   174  		fmt.Println("no scripts/args yet")
   175  		os.Exit(1)
   176  	}
   177  
   178  	b := bufio.NewReader(os.Stdin)
   179  	tty()
   180  	fmt.Printf("%% ")
   181  	for {
   182  		foreground()
   183  		cmds, status, err := getCommand(b)
   184  		if err != nil {
   185  			fmt.Fprintf(os.Stderr, "%v\n", err)
   186  		}
   187  		doArgs(cmds)
   188  		if err := commands(cmds); err != nil {
   189  			fmt.Fprintf(os.Stderr, "%v\n", err)
   190  			continue
   191  		}
   192  		if err := wire(cmds); err != nil {
   193  			fmt.Fprintf(os.Stderr, "%v\n", err)
   194  			continue
   195  		}
   196  		for _, c := range cmds {
   197  			if err := command(c); err != nil {
   198  				fmt.Fprintf(os.Stderr, "%v\n", err)
   199  				if c.Link == "||" {
   200  					continue
   201  				}
   202  				// yes, not needed, but useful so you know
   203  				// what goes on here.
   204  				if c.Link == "&&" {
   205  					break
   206  				}
   207  				break
   208  			} else {
   209  				if c.Link == "||" {
   210  					break
   211  				}
   212  			}
   213  		}
   214  		if status == "EOF" {
   215  			break
   216  		}
   217  		fmt.Printf("%% ")
   218  	}
   219  }