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