github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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/upath"
    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  	r       = upath.UrootPath
    49  )
    50  
    51  type form struct {
    52  	// Name of the command, ex: "ls"
    53  	cmdName string
    54  	// Args passed to the command, ex: {"-l", "-R"}
    55  	cmdArgs []string
    56  
    57  	// Args intended for installcommand
    58  	lowPri  bool
    59  	exec    bool
    60  	force   bool
    61  	verbose bool
    62  }
    63  
    64  func usage() {
    65  	fmt.Fprintf(os.Stderr, "Usage: installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]\n")
    66  	os.Exit(2)
    67  }
    68  
    69  // Parse the command line to determine the form.
    70  func parseCommandLine() form {
    71  	// First form:
    72  	//     SYMLINK [ARGS...]
    73  	if !strings.HasSuffix(os.Args[0], "installcommand") {
    74  		// This is almost certain to be a symlink, and it's no harm
    75  		// to check it.
    76  		f := upath.ResolveUntilLastSymlink(os.Args[0])
    77  		return form{
    78  			cmdName: filepath.Base(f),
    79  			cmdArgs: os.Args[1:],
    80  			lowPri:  *lowpri,
    81  			exec:    *exe,
    82  			force:   *force,
    83  			verbose: *verbose,
    84  		}
    85  	}
    86  
    87  	// Second form:
    88  	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
    89  	flag.Parse()
    90  	if flag.NArg() < 1 {
    91  		log.Println("Second form requires a COMMAND argument")
    92  		usage()
    93  	}
    94  	return form{
    95  		cmdName: flag.Arg(0),
    96  		cmdArgs: flag.Args()[1:],
    97  		lowPri:  *lowpri,
    98  		exec:    *exe,
    99  		force:   *force,
   100  		verbose: *verbose,
   101  	}
   102  }
   103  
   104  // run runs the command with the information from form.
   105  // Since run can potentially never return, since it can use Exec,
   106  // it should never return in any other case. Hence, if all goes well
   107  // at the end, we os.Exit(0)
   108  func run(n string, form form) {
   109  	cmd := exec.Command(n, form.cmdArgs...)
   110  	cmd.Stdin = os.Stdin
   111  	cmd.Stderr = os.Stderr
   112  	cmd.Stdout = os.Stdout
   113  	if err := cmd.Run(); err != nil {
   114  		exitErr, ok := err.(*exec.ExitError)
   115  		if !ok {
   116  			log.Fatal(err)
   117  		}
   118  		exitWithStatus(exitErr)
   119  	}
   120  	os.Exit(0)
   121  }
   122  
   123  func main() {
   124  	form := parseCommandLine()
   125  
   126  	if form.lowPri {
   127  		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
   128  			log.Printf("Cannot set low priority: %v", err)
   129  		}
   130  	}
   131  
   132  	destFile := filepath.Join(r("/ubin"), form.cmdName)
   133  
   134  	// Is the command there? This covers a race condition
   135  	// in that some other process may have caused it to be
   136  	// built.
   137  	if _, err := os.Stat(destFile); err == nil {
   138  		if !form.exec {
   139  			os.Exit(0)
   140  		}
   141  		run(destFile, form)
   142  	}
   143  
   144  	env := golang.Default()
   145  	env.Context.GOROOT = r("/go")
   146  	env.Context.GOPATH = r("/")
   147  	env.Context.CgoEnabled = false
   148  
   149  	var srcDir string
   150  	err := filepath.Walk(r("/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  }