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 }