github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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 creates root file systems from Go programs. 6 // 7 // uroot will appropriately compile the Go programs, create symlinks for their 8 // names, and assemble an initramfs with additional files as specified. 9 package uroot 10 11 import ( 12 "fmt" 13 "io/ioutil" 14 "os" 15 "path" 16 "path/filepath" 17 "strings" 18 19 "github.com/u-root/u-root/pkg/cpio" 20 "github.com/u-root/u-root/pkg/golang" 21 "github.com/u-root/u-root/pkg/ldd" 22 "github.com/u-root/u-root/pkg/uflag" 23 "github.com/u-root/u-root/pkg/ulog" 24 "github.com/u-root/u-root/pkg/uroot/builder" 25 "github.com/u-root/u-root/pkg/uroot/initramfs" 26 ) 27 28 // These constants are used in DefaultRamfs. 29 const ( 30 // This is the literal timezone file for GMT-0. Given that we have no 31 // idea where we will be running, GMT seems a reasonable guess. If it 32 // matters, setup code should download and change this to something 33 // else. 34 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" 35 36 nameserver = "nameserver 8.8.8.8\n" 37 ) 38 39 // DefaultRamRamfs returns a cpio.Archive for the target OS. 40 // If an OS is not known it will return a reasonable u-root specific 41 // default. 42 func DefaultRamfs() *cpio.Archive { 43 switch golang.Default().GOOS { 44 case "linux": 45 return cpio.ArchiveFromRecords([]cpio.Record{ 46 cpio.Directory("bin", 0755), 47 cpio.Directory("dev", 0755), 48 cpio.Directory("env", 0755), 49 cpio.Directory("etc", 0755), 50 cpio.Directory("lib64", 0755), 51 cpio.Directory("proc", 0755), 52 cpio.Directory("sys", 0755), 53 cpio.Directory("tcz", 0755), 54 cpio.Directory("tmp", 0777), 55 cpio.Directory("ubin", 0755), 56 cpio.Directory("usr", 0755), 57 cpio.Directory("usr/lib", 0755), 58 cpio.Directory("var/log", 0777), 59 cpio.CharDev("dev/console", 0600, 5, 1), 60 cpio.CharDev("dev/tty", 0666, 5, 0), 61 cpio.CharDev("dev/null", 0666, 1, 3), 62 cpio.CharDev("dev/port", 0640, 1, 4), 63 cpio.CharDev("dev/urandom", 0666, 1, 9), 64 cpio.StaticFile("etc/resolv.conf", nameserver, 0644), 65 cpio.StaticFile("etc/localtime", gmt0, 0644), 66 }) 67 default: 68 return cpio.ArchiveFromRecords([]cpio.Record{ 69 cpio.Directory("ubin", 0755), 70 cpio.Directory("bbin", 0755), 71 }) 72 } 73 } 74 75 // Commands specifies a list of Golang packages to build with a builder, e.g. 76 // in busybox mode, source mode, or binary mode. 77 // 78 // See Builder for an explanation of build modes. 79 type Commands struct { 80 // Builder is the Go compiler mode. 81 Builder builder.Builder 82 83 // Packages are the Go commands to include (compiled or otherwise) and 84 // add to the archive. 85 // 86 // Currently allowed formats: 87 // 88 // - package imports; e.g. github.com/u-root/u-root/cmds/ls 89 // - globs of package imports; e.g. github.com/u-root/u-root/cmds/* 90 // - paths to package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 91 // - globs of paths to package directories; e.g. ./cmds/* 92 // 93 // Directories may be relative or absolute, with or without globs. 94 // Globs are resolved using filepath.Glob. 95 Packages []string 96 97 // BinaryDir is the directory in which the resulting binaries are 98 // placed inside the initramfs. 99 // 100 // BinaryDir may be empty, in which case Builder.DefaultBinaryDir() 101 // will be used. 102 BinaryDir string 103 } 104 105 // TargetDir returns the initramfs binary directory for these Commands. 106 func (c Commands) TargetDir() string { 107 if len(c.BinaryDir) != 0 { 108 return c.BinaryDir 109 } 110 return c.Builder.DefaultBinaryDir() 111 } 112 113 // Opts are the arguments to CreateInitramfs. 114 // 115 // Opts contains everything that influences initramfs creation such as the Go 116 // build environment. 117 type Opts struct { 118 // Env is the Golang build environment (GOOS, GOARCH, etc). 119 Env golang.Environ 120 121 // Commands specify packages to build using a specific builder. 122 // 123 // E.g. the following will build 'ls' and 'ip' in busybox mode, but 124 // 'cd' and 'cat' as separate binaries. 'cd', 'cat', 'bb', and symlinks 125 // from 'ls' and 'ip' will be added to the final initramfs. 126 // 127 // []Commands{ 128 // Commands{ 129 // Builder: builder.BusyBox, 130 // Packages: []string{ 131 // "github.com/u-root/u-root/cmds/ls", 132 // "github.com/u-root/u-root/cmds/ip", 133 // }, 134 // }, 135 // Commands{ 136 // Builder: builder.Binary, 137 // Packages: []string{ 138 // "github.com/u-root/u-root/cmds/cd", 139 // "github.com/u-root/u-root/cmds/cat", 140 // }, 141 // }, 142 // } 143 Commands []Commands 144 145 // TempDir is a temporary directory for builders to store files in. 146 TempDir string 147 148 // ExtraFiles are files to add to the archive in addition to the Go 149 // packages. 150 // 151 // Shared library dependencies will automatically also be added to the 152 // archive using ldd, unless SkipLDD (below) is true. 153 // 154 // The following formats are allowed in the list: 155 // 156 // - "/home/chrisko/foo:root/bar" adds the file from absolute path 157 // /home/chrisko/foo on the host at the relative root/bar in the 158 // archive. 159 // - "/home/foo" is equivalent to "/home/foo:home/foo". 160 ExtraFiles []string 161 162 // If true, do not use ldd to pick up dependencies from local machine for 163 // ExtraFiles. Useful if you have all deps revision controlled and wish to 164 // ensure builds are repeatable, and/or if the local machine's binaries use 165 // instructions unavailable on the emulated cpu. 166 // 167 // If you turn this on but do not manually list all deps, affected binaries 168 // will misbehave. 169 SkipLDD bool 170 171 // OutputFile is the archive output file. 172 OutputFile initramfs.Writer 173 174 // BaseArchive is an existing initramfs to include in the resulting 175 // initramfs. 176 BaseArchive initramfs.Reader 177 178 // UseExistingInit determines whether the existing init from 179 // BaseArchive should be used. 180 // 181 // If this is false, the "init" from BaseArchive will be renamed to 182 // "inito" (init-original). 183 UseExistingInit bool 184 185 // InitCmd is the name of a command to link /init to. 186 // 187 // This can be an absolute path or the name of a command included in 188 // Commands. 189 // 190 // If this is empty, no init symlink will be created, but a user may 191 // still specify a command called init or include an /init file. 192 InitCmd string 193 194 // UinitCmd is the name of a command to link /bin/uinit to. 195 // 196 // This can be an absolute path or the name of a command included in 197 // Commands. 198 // 199 // The u-root init will always attempt to fork/exec a uinit program, 200 // and append arguments from both the kernel command-line 201 // (uroot.uinitargs) as well as specified in UinitArgs. 202 // 203 // If this is empty, no uinit symlink will be created, but a user may 204 // still specify a command called uinit or include a /bin/uinit file. 205 UinitCmd string 206 207 // UinitArgs are the arguments passed to /bin/uinit. 208 UinitArgs []string 209 210 // DefaultShell is the default shell to start after init. 211 // 212 // This can be an absolute path or the name of a command included in 213 // Commands. 214 // 215 // This must be specified to have a default shell. 216 DefaultShell string 217 218 // NoStrip builds unstripped binaries. 219 NoStrip bool 220 } 221 222 // CreateInitramfs creates an initramfs built to opts' specifications. 223 func CreateInitramfs(logger ulog.Logger, opts Opts) error { 224 if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) { 225 return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err) 226 } 227 if opts.OutputFile == nil { 228 return fmt.Errorf("must give output file") 229 } 230 231 files := initramfs.NewFiles() 232 233 // Expand commands. 234 for index, cmds := range opts.Commands { 235 importPaths, err := ResolvePackagePaths(logger, opts.Env, cmds.Packages) 236 if err != nil { 237 return err 238 } 239 opts.Commands[index].Packages = importPaths 240 } 241 242 // Add each build mode's commands to the archive. 243 for _, cmds := range opts.Commands { 244 builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder") 245 if err != nil { 246 return err 247 } 248 249 // Build packages. 250 bOpts := builder.Opts{ 251 Env: opts.Env, 252 Packages: cmds.Packages, 253 TempDir: builderTmpDir, 254 BinaryDir: cmds.TargetDir(), 255 NoStrip: opts.NoStrip, 256 } 257 if err := cmds.Builder.Build(files, bOpts); err != nil { 258 return fmt.Errorf("error building: %v", err) 259 } 260 } 261 262 // Open the target initramfs file. 263 archive := &initramfs.Opts{ 264 Files: files, 265 OutputFile: opts.OutputFile, 266 BaseArchive: opts.BaseArchive, 267 UseExistingInit: opts.UseExistingInit, 268 } 269 if err := ParseExtraFiles(logger, archive.Files, opts.ExtraFiles, !opts.SkipLDD); err != nil { 270 return err 271 } 272 273 if err := opts.addSymlinkTo(logger, archive, opts.UinitCmd, "bin/uinit"); err != nil { 274 return fmt.Errorf("%v: specify -uinitcmd=\"\" to ignore this error and build without a uinit", err) 275 } 276 if len(opts.UinitArgs) > 0 { 277 if err := archive.AddRecord(cpio.StaticFile("etc/uinit.flags", uflag.ArgvToFile(opts.UinitArgs), 0444)); err != nil { 278 return fmt.Errorf("%v: could not add uinit arguments from UinitArgs (-uinitcmd) to initramfs", err) 279 } 280 } 281 if err := opts.addSymlinkTo(logger, archive, opts.InitCmd, "init"); err != nil { 282 return fmt.Errorf("%v: specify -initcmd=\"\" to ignore this error and build without an init", err) 283 } 284 if err := opts.addSymlinkTo(logger, archive, opts.DefaultShell, "bin/sh"); err != nil { 285 return fmt.Errorf("%v: specify -defaultsh=\"\" to ignore this error and build without a shell", err) 286 } 287 if err := opts.addSymlinkTo(logger, archive, opts.DefaultShell, "bin/defaultsh"); err != nil { 288 return fmt.Errorf("%v: specify -defaultsh=\"\" to ignore this error and build without a shell", err) 289 } 290 291 // Finally, write the archive. 292 if err := initramfs.Write(archive); err != nil { 293 return fmt.Errorf("error archiving: %v", err) 294 } 295 return nil 296 } 297 298 func (o *Opts) addSymlinkTo(logger ulog.Logger, archive *initramfs.Opts, command string, source string) error { 299 if len(command) == 0 { 300 return nil 301 } 302 303 target, err := resolveCommandOrPath(command, o.Commands) 304 if err != nil { 305 if o.Commands != nil { 306 return fmt.Errorf("could not create symlink from %q to %q: %v", source, command, err) 307 } 308 logger.Printf("Could not create symlink from %q to %q: %v", source, command, err) 309 return nil 310 } 311 312 // Make a relative symlink from /source -> target 313 // 314 // E.g. bin/defaultsh -> target, so you need to 315 // filepath.Rel(/bin, target) since relative symlinks are 316 // evaluated from their PARENT directory. 317 relTarget, err := filepath.Rel(filepath.Join("/", filepath.Dir(source)), target) 318 if err != nil { 319 return err 320 } 321 322 if err := archive.AddRecord(cpio.Symlink(source, relTarget)); err != nil { 323 return fmt.Errorf("failed to add symlink %s -> %s to initramfs: %v", source, relTarget, err) 324 } 325 return nil 326 } 327 328 // resolvePackagePath finds import paths for a single import path or directory string 329 func resolvePackagePath(logger ulog.Logger, env golang.Environ, pkg string) ([]string, error) { 330 // Search the current working directory, as well GOROOT and GOPATHs 331 prefixes := append([]string{""}, env.SrcDirs()...) 332 // Resolve file system paths to package import paths. 333 for _, prefix := range prefixes { 334 path := filepath.Join(prefix, pkg) 335 matches, err := filepath.Glob(path) 336 if len(matches) == 0 || err != nil { 337 continue 338 } 339 340 var importPaths []string 341 for _, match := range matches { 342 343 // Only match directories for building. 344 // Skip anything that is not a directory 345 fileInfo, _ := os.Stat(match) 346 if !fileInfo.IsDir() { 347 continue 348 } 349 350 p, err := env.PackageByPath(match) 351 if err != nil { 352 logger.Printf("Skipping package %q: %v", match, err) 353 } else if p.ImportPath == "." { 354 // TODO: I do not completely understand why 355 // this is triggered. This is only an issue 356 // while this function is run inside the 357 // process of a "go test". 358 importPaths = append(importPaths, pkg) 359 } else { 360 importPaths = append(importPaths, p.ImportPath) 361 } 362 } 363 return importPaths, nil 364 } 365 366 // No file import paths found. Check if pkg still resolves as a package name. 367 if _, err := env.Package(pkg); err != nil { 368 return nil, fmt.Errorf("%q is neither package or path/glob: %v", pkg, err) 369 } 370 return []string{pkg}, nil 371 } 372 373 func resolveCommandOrPath(cmd string, cmds []Commands) (string, error) { 374 if strings.ContainsRune(cmd, filepath.Separator) { 375 return cmd, nil 376 } 377 378 // Each build mode has its own binary dir (/bbin or /bin or /ubin). 379 // 380 // Figure out which build mode the shell is in, and symlink to that 381 // build mode. 382 for _, c := range cmds { 383 for _, p := range c.Packages { 384 if name := path.Base(p); name == cmd { 385 return path.Join("/", c.TargetDir(), cmd), nil 386 } 387 } 388 } 389 390 return "", fmt.Errorf("command or path %q not included in u-root build", cmd) 391 } 392 393 // ResolvePackagePaths takes a list of Go package import paths and directories 394 // and turns them into exclusively import paths. 395 // 396 // Currently allowed formats: 397 // 398 // - package imports; e.g. github.com/u-root/u-root/cmds/ls 399 // - globs of package imports, e.g. github.com/u-root/u-root/cmds/* 400 // - paths to package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 401 // - globs of paths to package directories; e.g. ./cmds/* 402 // - if an entry starts with "-" it excludes the matching package(s) 403 // 404 // Directories may be relative or absolute, with or without globs. 405 // Globs are resolved using filepath.Glob. 406 func ResolvePackagePaths(logger ulog.Logger, env golang.Environ, pkgs []string) ([]string, error) { 407 var includes []string 408 excludes := map[string]bool{} 409 for _, pkg := range pkgs { 410 isExclude := false 411 if strings.HasPrefix(pkg, "-") { 412 pkg = pkg[1:] 413 isExclude = true 414 } 415 paths, err := resolvePackagePath(logger, env, pkg) 416 if err != nil { 417 return nil, err 418 } 419 if !isExclude { 420 includes = append(includes, paths...) 421 } else { 422 for _, p := range paths { 423 excludes[p] = true 424 } 425 } 426 } 427 var result []string 428 for _, p := range includes { 429 if !excludes[p] { 430 result = append(result, p) 431 } 432 } 433 return result, nil 434 } 435 436 // ParseExtraFiles adds files from the extraFiles list to the archive. 437 // 438 // The following formats are allowed in the extraFiles list: 439 // 440 // - "/home/chrisko/foo:root/bar" adds the file from absolute path 441 // /home/chrisko/foo on the host at the relative root/bar in the 442 // archive. 443 // - "/home/foo" is equivalent to "/home/foo:home/foo". 444 // 445 // ParseExtraFiles will also add ldd-listed dependencies if lddDeps is true. 446 func ParseExtraFiles(logger ulog.Logger, archive *initramfs.Files, extraFiles []string, lddDeps bool) error { 447 var err error 448 // Add files from command line. 449 for _, file := range extraFiles { 450 var src, dst string 451 parts := strings.SplitN(file, ":", 2) 452 if len(parts) == 2 { 453 // treat the entry with the new src:dst syntax 454 src = filepath.Clean(parts[0]) 455 dst = filepath.Clean(parts[1]) 456 } else { 457 // plain old syntax 458 // filepath.Clean interprets an empty string as CWD for no good reason. 459 if len(file) == 0 { 460 continue 461 } 462 src = filepath.Clean(file) 463 dst = src 464 if filepath.IsAbs(dst) { 465 dst, err = filepath.Rel("/", dst) 466 if err != nil { 467 return fmt.Errorf("cannot make path relative to /: %v: %v", dst, err) 468 } 469 } 470 } 471 src, err := filepath.Abs(src) 472 if err != nil { 473 return fmt.Errorf("couldn't find absolute path for %q: %v", src, err) 474 } 475 if err := archive.AddFileNoFollow(src, dst); err != nil { 476 return fmt.Errorf("couldn't add %q to archive: %v", file, err) 477 } 478 479 if lddDeps { 480 // Pull dependencies in the case of binaries. If `path` is not 481 // a binary, `libs` will just be empty. 482 libs, err := ldd.List([]string{src}) 483 if err != nil { 484 logger.Printf("WARNING: couldn't add ldd dependencies for %q: %v", file, err) 485 continue 486 } 487 for _, lib := range libs { 488 // N.B.: we already added information about the src. 489 // Don't add it twice. We have to do this check here in 490 // case we're renaming the src to a different dest. 491 if lib == src { 492 continue 493 } 494 if err := archive.AddFileNoFollow(lib, lib[1:]); err != nil { 495 logger.Printf("WARNING: couldn't add ldd dependencies for %q: %v", lib, err) 496 } 497 } 498 } 499 } 500 return nil 501 } 502 503 // AddCommands adds commands to the build. 504 func (o *Opts) AddCommands(c ...Commands) { 505 o.Commands = append(o.Commands, c...) 506 } 507 508 func (o *Opts) AddBusyBoxCommands(pkgs ...string) { 509 for i, cmds := range o.Commands { 510 if cmds.Builder == builder.BusyBox { 511 o.Commands[i].Packages = append(cmds.Packages, pkgs...) 512 return 513 } 514 } 515 516 // Not found? Add first busybox. 517 o.AddCommands(BusyBoxCmds(pkgs...)...) 518 } 519 520 // BinaryCmds returns a list of Commands with cmds built as a busybox. 521 func BinaryCmds(cmds ...string) []Commands { 522 if len(cmds) == 0 { 523 return nil 524 } 525 return []Commands{ 526 { 527 Builder: builder.Binary, 528 Packages: cmds, 529 }, 530 } 531 } 532 533 // BusyBoxCmds returns a list of Commands with cmds built as a busybox. 534 func BusyBoxCmds(cmds ...string) []Commands { 535 if len(cmds) == 0 { 536 return nil 537 } 538 return []Commands{ 539 { 540 Builder: builder.BusyBox, 541 Packages: cmds, 542 }, 543 } 544 }