github.com/system-transparency/u-root@v6.0.1-0.20190919065413-ed07a650de4c+incompatible/cmds/core/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  //     -exec:      build and exec the command
    26  //     -force:     do not build if a file already exists at the destination
    27  //     -v:         print all build commands
    28  import (
    29  	"flag"
    30  	"fmt"
    31  	"log"
    32  	"os"
    33  	"os/exec"
    34  	"path/filepath"
    35  	"strings"
    36  	"syscall"
    37  
    38  	"github.com/u-root/u-root/pkg/golang"
    39  	"github.com/u-root/u-root/pkg/uroot/util"
    40  )
    41  
    42  var (
    43  	lowpri = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting")
    44  	exe    = flag.Bool("exec", true, "build AND execute the command")
    45  	force  = flag.Bool("force", false, "build even if a file already exists at the destination")
    46  
    47  	verbose = flag.Bool("v", false, "print all build commands")
    48  	debug   = func(string, ...interface{}) {}
    49  	r       = util.UrootPath
    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  
    58  	// Args intended for installcommand
    59  	lowPri  bool
    60  	exec    bool
    61  	force   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  		// This is almost certain to be a symlink, and it's no harm
    76  		// to check it.
    77  		f := util.ResolveUntilLastSymlink(os.Args[0])
    78  		return form{
    79  			cmdName: filepath.Base(f),
    80  			cmdArgs: os.Args[1:],
    81  			lowPri:  *lowpri,
    82  			exec:    *exe,
    83  			force:   *force,
    84  			verbose: *verbose,
    85  		}
    86  	}
    87  
    88  	// Second form:
    89  	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    90  	flag.Parse()
    91  	if flag.NArg() < 1 {
    92  		log.Println("Second form requires a COMMAND argument")
    93  		usage()
    94  	}
    95  	return form{
    96  		cmdName: flag.Arg(0),
    97  		cmdArgs: flag.Args()[1:],
    98  		lowPri:  *lowpri,
    99  		exec:    *exe,
   100  		force:   *force,
   101  		verbose: *verbose,
   102  	}
   103  }
   104  
   105  // run runs the command with the information from form.
   106  // Since run can potentially never return, since it can use Exec,
   107  // it should never return in any other case. Hence, if all goes well
   108  // at the end, we os.Exit(0)
   109  func run(n string, form form) {
   110  	cmd := exec.Command(n, form.cmdArgs...)
   111  	cmd.Stdin = os.Stdin
   112  	cmd.Stderr = os.Stderr
   113  	cmd.Stdout = os.Stdout
   114  	if err := cmd.Run(); err != nil {
   115  		exitErr, ok := err.(*exec.ExitError)
   116  		if !ok {
   117  			log.Fatal(err)
   118  		}
   119  		exitWithStatus(exitErr)
   120  	}
   121  	os.Exit(0)
   122  }
   123  
   124  func main() {
   125  	form := parseCommandLine()
   126  
   127  	if form.lowPri {
   128  		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
   129  			log.Printf("Cannot set low priority: %v", err)
   130  		}
   131  	}
   132  
   133  	if form.verbose {
   134  		debug = log.Printf
   135  	}
   136  
   137  	destFile := filepath.Join(r("/ubin"), form.cmdName)
   138  
   139  	// Is the command there? This covers a race condition
   140  	// in that some other process may have caused it to be
   141  	// built.
   142  	if _, err := os.Stat(destFile); err == nil {
   143  		if !form.exec {
   144  			os.Exit(0)
   145  		}
   146  		run(destFile, form)
   147  	}
   148  
   149  	env := golang.Default()
   150  	env.Context.GOROOT = r("/go")
   151  	env.Context.GOPATH = r("/")
   152  	env.Context.CgoEnabled = false
   153  
   154  	var srcDir string
   155  	err := filepath.Walk(r("/src"), func(p string, fi os.FileInfo, err error) error {
   156  		if err != nil {
   157  			return nil
   158  		}
   159  
   160  		if fi.IsDir() && filepath.Base(p) == form.cmdName {
   161  			// Make sure it's an actual Go command.
   162  			pkg, err := env.PackageByPath(p)
   163  			if err == nil && pkg.IsCommand() {
   164  				srcDir = p
   165  			}
   166  		}
   167  		return nil
   168  	})
   169  	if err != nil {
   170  		log.Fatal(err)
   171  	}
   172  	if len(srcDir) == 0 {
   173  		log.Fatalf("Can not find source code for %q", form.cmdName)
   174  	}
   175  
   176  	if err := env.BuildDir(srcDir, destFile, golang.BuildOpts{}); err != nil {
   177  		log.Fatalf("Couldn't compile %q: %v", form.cmdName, err)
   178  	}
   179  
   180  	if form.exec {
   181  		run(destFile, form)
   182  	}
   183  }