github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/builtin/builtin.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  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"syscall"
    16  
    17  	"golang.org/x/tools/imports"
    18  )
    19  
    20  const (
    21  	elvishPath = "/src/github.com/u-root/u-root/cmds/elvish"
    22  )
    23  
    24  type mount struct {
    25  	source string
    26  	target string
    27  	fstype string
    28  	flags  uintptr
    29  	opts   string
    30  }
    31  
    32  var (
    33  	startPart = "package main\n"
    34  	initPart  = "func init() {\n	addBuiltIn(\"%s\", %s)\n}\nfunc %s(c*Command) error {\nvar err error\n"
    35  	//	endPart = "\n}\n)\n}\n"
    36  	endPart   = "\nreturn err\n}\n"
    37  	namespace = []mount{
    38  		{source: "tmpfs", target: elvishPath, fstype: "tmpfs", flags: syscall.MS_MGC_VAL, opts: ""},
    39  		{source: "tmpfs", target: "/ubin", fstype: "tmpfs", flags: syscall.MS_MGC_VAL, opts: ""},
    40  	}
    41  	debug = flag.Bool("d", false, "Print debug info")
    42  )
    43  
    44  func main() {
    45  	opts := imports.Options{
    46  		Fragment:  true,
    47  		AllErrors: true,
    48  		Comments:  true,
    49  		TabIndent: true,
    50  		TabWidth:  8,
    51  	}
    52  	flag.Parse()
    53  	a := flag.Args()
    54  	if len(a) < 2 || len(a)%2 != 0 {
    55  		log.Fatalf("Usage: builtin <command> <code> [<command> <code>]*")
    56  	}
    57  	filemap := make(map[string][]byte)
    58  	for ; len(a) > 0; a = a[2:] {
    59  		goCode := startPart
    60  		// Simple programs are just bits of code for main ...
    61  		if a[1][0] == '{' {
    62  			goCode = goCode + fmt.Sprintf(initPart, a[0], a[0], a[0])
    63  			goCode = goCode + a[1][1:len(a[1])-1]
    64  		} else {
    65  			for _, v := range a[1:] {
    66  				if v == "{" {
    67  					goCode = goCode + fmt.Sprintf(initPart, a[0])
    68  					continue
    69  				}
    70  				// FIXME: should only look for last arg.
    71  				if v == "}" {
    72  					break
    73  				}
    74  				goCode = goCode + v + "\n"
    75  			}
    76  		}
    77  		goCode = goCode + endPart
    78  		if *debug {
    79  			log.Printf("\n---------------------\n%v\n------------------------\n", goCode)
    80  		}
    81  		fullCode, err := imports.Process("commandline", []byte(goCode), &opts)
    82  		if err != nil {
    83  			log.Fatalf("bad parse: '%v': %v", goCode, err)
    84  		}
    85  		if *debug {
    86  			log.Printf("\n----FULLCODE---------\n%v\n------FULLCODE----------\n", string(fullCode))
    87  		}
    88  		bName := filepath.Join(elvishPath, a[0]+".go")
    89  		filemap[bName] = fullCode
    90  	}
    91  
    92  	// processed code, read in shell files.
    93  	globs, err := filepath.Glob(elvishPath + "/*.go")
    94  	if err != nil {
    95  		log.Fatal(err)
    96  	}
    97  	for _, i := range globs {
    98  		if b, err := ioutil.ReadFile(i); err != nil {
    99  			log.Fatal(err)
   100  		} else {
   101  			if _, ok := filemap[i]; ok {
   102  				log.Fatalf("%v exists", i)
   103  			}
   104  			filemap[i] = b
   105  		}
   106  	}
   107  
   108  	if b, err := ioutil.ReadFile("/proc/mounts"); err == nil && false {
   109  		log.Printf("m %v\n", string(b))
   110  	}
   111  	// we'd like to do this here, but it seems it doesn't end
   112  	// up applying to all procs in this group, leading to confusion.
   113  	// sometimes they get the private mount, sometimes not.
   114  	// It's a fundamental limit in the go runtime.
   115  	// So we hack it in the shell.
   116  	// There is no FIXME
   117  	if false {
   118  		if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
   119  			log.Fatal(err)
   120  		}
   121  	}
   122  	if *debug {
   123  		if b, err := ioutil.ReadFile("/proc/mounts"); err == nil {
   124  			log.Printf("Reading /proc/mount:m %v\n", b)
   125  		}
   126  	}
   127  
   128  	// We are rewriting the shell. We need to create a new binary, i.e.
   129  	// rewrite the one in /ubin. Sadly, there is no way to say "mount THIS bin
   130  	// before THAT bin". There will be ca. 3.18 and we might as well wait for
   131  	// that to become common. For now, we essentially erase /ubin but mounting
   132  	// a tmpfs over it.
   133  	// This would be infinitely easier with a true union file system. Oh well.
   134  	for _, m := range namespace {
   135  		if err := syscall.Mount(m.source, m.target, m.fstype, m.flags, m.opts); err != nil {
   136  			log.Printf("Mount :%s: on :%s: type :%s: flags %x: opts %v: %v\n", m.source, m.target, m.fstype, m.flags, m.opts, err)
   137  		}
   138  	}
   139  	// write the new elvishPath
   140  	for i, v := range filemap {
   141  		if err = ioutil.WriteFile(i, v, 0600); err != nil {
   142  			log.Fatal(err)
   143  		}
   144  	}
   145  
   146  	// the big fun: just run it. The Right Things Happen.
   147  	cmd := exec.Command("/buildbin/elvish")
   148  	cmd.Stdin = os.Stdin
   149  	cmd.Stderr = os.Stderr
   150  	cmd.Stdout = os.Stdout
   151  	// TODO: figure out why we get EPERM when we use this.
   152  	//cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true,}
   153  	if *debug {
   154  		log.Printf("Run %v", cmd)
   155  	}
   156  	if err := cmd.Run(); err != nil {
   157  		log.Printf("%v\n", err)
   158  	}
   159  	// Unshare doesn't work in a sane way due to a Go issue?
   160  	for _, m := range namespace {
   161  		if err := syscall.Unmount(m.target, syscall.MNT_FORCE); err != nil {
   162  			log.Printf("Umount :%s: %v\n", m.target, err)
   163  		}
   164  	}
   165  	log.Printf("builtin: /ubin/elvish returned!\n")
   166  }