gopkg.in/hugelgupf/u-root.v2@v2.0.0-20180831055005-3f8fdb0ce09d/pkg/uroot/uroot.go (about) 1 // Copyright 2015-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 uroot 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "log" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 16 "github.com/u-root/u-root/pkg/cpio" 17 "github.com/u-root/u-root/pkg/golang" 18 "github.com/u-root/u-root/pkg/ldd" 19 "github.com/u-root/u-root/pkg/uroot/builder" 20 "github.com/u-root/u-root/pkg/uroot/initramfs" 21 ) 22 23 // These constants are used in DefaultRamfs. 24 const ( 25 // This is the literal timezone file for GMT-0. Given that we have no 26 // idea where we will be running, GMT seems a reasonable guess. If it 27 // matters, setup code should download and change this to something 28 // else. 29 gmt0 = "TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00GMT\x00\x00\x00TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00GMT\x00\x00\x00\nGMT0\n" 30 31 nameserver = "nameserver 8.8.8.8\n" 32 ) 33 34 // DefaultRamfs are files that are contained in all u-root initramfs archives 35 // by default. 36 var DefaultRamfs = []cpio.Record{ 37 cpio.Directory("tcz", 0755), 38 cpio.Directory("etc", 0755), 39 cpio.Directory("dev", 0755), 40 cpio.Directory("tmp", 0777), 41 cpio.Directory("ubin", 0755), 42 cpio.Directory("usr", 0755), 43 cpio.Directory("usr/lib", 0755), 44 cpio.Directory("var/log", 0777), 45 cpio.Directory("lib64", 0755), 46 cpio.Directory("bin", 0755), 47 cpio.CharDev("dev/console", 0600, 5, 1), 48 cpio.CharDev("dev/tty", 0666, 5, 0), 49 cpio.CharDev("dev/null", 0666, 1, 3), 50 cpio.CharDev("dev/port", 0640, 1, 4), 51 cpio.CharDev("dev/urandom", 0666, 1, 9), 52 cpio.StaticFile("etc/resolv.conf", nameserver, 0644), 53 cpio.StaticFile("etc/localtime", gmt0, 0644), 54 } 55 56 // Commands specifies a list of Golang packages to build with a builder, e.g. 57 // in busybox mode or source mode or binary mode. 58 // 59 // See Builder for an explanation of build modes. 60 type Commands struct { 61 // Builder is the Go compiler mode. 62 Builder builder.Builder 63 64 // Packages are the Go packages to compile and add to the archive. 65 // 66 // Currently allowed formats: 67 // - Go package imports; e.g. github.com/u-root/u-root/cmds/ls 68 // - Globs of Go package imports; e.g. github.com/u-root/u-root/cmds/* 69 // - Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 70 // - Globs of paths to Go package directories; e.g. ./cmds/* 71 Packages []string 72 73 // BinaryDir is the directory in which the resulting binaries are 74 // placed inside the initramfs. 75 BinaryDir string 76 } 77 78 // TargetDir returns the initramfs binary directory for these Commands. 79 func (c Commands) TargetDir() string { 80 if len(c.BinaryDir) != 0 { 81 return c.BinaryDir 82 } 83 return c.Builder.DefaultBinaryDir() 84 } 85 86 // Opts are the arguments to CreateInitramfs. 87 // 88 // Opts contains everything that influences initramfs creation such as the Go 89 // build environment. 90 // 91 // 92 type Opts struct { 93 // Env is the Golang build environment (GOOS, GOARCH, etc). 94 Env golang.Environ 95 96 // Commands specify packages to build using a specific builder. 97 Commands []Commands 98 99 // TempDir is a temporary directory for builders to store files in. 100 TempDir string 101 102 // ExtraFiles are files to add to the archive in addition to the Go 103 // packages. 104 // 105 // Shared library dependencies will automatically also be added to the 106 // archive using ldd. 107 ExtraFiles []string 108 109 // OutputFile is the archive output file. 110 OutputFile initramfs.Writer 111 112 // BaseArchive is an existing initramfs to include in the resulting 113 // initramfs. 114 BaseArchive initramfs.Reader 115 116 // UseExistingInit determines whether the existing init from 117 // BaseArchive should be used. 118 // 119 // If this is false, the "init" from BaseArchive will be renamed to 120 // "inito". 121 UseExistingInit bool 122 123 // InitCmd is the name of a command to link /init to. 124 // 125 // This can be an absolute path or the name of a command included in 126 // Commands. 127 // 128 // If this is empty, no init symlink will be created. 129 InitCmd string 130 131 // DefaultShell is the default shell to start after init. 132 // 133 // This can be an absolute path or the name of a command included in 134 // Commands. 135 // 136 // This must be specified to have a default shell. 137 DefaultShell string 138 } 139 140 // CreateInitramfs creates an initramfs built to opts' specifications. 141 func CreateInitramfs(opts Opts) error { 142 if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) { 143 return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err) 144 } 145 if opts.OutputFile == nil { 146 return fmt.Errorf("must give output file") 147 } 148 149 files := initramfs.NewFiles() 150 151 // Expand commands. 152 for index, cmds := range opts.Commands { 153 importPaths, err := ResolvePackagePaths(opts.Env, cmds.Packages) 154 if err != nil { 155 return err 156 } 157 opts.Commands[index].Packages = importPaths 158 } 159 160 // Add each build mode's commands to the archive. 161 for _, cmds := range opts.Commands { 162 builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder") 163 if err != nil { 164 return err 165 } 166 167 // Build packages. 168 bOpts := builder.Opts{ 169 Env: opts.Env, 170 Packages: cmds.Packages, 171 TempDir: builderTmpDir, 172 BinaryDir: cmds.TargetDir(), 173 } 174 if err := cmds.Builder.Build(files, bOpts); err != nil { 175 return fmt.Errorf("error building %#v: %v", bOpts, err) 176 } 177 } 178 179 // Open the target initramfs file. 180 archive := initramfs.Opts{ 181 Files: files, 182 OutputFile: opts.OutputFile, 183 BaseArchive: opts.BaseArchive, 184 UseExistingInit: opts.UseExistingInit, 185 DefaultRecords: DefaultRamfs, 186 } 187 188 if len(opts.DefaultShell) > 0 { 189 if target, err := resolveCommandOrPath(opts.DefaultShell, opts.Commands); err != nil { 190 log.Printf("No default shell: %v", err) 191 } else if err := archive.AddRecord(cpio.Symlink("bin/defaultsh", target)); err != nil { 192 return err 193 } 194 } 195 196 if len(opts.InitCmd) > 0 { 197 if target, err := resolveCommandOrPath(opts.InitCmd, opts.Commands); err != nil { 198 return fmt.Errorf("could not find init: %v", err) 199 } else if err := archive.AddRecord(cpio.Symlink("init", target)); err != nil { 200 return err 201 } 202 } 203 204 if err := ParseExtraFiles(archive.Files, opts.ExtraFiles, true); err != nil { 205 return err 206 } 207 208 // Finally, write the archive. 209 if err := initramfs.Write(&archive); err != nil { 210 return fmt.Errorf("error archiving: %v", err) 211 } 212 return nil 213 } 214 215 // resolvePackagePath finds import paths for a single import path or directory string 216 func resolvePackagePath(env golang.Environ, pkg string) ([]string, error) { 217 // Search the current working directory, as well GOROOT and GOPATHs 218 prefixes := append([]string{""}, env.SrcDirs()...) 219 // Resolve file system paths to package import paths. 220 for _, prefix := range prefixes { 221 path := filepath.Join(prefix, pkg) 222 matches, err := filepath.Glob(path) 223 if len(matches) == 0 || err != nil { 224 continue 225 } 226 227 var importPaths []string 228 for _, match := range matches { 229 p, err := env.PackageByPath(match) 230 if err != nil { 231 log.Printf("Skipping package %q: %v", match, err) 232 } else if p.ImportPath == "." { 233 // TODO: I do not completely understand why 234 // this is triggered. This is only an issue 235 // while this function is run inside the 236 // process of a "go test". 237 importPaths = append(importPaths, pkg) 238 } else { 239 importPaths = append(importPaths, p.ImportPath) 240 } 241 } 242 return importPaths, nil 243 } 244 245 // No file import paths found. Check if pkg still resolves as a package name. 246 if _, err := env.Package(pkg); err != nil { 247 return nil, fmt.Errorf("%q is neither package or path/glob: %v", pkg, err) 248 } 249 return []string{pkg}, nil 250 } 251 252 func resolveCommandOrPath(cmd string, cmds []Commands) (string, error) { 253 if filepath.IsAbs(cmd) { 254 return cmd, nil 255 } 256 257 for _, c := range cmds { 258 for _, p := range c.Packages { 259 // Figure out which build mode the shell is in, and symlink to 260 // that build modee 261 if name := path.Base(p); name == cmd { 262 return path.Join("/", c.TargetDir(), cmd), nil 263 } 264 } 265 } 266 267 return "", fmt.Errorf("command or path %q not included in u-root build", cmd) 268 } 269 270 // ResolvePackagePaths takes a list of Go package import paths and directories 271 // and turns them into exclusively import paths. 272 // 273 // Currently allowed formats: 274 // Go package imports; e.g. github.com/u-root/u-root/cmds/ls 275 // Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 276 // Globs of package imports, e.g. github.com/u-root/u-root/cmds/* 277 // Globs of paths to Go package directories; e.g. ./cmds/* 278 func ResolvePackagePaths(env golang.Environ, pkgs []string) ([]string, error) { 279 var importPaths []string 280 for _, pkg := range pkgs { 281 paths, err := resolvePackagePath(env, pkg) 282 if err != nil { 283 return nil, err 284 } 285 importPaths = append(importPaths, paths...) 286 } 287 return importPaths, nil 288 } 289 290 // ParseExtraFiles adds files from the extraFiles list to the archive. 291 // 292 // The following formats are allowed in the extraFiles list: 293 // - hostPath:archivePath adds the file from hostPath at the relative 294 // archivePath in the archive. 295 // - justAPath is added to the archive under justAPath. 296 // 297 // ParseExtraFiles will also add ldd-listed dependencies if lddDeps is true. 298 func ParseExtraFiles(archive initramfs.Files, extraFiles []string, lddDeps bool) error { 299 var err error 300 // Add files from command line. 301 for _, file := range extraFiles { 302 var src, dst string 303 parts := strings.SplitN(file, ":", 2) 304 if len(parts) == 2 { 305 // treat the entry with the new src:dst syntax 306 src = filepath.Clean(parts[0]) 307 dst = filepath.Clean(parts[1]) 308 } else { 309 // plain old syntax 310 // filepath.Clean interprets an empty string as CWD for no good reason. 311 if len(file) == 0 { 312 continue 313 } 314 src = filepath.Clean(file) 315 dst = src 316 if filepath.IsAbs(dst) { 317 dst, err = filepath.Rel("/", dst) 318 if err != nil { 319 return fmt.Errorf("cannot make path relative to /: %v: %v", dst, err) 320 } 321 } 322 } 323 src, err := filepath.Abs(src) 324 if err != nil { 325 return fmt.Errorf("couldn't find absolute path for %q: %v", src, err) 326 } 327 if err := archive.AddFile(src, dst); err != nil { 328 return fmt.Errorf("couldn't add %q to archive: %v", file, err) 329 } 330 331 if lddDeps { 332 // Pull dependencies in the case of binaries. If `path` is not 333 // a binary, `libs` will just be empty. 334 libs, err := ldd.List([]string{src}) 335 if err != nil { 336 log.Printf("WARNING: couldn't add ldd dependencies for %q: %v", file, err) 337 return nil 338 } 339 for _, lib := range libs { 340 if err := archive.AddFile(lib, lib[1:]); err != nil { 341 log.Printf("WARNING: couldn't add ldd dependencies for %q: %v", lib, err) 342 } 343 } 344 } 345 } 346 return nil 347 } 348 349 // DefaultPackageImports returns a list of default u-root packages to include. 350 func DefaultPackageImports(env golang.Environ) ([]string, error) { 351 // Find u-root directory. 352 urootPkg, err := env.Package("github.com/u-root/u-root") 353 if err != nil { 354 return nil, fmt.Errorf("Couldn't find u-root src directory: %v", err) 355 } 356 357 matches, err := filepath.Glob(filepath.Join(urootPkg.Dir, "cmds/*")) 358 if err != nil { 359 return nil, fmt.Errorf("couldn't find u-root cmds: %v", err) 360 } 361 pkgs := make([]string, 0, len(matches)) 362 for _, match := range matches { 363 pkg, err := env.PackageByPath(match) 364 if err == nil { 365 pkgs = append(pkgs, pkg.ImportPath) 366 } 367 } 368 return pkgs, nil 369 }