github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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  //     -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  )
    40  
    41  var (
    42  	lowpri = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting")
    43  	exe    = flag.Bool("exec", true, "build AND execute the command")
    44  	force  = flag.Bool("force", false, "build even if a file already exists at the destination")
    45  
    46  	verbose = flag.Bool("v", false, "print all build commands")
    47  	debug   = func(string, ...interface{}) {}
    48  )
    49  
    50  type form struct {
    51  	// Name of the command, ex: "ls"
    52  	cmdName string
    53  	// Args passed to the command, ex: {"-l", "-R"}
    54  	cmdArgs []string
    55  
    56  	// Args intended for installcommand
    57  	lowPri  bool
    58  	exec    bool
    59  	force   bool
    60  	verbose bool
    61  }
    62  
    63  func usage() {
    64  	fmt.Fprintf(os.Stderr, "Usage: installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]\n")
    65  	os.Exit(2)
    66  }
    67  
    68  // Parse the command line to determine the form.
    69  func parseCommandLine() form {
    70  	// First form:
    71  	//     SYMLINK [ARGS...]
    72  	if !strings.HasSuffix(os.Args[0], "installcommand") {
    73  		return form{
    74  			cmdName: filepath.Base(os.Args[0]),
    75  			cmdArgs: os.Args[1:],
    76  			lowPri:  *lowpri,
    77  			exec:    *exe,
    78  			force:   *force,
    79  			verbose: *verbose,
    80  		}
    81  	}
    82  
    83  	// Second form:
    84  	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    85  	flag.Parse()
    86  	if flag.NArg() < 1 {
    87  		log.Println("Second form requires a COMMAND argument")
    88  		usage()
    89  	}
    90  	return form{
    91  		cmdName: flag.Arg(0),
    92  		cmdArgs: flag.Args()[1:],
    93  		lowPri:  *lowpri,
    94  		exec:    *exe,
    95  		force:   *force,
    96  		verbose: *verbose,
    97  	}
    98  }
    99  
   100  // run runs the command with the information from form.
   101  // Since run can potentially never return, since it can use Exec,
   102  // it should never return in any other case. Hence, if all goes well
   103  // at the end, we os.Exit(0)
   104  func run(n string, form form) {
   105  	cmd := exec.Command(n, form.cmdArgs...)
   106  	cmd.Stdin = os.Stdin
   107  	cmd.Stderr = os.Stderr
   108  	cmd.Stdout = os.Stdout
   109  	if err := cmd.Run(); err != nil {
   110  		exitErr, ok := err.(*exec.ExitError)
   111  		if !ok {
   112  			log.Fatal(err)
   113  		}
   114  		exitWithStatus(exitErr)
   115  	}
   116  	os.Exit(0)
   117  }
   118  
   119  func main() {
   120  	form := parseCommandLine()
   121  
   122  	if form.lowPri {
   123  		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
   124  			log.Printf("Cannot set low priority: %v", err)
   125  		}
   126  	}
   127  
   128  	if form.verbose {
   129  		debug = log.Printf
   130  	}
   131  
   132  	debug("Command name: %v\n", form.cmdName)
   133  	destFile := filepath.Join("/ubin", form.cmdName)
   134  
   135  	// Is the command there? This covers a race condition
   136  	// in that some other process may have caused it to be
   137  	// built.
   138  	if _, err := os.Stat(destFile); err == nil {
   139  		if !form.exec {
   140  			os.Exit(0)
   141  		}
   142  		run(destFile, form)
   143  	}
   144  
   145  	env := golang.Default()
   146  	env.Context.GOROOT = "/go"
   147  	env.Context.GOPATH = "/"
   148  
   149  	var srcDir string
   150  	err := filepath.Walk("/src", func(p string, fi os.FileInfo, err error) error {
   151  		if err != nil {
   152  			return nil
   153  		}
   154  
   155  		if fi.IsDir() && filepath.Base(p) == form.cmdName {
   156  			// Make sure it's an actual Go command.
   157  			pkg, err := env.PackageByPath(p)
   158  			if err == nil && pkg.IsCommand() {
   159  				srcDir = p
   160  			}
   161  		}
   162  		return nil
   163  	})
   164  	if err != nil {
   165  		log.Fatal(err)
   166  	}
   167  	if len(srcDir) == 0 {
   168  		log.Fatalf("Can not find source code for %q", form.cmdName)
   169  	}
   170  
   171  	if err := env.BuildDir(srcDir, destFile, golang.BuildOpts{}); err != nil {
   172  		log.Fatalf("Couldn't compile %q: %v", form.cmdName, err)
   173  	}
   174  
   175  	if form.exec {
   176  		run(destFile, form)
   177  	}
   178  }