github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+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  // installcommand installs a command from Go source files.
     6  //
     7  // Synopsis:
     8  //     SYMLINK [ARGS...]
     9  //     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    10  //
    11  // Description:
    12  //     In u-root's source mode, uncompiled commands in the /bin directory are
    13  //     symbolic links to installcommand. When executed through the symbolic
    14  //     link, installcommand will build the command from source and exec it.
    15  //
    16  //     The second form allows commands to be installed and exec'ed without a
    17  //     symbolic link. In this form additional arguments such as `-v` and
    18  //     `-ludicrous` can be passed into installcommand.
    19  //
    20  // Options:
    21  //     -lowpri:    the scheduler priority to lowered before starting
    22  //     -exec:      build and exec the command
    23  //     -force:     do not build if a file already exists at the destination
    24  //     -v:         print all build commands
    25  package main
    26  
    27  import (
    28  	"flag"
    29  	"fmt"
    30  	"log"
    31  	"os"
    32  	"os/exec"
    33  	"path/filepath"
    34  	"strings"
    35  	"syscall"
    36  
    37  	"github.com/u-root/u-root/pkg/golang"
    38  	"github.com/u-root/u-root/pkg/upath"
    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  	r       = upath.UrootPath
    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  		// This is almost certain to be a symlink, and it's no harm
    74  		// to check it.
    75  		f := upath.ResolveUntilLastSymlink(os.Args[0])
    76  		return form{
    77  			cmdName: filepath.Base(f),
    78  			cmdArgs: os.Args[1:],
    79  			lowPri:  *lowpri,
    80  			exec:    *exe,
    81  			force:   *force,
    82  			verbose: *verbose,
    83  		}
    84  	}
    85  
    86  	// Second form:
    87  	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    88  	flag.Parse()
    89  	if flag.NArg() < 1 {
    90  		log.Println("Second form requires a COMMAND argument")
    91  		usage()
    92  	}
    93  	return form{
    94  		cmdName: flag.Arg(0),
    95  		cmdArgs: flag.Args()[1:],
    96  		lowPri:  *lowpri,
    97  		exec:    *exe,
    98  		force:   *force,
    99  		verbose: *verbose,
   100  	}
   101  }
   102  
   103  // run runs the command with the information from form.
   104  // Since run can potentially never return, since it can use Exec,
   105  // it should never return in any other case. Hence, if all goes well
   106  // at the end, we os.Exit(0)
   107  func run(n string, form form) {
   108  	cmd := exec.Command(n, form.cmdArgs...)
   109  	cmd.Stdin = os.Stdin
   110  	cmd.Stderr = os.Stderr
   111  	cmd.Stdout = os.Stdout
   112  	if err := cmd.Run(); err != nil {
   113  		exitErr, ok := err.(*exec.ExitError)
   114  		if !ok {
   115  			log.Fatal(err)
   116  		}
   117  		exitWithStatus(exitErr)
   118  	}
   119  	os.Exit(0)
   120  }
   121  
   122  func main() {
   123  	form := parseCommandLine()
   124  
   125  	if form.lowPri {
   126  		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
   127  			log.Printf("Cannot set low priority: %v", err)
   128  		}
   129  	}
   130  
   131  	destFile := filepath.Join(r("/ubin"), form.cmdName)
   132  
   133  	// Is the command there? This covers a race condition
   134  	// in that some other process may have caused it to be
   135  	// built.
   136  	if _, err := os.Stat(destFile); err == nil {
   137  		if !form.exec {
   138  			os.Exit(0)
   139  		}
   140  		run(destFile, form)
   141  	}
   142  
   143  	env := golang.Default()
   144  	env.Context.GOROOT = r("/go")
   145  	env.Context.GOPATH = r("/")
   146  	env.Context.CgoEnabled = false
   147  
   148  	var srcDir string
   149  	err := filepath.Walk(r("/src"), func(p string, fi os.FileInfo, err error) error {
   150  		if err != nil {
   151  			return nil
   152  		}
   153  
   154  		if fi.IsDir() && filepath.Base(p) == form.cmdName {
   155  			// Make sure it's an actual Go command.
   156  			pkg, err := env.PackageByPath(p)
   157  			if err == nil && pkg.IsCommand() {
   158  				srcDir = p
   159  			}
   160  		}
   161  		return nil
   162  	})
   163  	if err != nil {
   164  		log.Fatal(err)
   165  	}
   166  	if len(srcDir) == 0 {
   167  		log.Fatalf("Can not find source code for %q", form.cmdName)
   168  	}
   169  
   170  	if err := env.BuildDir(srcDir, destFile, golang.BuildOpts{}); err != nil {
   171  		log.Fatalf("Couldn't compile %q: %v", form.cmdName, err)
   172  	}
   173  
   174  	if form.exec {
   175  		run(destFile, form)
   176  	}
   177  }