github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/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 // -ludicrous: print out ALL the output from the go build commands 26 // -onlybuild: just build, do not exec 27 // -noforce: do not build if a file already exists at the destination 28 // -v: print all build commands 29 import ( 30 "flag" 31 "fmt" 32 "log" 33 "os" 34 "os/exec" 35 "path/filepath" 36 "strings" 37 "syscall" 38 39 "github.com/u-root/u-root/pkg/uroot/util" 40 ) 41 42 var ( 43 urpath = "/go/bin:/ubin:/buildbin:/bbin:/usr/local/bin:" 44 lowpri = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting") 45 ludicrous = flag.Bool("ludicrous", false, "print out ALL the output from the go build commands") 46 onlybuild = flag.Bool("onlybuild", false, "only build, i.e. do not execute the process after building") 47 noforce = flag.Bool("noforce", false, "do not build if a file already exists at the destination") 48 verbose = flag.Bool("v", false, "print all build commands") 49 debug = func(string, ...interface{}) {} 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 // Args intended for installcommand 58 lowPri bool 59 ludicrous bool 60 onlybuild bool 61 noForce 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 return form{ 76 cmdName: filepath.Base(os.Args[0]), 77 cmdArgs: os.Args[1:], 78 } 79 } 80 81 // Second form: 82 // installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...] 83 flag.Parse() 84 if flag.NArg() < 1 { 85 log.Println("Second form requires a COMMAND argument") 86 usage() 87 } 88 return form{ 89 cmdName: flag.Arg(0), 90 cmdArgs: flag.Args()[1:], 91 lowPri: *lowpri, 92 ludicrous: *ludicrous, 93 onlybuild: *onlybuild, 94 noForce: *noforce, 95 verbose: *verbose, 96 } 97 } 98 99 // run runs the command with the information from form. 100 // Since run can potentially never return, since it can use Exec, 101 // it should never return in any other case. Hence, if all goes well 102 // at the end, we os.Exit(0) 103 func run(n string, form form) { 104 if os.Getenv("INSTALLCOMMAND_NOEXEC") == "" { 105 err := syscall.Exec(n, append([]string{form.cmdName}, form.cmdArgs...), os.Environ()) 106 // Regardless of whether err is nil, if Exec returns at all, it failed 107 // at its job. Print an error and then let's see if a normal run can succeed. 108 log.Printf("Failed to exec %s: %v", form.cmdName, err) 109 } 110 111 cmd := exec.Command(n) 112 cmd.Args = append([]string{form.cmdName}, form.cmdArgs...) 113 cmd.Stdin = os.Stdin 114 cmd.Stderr = os.Stderr 115 cmd.Stdout = os.Stdout 116 if err := cmd.Run(); err != nil { 117 exitErr, ok := err.(*exec.ExitError) 118 if !ok { 119 log.Fatal(err) 120 } 121 exitWithStatus(exitErr) 122 } 123 os.Exit(0) 124 } 125 126 func main() { 127 form := parseCommandLine() 128 129 if form.lowPri { 130 if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil { 131 log.Printf("Cannot set low priority: %v", err) 132 } 133 } 134 135 a := []string{"install"} 136 137 if form.verbose { 138 debug = log.Printf 139 a = append(a, "-x") 140 } 141 142 debug("Command name: %v\n", form.cmdName) 143 destDir := "/ubin" 144 destFile := filepath.Join(destDir, form.cmdName) 145 146 // Is the command there? This covers a race condition 147 // in that some other process may have caused it to be 148 // built. 149 _, err := os.Stat(destFile) 150 151 // The file exists. 152 if err == nil { 153 if form.onlybuild { 154 os.Exit(0) 155 } 156 run(destFile, form) 157 } 158 159 // If we are here, things did not go so well. We have to build 160 // the command. Which means we have to find the source. Now 161 // that we can add new commands, we need to find out where the 162 // command is. The current rule is that command source is 163 // either in /src/github.com/u-root/u-root/cmds/cmdName or 164 // /src/*/*/cmdName. We stat that first name and, if it's there, 165 // we're good to go; if not, we do the glob. 166 // We considered building these paths into install command via an 167 // init() function in a file generated by initramfs. We went with this 168 // simpler approach as it allows people to add new commands even after boot: 169 // it suffices to, e.g. 170 // go get github.com/new/fancyshell 171 // ln -s /buildbin/installcommand /buildbin/fancyshell 172 // and the glob will find it. If we get to the point that the glob 173 // no longer works we can go with filepath.Walk (which glob uses anyway) 174 // but for now this works. 175 var src string 176 for _, p := range util.CmdsGlob { 177 g := filepath.Join("/src", p, form.cmdName) 178 debug("Check %v", g) 179 l, err := filepath.Glob(g) 180 debug("glob is %v %v", l, err) 181 if err != nil { 182 log.Printf("Glob %v fails: %v", g, err) 183 continue 184 } 185 if len(l) == 0 { 186 continue 187 } 188 189 if len(l) > 1 { 190 log.Printf("Found these paths for %v: %v; going with %v", form.cmdName, l, l[0]) 191 } 192 src = l[0] 193 break 194 } 195 196 if src == "" { 197 log.Fatalf("Can not find source code for %v", form.cmdName) 198 } 199 200 r, err := filepath.Rel("/src", src) 201 if err != nil { 202 log.Fatalf("Can't take rel of %v: %v", src, err) 203 } 204 cmd := exec.Command("go", append(a, r)...) 205 206 // Set GOGC if unset. The best value is determined empirically and 207 // depends on the machine and Go version. For the workload of compiling 208 // a small Go program, values larger than the default perform better. 209 // See: /scripts/build_perf.sh 210 if _, ok := os.LookupEnv("GOGC"); !ok { 211 cmd.Env = append(os.Environ(), "GOGC=400") 212 } 213 214 cmd.Dir = "/" 215 216 debug("Run %v", cmd) 217 out, err := cmd.CombinedOutput() 218 debug("installcommand: go build returned") 219 220 if err != nil { 221 p := os.Getenv("PATH") 222 log.Fatalf("installcommand: trying to build {cmdName: %v, PATH %s, err %v, out %s}", form.cmdName, p, err, out) 223 } 224 225 if *ludicrous { 226 debug(string(out)) 227 } 228 if !form.onlybuild { 229 run(destFile, form) 230 } 231 }