github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/cmds/installcommand/installcommand.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  // Install command from a go source file.
     8  //
     9  // Synopsis:
    10  //     SYMLINK [ARGS...]
    11  //     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    12  //
    13  // Description:
    14  //     u-root commands are lazily compiled. Uncompiled commands in the /bin
    15  //     directory are symbolic links to installcommand. When executed through
    16  //     the symbolic link, installcommand will build the command from source and
    17  //     exec it.
    18  //
    19  //     The second form allows commands to be installed and exec'ed without a
    20  //     symbolic link. In this form additional arguments such as `-v` and
    21  //     `-ludicrous` can be passed into installcommand.
    22  //
    23  // Options:
    24  //     -lowpri:    the scheduler priority to lowered before starting
    25  //     -ludicrous: print out ALL the output from the go build commands
    26  //     -onlybuild:     just build, do not exec
    27  //     -noforce:   do not build if a file already exists at the destination
    28  //     -v:         print all build commands
    29  import (
    30  	"flag"
    31  	"fmt"
    32  	"log"
    33  	"os"
    34  	"os/exec"
    35  	"path/filepath"
    36  	"strings"
    37  	"syscall"
    38  
    39  	"github.com/u-root/u-root/pkg/uroot/util"
    40  )
    41  
    42  var (
    43  	urpath    = "/go/bin:/ubin:/buildbin:/bbin:/usr/local/bin:"
    44  	lowpri    = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting")
    45  	ludicrous = flag.Bool("ludicrous", false, "print out ALL the output from the go build commands")
    46  	onlybuild = flag.Bool("onlybuild", false, "only build, i.e. do not execute the process after building")
    47  	noforce   = flag.Bool("noforce", false, "do not build if a file already exists at the destination")
    48  	verbose   = flag.Bool("v", false, "print all build commands")
    49  	debug     = func(string, ...interface{}) {}
    50  )
    51  
    52  type form struct {
    53  	// Name of the command, ex: "ls"
    54  	cmdName string
    55  	// Args passed to the command, ex: {"-l", "-R"}
    56  	cmdArgs []string
    57  	// Args intended for installcommand
    58  	lowPri    bool
    59  	ludicrous bool
    60  	onlybuild bool
    61  	noForce   bool
    62  	verbose   bool
    63  }
    64  
    65  func usage() {
    66  	fmt.Fprintf(os.Stderr, "Usage: installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]\n")
    67  	os.Exit(2)
    68  }
    69  
    70  // Parse the command line to determine the form.
    71  func parseCommandLine() form {
    72  	// First form:
    73  	//     SYMLINK [ARGS...]
    74  	if !strings.HasSuffix(os.Args[0], "installcommand") {
    75  		return form{
    76  			cmdName: filepath.Base(os.Args[0]),
    77  			cmdArgs: os.Args[1:],
    78  		}
    79  	}
    80  
    81  	// Second form:
    82  	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    83  	flag.Parse()
    84  	if flag.NArg() < 1 {
    85  		log.Println("Second form requires a COMMAND argument")
    86  		usage()
    87  	}
    88  	return form{
    89  		cmdName:   flag.Arg(0),
    90  		cmdArgs:   flag.Args()[1:],
    91  		lowPri:    *lowpri,
    92  		ludicrous: *ludicrous,
    93  		onlybuild: *onlybuild,
    94  		noForce:   *noforce,
    95  		verbose:   *verbose,
    96  	}
    97  }
    98  
    99  // run runs the command with the information from form.
   100  // Since run can potentially never return, since it can use Exec,
   101  // it should never return in any other case. Hence, if all goes well
   102  // at the end, we os.Exit(0)
   103  func run(n string, form form) {
   104  	if os.Getenv("INSTALLCOMMAND_NOEXEC") == "" {
   105  		err := syscall.Exec(n, append([]string{form.cmdName}, form.cmdArgs...), os.Environ())
   106  		// Regardless of whether err is nil, if Exec returns at all, it failed
   107  		// at its job. Print an error and then let's see if a normal run can succeed.
   108  		log.Printf("Failed to exec %s: %v", form.cmdName, err)
   109  	}
   110  
   111  	cmd := exec.Command(n)
   112  	cmd.Args = append([]string{form.cmdName}, form.cmdArgs...)
   113  	cmd.Stdin = os.Stdin
   114  	cmd.Stderr = os.Stderr
   115  	cmd.Stdout = os.Stdout
   116  	if err := cmd.Run(); err != nil {
   117  		exitErr, ok := err.(*exec.ExitError)
   118  		if !ok {
   119  			log.Fatal(err)
   120  		}
   121  		exitWithStatus(exitErr)
   122  	}
   123  	os.Exit(0)
   124  }
   125  
   126  func main() {
   127  	form := parseCommandLine()
   128  
   129  	if form.lowPri {
   130  		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
   131  			log.Printf("Cannot set low priority: %v", err)
   132  		}
   133  	}
   134  
   135  	a := []string{"install"}
   136  
   137  	if form.verbose {
   138  		debug = log.Printf
   139  		a = append(a, "-x")
   140  	}
   141  
   142  	debug("Command name: %v\n", form.cmdName)
   143  	destDir := "/ubin"
   144  	destFile := filepath.Join(destDir, form.cmdName)
   145  
   146  	// Is the command there? This covers a race condition
   147  	// in that some other process may have caused it to be
   148  	// built.
   149  	_, err := os.Stat(destFile)
   150  
   151  	// The file exists.
   152  	if err == nil {
   153  		if form.onlybuild {
   154  			os.Exit(0)
   155  		}
   156  		run(destFile, form)
   157  	}
   158  
   159  	// If we are here, things did not go so well. We have to build
   160  	// the command.  Which means we have to find the source.  Now
   161  	// that we can add new commands, we need to find out where the
   162  	// command is.  The current rule is that command source is
   163  	// either in /src/github.com/u-root/u-root/cmds/cmdName or
   164  	// /src/*/*/cmdName.  We stat that first name and, if it's there,
   165  	// we're good to go; if not, we do the glob.
   166  	// We considered building these paths into install command via an
   167  	// init() function in a file generated by initramfs. We went with this
   168  	// simpler approach as it allows people to add new commands even after boot:
   169  	// it suffices to, e.g.
   170  	// go get github.com/new/fancyshell
   171  	// ln -s /buildbin/installcommand /buildbin/fancyshell
   172  	// and the glob will find it. If we get to the point that the glob
   173  	// no longer works we can go with filepath.Walk (which glob uses anyway)
   174  	// but for now this works.
   175  	var src string
   176  	for _, p := range util.CmdsGlob {
   177  		g := filepath.Join("/src", p, form.cmdName)
   178  		debug("Check %v", g)
   179  		l, err := filepath.Glob(g)
   180  		debug("glob is %v %v", l, err)
   181  		if err != nil {
   182  			log.Printf("Glob %v fails: %v", g, err)
   183  			continue
   184  		}
   185  		if len(l) == 0 {
   186  			continue
   187  		}
   188  
   189  		if len(l) > 1 {
   190  			log.Printf("Found these paths for %v: %v; going with %v", form.cmdName, l, l[0])
   191  		}
   192  		src = l[0]
   193  		break
   194  	}
   195  
   196  	if src == "" {
   197  		log.Fatalf("Can not find source code for %v", form.cmdName)
   198  	}
   199  
   200  	r, err := filepath.Rel("/src", src)
   201  	if err != nil {
   202  		log.Fatalf("Can't take rel of %v: %v", src, err)
   203  	}
   204  	cmd := exec.Command("go", append(a, r)...)
   205  
   206  	// Set GOGC if unset. The best value is determined empirically and
   207  	// depends on the machine and Go version. For the workload of compiling
   208  	// a small Go program, values larger than the default perform better.
   209  	// See: /scripts/build_perf.sh
   210  	if _, ok := os.LookupEnv("GOGC"); !ok {
   211  		cmd.Env = append(os.Environ(), "GOGC=400")
   212  	}
   213  
   214  	cmd.Dir = "/"
   215  
   216  	debug("Run %v", cmd)
   217  	out, err := cmd.CombinedOutput()
   218  	debug("installcommand: go build returned")
   219  
   220  	if err != nil {
   221  		p := os.Getenv("PATH")
   222  		log.Fatalf("installcommand: trying to build {cmdName: %v, PATH %s, err %v, out %s}", form.cmdName, p, err, out)
   223  	}
   224  
   225  	if *ludicrous {
   226  		debug(string(out))
   227  	}
   228  	if !form.onlybuild {
   229  		run(destFile, form)
   230  	}
   231  }