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 }