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