gopkg.in/hugelgupf/u-root.v9@v9.0.0-20180831063832-3f6f1057f09b/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 // 68 // - package imports; e.g. github.com/u-root/u-root/cmds/ls 69 // - globs of package imports; e.g. github.com/u-root/u-root/cmds/* 70 // - paths to package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 71 // - globs of paths to package directories; e.g. ./cmds/* 72 // 73 // Directories may be relative or absolute, with or without globs. 74 // Globs are resolved using filepath.Glob. 75 Packages []string 76 77 // BinaryDir is the directory in which the resulting binaries are 78 // placed inside the initramfs. 79 // 80 // BinaryDir may be empty, in which case Builder.DefaultBinaryDir() 81 // will be used. 82 BinaryDir string 83 } 84 85 // TargetDir returns the initramfs binary directory for these Commands. 86 func (c Commands) TargetDir() string { 87 if len(c.BinaryDir) != 0 { 88 return c.BinaryDir 89 } 90 return c.Builder.DefaultBinaryDir() 91 } 92 93 // Opts are the arguments to CreateInitramfs. 94 // 95 // Opts contains everything that influences initramfs creation such as the Go 96 // build environment. 97 // 98 // 99 type Opts struct { 100 // Env is the Golang build environment (GOOS, GOARCH, etc). 101 Env golang.Environ 102 103 // Commands specify packages to build using a specific builder. 104 // 105 // E.g. the following will build 'ls' and 'ip' in busybox mode, but 106 // 'cd' and 'cat' as separate binaries. 'cd', 'cat', 'bb', and symlinks 107 // from 'ls' and 'ip' will be added to the final initramfs. 108 // 109 // []Commands{ 110 // Commands{ 111 // Builder: builder.BBBuilder{}, 112 // Packages: []string{ 113 // "github.com/u-root/u-root/cmds/ls", 114 // "github.com/u-root/u-root/cmds/ip", 115 // }, 116 // }, 117 // Commands{ 118 // Builder: builder.BinaryBuilder{}, 119 // Packages: []string{ 120 // "github.com/u-root/u-root/cmds/cd", 121 // "github.com/u-root/u-root/cmds/cat", 122 // }, 123 // }, 124 // } 125 Commands []Commands 126 127 // TempDir is a temporary directory for builders to store files in. 128 TempDir string 129 130 // ExtraFiles are files to add to the archive in addition to the Go 131 // packages. 132 // 133 // Shared library dependencies will automatically also be added to the 134 // archive using ldd. 135 // 136 // The following formats are allowed in the list: 137 // 138 // - `hostPath:archivePath` adds the file from hostPath at the relative 139 // archivePath in the archive. 140 // - `justAPath` is added to the archive under justAPath. 141 ExtraFiles []string 142 143 // OutputFile is the archive output file. 144 OutputFile initramfs.Writer 145 146 // BaseArchive is an existing initramfs to include in the resulting 147 // initramfs. 148 BaseArchive initramfs.Reader 149 150 // UseExistingInit determines whether the existing init from 151 // BaseArchive should be used. 152 // 153 // If this is false, the "init" from BaseArchive will be renamed to 154 // "inito". 155 UseExistingInit bool 156 157 // InitCmd is the name of a command to link /init to. 158 // 159 // This can be an absolute path or the name of a command included in 160 // Commands. 161 // 162 // If this is empty, no init symlink will be created. 163 InitCmd string 164 165 // DefaultShell is the default shell to start after init. 166 // 167 // This can be an absolute path or the name of a command included in 168 // Commands. 169 // 170 // This must be specified to have a default shell. 171 DefaultShell string 172 } 173 174 // CreateInitramfs creates an initramfs built to opts' specifications. 175 func CreateInitramfs(opts Opts) error { 176 if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) { 177 return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err) 178 } 179 if opts.OutputFile == nil { 180 return fmt.Errorf("must give output file") 181 } 182 183 files := initramfs.NewFiles() 184 185 // Expand commands. 186 for index, cmds := range opts.Commands { 187 importPaths, err := ResolvePackagePaths(opts.Env, cmds.Packages) 188 if err != nil { 189 return err 190 } 191 opts.Commands[index].Packages = importPaths 192 } 193 194 // Add each build mode's commands to the archive. 195 for _, cmds := range opts.Commands { 196 builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder") 197 if err != nil { 198 return err 199 } 200 201 // Build packages. 202 bOpts := builder.Opts{ 203 Env: opts.Env, 204 Packages: cmds.Packages, 205 TempDir: builderTmpDir, 206 BinaryDir: cmds.TargetDir(), 207 } 208 if err := cmds.Builder.Build(files, bOpts); err != nil { 209 return fmt.Errorf("error building %#v: %v", bOpts, err) 210 } 211 } 212 213 // Open the target initramfs file. 214 archive := initramfs.Opts{ 215 Files: files, 216 OutputFile: opts.OutputFile, 217 BaseArchive: opts.BaseArchive, 218 UseExistingInit: opts.UseExistingInit, 219 DefaultRecords: DefaultRamfs, 220 } 221 222 if len(opts.DefaultShell) > 0 { 223 if target, err := resolveCommandOrPath(opts.DefaultShell, opts.Commands); err != nil { 224 log.Printf("No default shell: %v", err) 225 } else if err := archive.AddRecord(cpio.Symlink("bin/defaultsh", target)); err != nil { 226 return err 227 } 228 } 229 230 if len(opts.InitCmd) > 0 { 231 if target, err := resolveCommandOrPath(opts.InitCmd, opts.Commands); err != nil { 232 return fmt.Errorf("could not find init: %v", err) 233 } else if err := archive.AddRecord(cpio.Symlink("init", target)); err != nil { 234 return err 235 } 236 } 237 238 if err := ParseExtraFiles(archive.Files, opts.ExtraFiles, true); err != nil { 239 return err 240 } 241 242 // Finally, write the archive. 243 if err := initramfs.Write(&archive); err != nil { 244 return fmt.Errorf("error archiving: %v", err) 245 } 246 return nil 247 } 248 249 // resolvePackagePath finds import paths for a single import path or directory string 250 func resolvePackagePath(env golang.Environ, pkg string) ([]string, error) { 251 // Search the current working directory, as well GOROOT and GOPATHs 252 prefixes := append([]string{""}, env.SrcDirs()...) 253 // Resolve file system paths to package import paths. 254 for _, prefix := range prefixes { 255 path := filepath.Join(prefix, pkg) 256 matches, err := filepath.Glob(path) 257 if len(matches) == 0 || err != nil { 258 continue 259 } 260 261 var importPaths []string 262 for _, match := range matches { 263 p, err := env.PackageByPath(match) 264 if err != nil { 265 log.Printf("Skipping package %q: %v", match, err) 266 } else if p.ImportPath == "." { 267 // TODO: I do not completely understand why 268 // this is triggered. This is only an issue 269 // while this function is run inside the 270 // process of a "go test". 271 importPaths = append(importPaths, pkg) 272 } else { 273 importPaths = append(importPaths, p.ImportPath) 274 } 275 } 276 return importPaths, nil 277 } 278 279 // No file import paths found. Check if pkg still resolves as a package name. 280 if _, err := env.Package(pkg); err != nil { 281 return nil, fmt.Errorf("%q is neither package or path/glob: %v", pkg, err) 282 } 283 return []string{pkg}, nil 284 } 285 286 func resolveCommandOrPath(cmd string, cmds []Commands) (string, error) { 287 if filepath.IsAbs(cmd) { 288 return cmd, nil 289 } 290 291 for _, c := range cmds { 292 for _, p := range c.Packages { 293 // Figure out which build mode the shell is in, and symlink to 294 // that build modee 295 if name := path.Base(p); name == cmd { 296 return path.Join("/", c.TargetDir(), cmd), nil 297 } 298 } 299 } 300 301 return "", fmt.Errorf("command or path %q not included in u-root build", cmd) 302 } 303 304 // ResolvePackagePaths takes a list of Go package import paths and directories 305 // and turns them into exclusively import paths. 306 // 307 // Currently allowed formats: 308 // 309 // - package imports; e.g. github.com/u-root/u-root/cmds/ls 310 // - globs of package imports, e.g. github.com/u-root/u-root/cmds/* 311 // - paths to package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 312 // - globs of paths to package directories; e.g. ./cmds/* 313 // 314 // Directories may be relative or absolute, with or without globs. 315 // Globs are resolved using filepath.Glob. 316 func ResolvePackagePaths(env golang.Environ, pkgs []string) ([]string, error) { 317 var importPaths []string 318 for _, pkg := range pkgs { 319 paths, err := resolvePackagePath(env, pkg) 320 if err != nil { 321 return nil, err 322 } 323 importPaths = append(importPaths, paths...) 324 } 325 return importPaths, nil 326 } 327 328 // ParseExtraFiles adds files from the extraFiles list to the archive. 329 // 330 // The following formats are allowed in the extraFiles list: 331 // 332 // - `hostPath:archivePath` adds the file from hostPath at the relative 333 // archivePath in the archive. 334 // - `justAPath` is added to the archive under justAPath. 335 // 336 // ParseExtraFiles will also add ldd-listed dependencies if lddDeps is true. 337 func ParseExtraFiles(archive initramfs.Files, extraFiles []string, lddDeps bool) error { 338 var err error 339 // Add files from command line. 340 for _, file := range extraFiles { 341 var src, dst string 342 parts := strings.SplitN(file, ":", 2) 343 if len(parts) == 2 { 344 // treat the entry with the new src:dst syntax 345 src = filepath.Clean(parts[0]) 346 dst = filepath.Clean(parts[1]) 347 } else { 348 // plain old syntax 349 // filepath.Clean interprets an empty string as CWD for no good reason. 350 if len(file) == 0 { 351 continue 352 } 353 src = filepath.Clean(file) 354 dst = src 355 if filepath.IsAbs(dst) { 356 dst, err = filepath.Rel("/", dst) 357 if err != nil { 358 return fmt.Errorf("cannot make path relative to /: %v: %v", dst, err) 359 } 360 } 361 } 362 src, err := filepath.Abs(src) 363 if err != nil { 364 return fmt.Errorf("couldn't find absolute path for %q: %v", src, err) 365 } 366 if err := archive.AddFile(src, dst); err != nil { 367 return fmt.Errorf("couldn't add %q to archive: %v", file, err) 368 } 369 370 if lddDeps { 371 // Pull dependencies in the case of binaries. If `path` is not 372 // a binary, `libs` will just be empty. 373 libs, err := ldd.List([]string{src}) 374 if err != nil { 375 log.Printf("WARNING: couldn't add ldd dependencies for %q: %v", file, err) 376 return nil 377 } 378 for _, lib := range libs { 379 if err := archive.AddFile(lib, lib[1:]); err != nil { 380 log.Printf("WARNING: couldn't add ldd dependencies for %q: %v", lib, err) 381 } 382 } 383 } 384 } 385 return nil 386 }