github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/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"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    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  )
    20  
    21  // These constants are used in DefaultRamfs.
    22  const (
    23  	// This is the literal timezone file for GMT-0. Given that we have no
    24  	// idea where we will be running, GMT seems a reasonable guess. If it
    25  	// matters, setup code should download and change this to something
    26  	// else.
    27  	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"
    28  
    29  	nameserver = "nameserver 8.8.8.8\n"
    30  )
    31  
    32  var (
    33  	builders = map[string]Build{
    34  		"source": SourceBuild,
    35  		"bb":     BBBuild,
    36  		"binary": BinaryBuild,
    37  	}
    38  	archivers = map[string]Archiver{
    39  		"cpio": CPIOArchiver{
    40  			Format: "newc",
    41  		},
    42  		"dir": DirArchiver{},
    43  	}
    44  )
    45  
    46  // DefaultRamfs are files that are contained in all u-root initramfs archives
    47  // by default.
    48  var DefaultRamfs = []cpio.Record{
    49  	cpio.Directory("tcz", 0755),
    50  	cpio.Directory("etc", 0755),
    51  	cpio.Directory("dev", 0755),
    52  	cpio.Directory("ubin", 0755),
    53  	cpio.Directory("usr", 0755),
    54  	cpio.Directory("usr/lib", 0755),
    55  	cpio.Directory("lib64", 0755),
    56  	cpio.Directory("bin", 0755),
    57  	cpio.CharDev("dev/console", 0600, 5, 1),
    58  	cpio.CharDev("dev/tty", 0666, 5, 0),
    59  	cpio.CharDev("dev/null", 0666, 1, 3),
    60  	cpio.CharDev("dev/port", 0640, 1, 4),
    61  	cpio.CharDev("dev/urandom", 0666, 1, 9),
    62  	cpio.StaticFile("etc/resolv.conf", nameserver, 0644),
    63  	cpio.StaticFile("etc/localtime", gmt0, 0644),
    64  }
    65  
    66  // Opts are the arguments to CreateInitramfs.
    67  type Opts struct {
    68  	// Env is the build environment (OS, arch, etc).
    69  	Env golang.Environ
    70  
    71  	// Builder is the build format.
    72  	//
    73  	// This can currently be "source" or "bb".
    74  	Builder Build
    75  
    76  	// Archiver is the initramfs archival format.
    77  	//
    78  	// Only "cpio" is currently supported.
    79  	Archiver Archiver
    80  
    81  	// Packages are the Go packages to add to the archive.
    82  	//
    83  	// Currently allowed formats:
    84  	//   Go package imports; e.g. github.com/u-root/u-root/cmds/ls
    85  	//   Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls
    86  	//   Globs of paths to Go package directories; e.g. ./cmds/*
    87  	Packages []string
    88  
    89  	// ExtraFiles are files to add to the archive in addition to the Go
    90  	// packages.
    91  	//
    92  	// Shared library dependencies will automatically also be added to the
    93  	// archive using ldd.
    94  	ExtraFiles []string
    95  
    96  	// TempDir is a temporary directory for the builder to store files in.
    97  	TempDir string
    98  
    99  	// OutputFile is the archive output file.
   100  	OutputFile ArchiveWriter
   101  
   102  	// BaseArchive is an existing initramfs to include in the resulting
   103  	// initramfs.
   104  	BaseArchive ArchiveReader
   105  
   106  	// UseExistingInit determines whether the existing init from
   107  	// BaseArchive should be used.
   108  	//
   109  	// If this is false, the "init" from BaseArchive will be renamed to
   110  	// "inito".
   111  	UseExistingInit bool
   112  }
   113  
   114  // CreateInitramfs creates an initramfs built to `opts`' specifications.
   115  func CreateInitramfs(opts Opts) error {
   116  	if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) {
   117  		return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err)
   118  	}
   119  	if opts.OutputFile == nil {
   120  		return fmt.Errorf("must give output file")
   121  	}
   122  
   123  	var importPaths []string
   124  	// Resolve file system paths to package import paths.
   125  	for _, pkg := range opts.Packages {
   126  		matches, err := filepath.Glob(pkg)
   127  		if len(matches) == 0 || err != nil {
   128  			if _, perr := opts.Env.Package(pkg); perr != nil {
   129  				return fmt.Errorf("%q is neither package or path/glob: %v / %v", pkg, err, perr)
   130  			}
   131  			importPaths = append(importPaths, pkg)
   132  		}
   133  
   134  		for _, match := range matches {
   135  			p, err := opts.Env.PackageByPath(match)
   136  			if err != nil {
   137  				log.Printf("Skipping package %q: %v", match, err)
   138  			} else {
   139  				importPaths = append(importPaths, p.ImportPath)
   140  			}
   141  		}
   142  	}
   143  
   144  	builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder")
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// Build the packages.
   150  	bOpts := BuildOpts{
   151  		Env:      opts.Env,
   152  		Packages: importPaths,
   153  		TempDir:  builderTmpDir,
   154  	}
   155  	files, err := opts.Builder(bOpts)
   156  	if err != nil {
   157  		return fmt.Errorf("error building %#v: %v", bOpts, err)
   158  	}
   159  
   160  	// Open the target initramfs file.
   161  	archive := ArchiveOpts{
   162  		ArchiveFiles:    files,
   163  		OutputFile:      opts.OutputFile,
   164  		BaseArchive:     opts.BaseArchive,
   165  		UseExistingInit: opts.UseExistingInit,
   166  		DefaultRecords:  DefaultRamfs,
   167  	}
   168  
   169  	// Add files from command line.
   170  	for _, file := range opts.ExtraFiles {
   171  		var src, dst string
   172  		parts := strings.SplitN(file, ":", 2)
   173  		if len(parts) == 2 {
   174  			// treat the entry with the new src:dst syntax
   175  			src = parts[0]
   176  			dst = parts[1]
   177  		} else {
   178  			// plain old syntax
   179  			src = file
   180  			dst = file
   181  		}
   182  		src, err := filepath.Abs(src)
   183  		if err != nil {
   184  			return fmt.Errorf("couldn't find absolute path for %q: %v", src, err)
   185  		}
   186  		if err := archive.AddFile(src, dst); err != nil {
   187  			return fmt.Errorf("couldn't add %q to archive: %v", file, err)
   188  		}
   189  
   190  		// Pull dependencies in the case of binaries. If `path` is not
   191  		// a binary, `libs` will just be empty.
   192  		libs, err := ldd.List([]string{src})
   193  		if err != nil {
   194  			return fmt.Errorf("couldn't list ldd dependencies for %q: %v", file, err)
   195  		}
   196  		for _, lib := range libs {
   197  			if err := archive.AddFile(lib, lib[1:]); err != nil {
   198  				return fmt.Errorf("couldn't add %q to archive: %v", lib, err)
   199  			}
   200  		}
   201  	}
   202  
   203  	// Finally, write the archive.
   204  	if err := archive.Write(); err != nil {
   205  		return fmt.Errorf("error archiving: %v", err)
   206  	}
   207  	return nil
   208  }
   209  
   210  // BuildOpts are arguments to the Build function.
   211  type BuildOpts struct {
   212  	// Env is the Go environment to use to compile and link packages.
   213  	Env golang.Environ
   214  
   215  	// Packages are the Go package import paths to compile.
   216  	//
   217  	// Builders need not support resolving packages by path.
   218  	//
   219  	// E.g. cmd/go or github.com/u-root/u-root/cmds/ls.
   220  	Packages []string
   221  
   222  	// TempDir is a temporary directory where the compilation mode compiled
   223  	// binaries can be placed.
   224  	//
   225  	// TempDir should contain no files.
   226  	TempDir string
   227  }
   228  
   229  // Build uses the given options to build Go packages and returns a list of
   230  // files to be included in an initramfs archive.
   231  type Build func(BuildOpts) (ArchiveFiles, error)
   232  
   233  // ArchiveOpts are the options for building the initramfs archive.
   234  type ArchiveOpts struct {
   235  	// ArchiveFiles are the files to be included.
   236  	//
   237  	// Files in ArchiveFiles generally have priority over files in
   238  	// DefaultRecords or BaseArchive.
   239  	ArchiveFiles
   240  
   241  	// DefaultRecords is a set of files to be included in the initramfs.
   242  	DefaultRecords []cpio.Record
   243  
   244  	// OutputFile is the file to write to.
   245  	OutputFile ArchiveWriter
   246  
   247  	// BaseArchive is an existing archive to add files to.
   248  	//
   249  	// BaseArchive may be nil.
   250  	BaseArchive ArchiveReader
   251  
   252  	// UseExistingInit determines whether the init from BaseArchive is used
   253  	// or not, if BaseArchive is specified.
   254  	//
   255  	// If this is false, the "init" file in BaseArchive will be renamed
   256  	// "inito" in the output archive.
   257  	UseExistingInit bool
   258  }
   259  
   260  // Archiver is an archive format that builds an archive using a given set of
   261  // files.
   262  type Archiver interface {
   263  	// OpenWriter opens an archive writer at `path`.
   264  	//
   265  	// If `path` is unspecified, implementations may choose an arbitrary
   266  	// default location, potentially based on `goos` and `goarch`.
   267  	OpenWriter(path, goos, goarch string) (ArchiveWriter, error)
   268  
   269  	// Reader returns an ArchiveReader wrapper using the given io.Reader.
   270  	Reader(io.ReaderAt) ArchiveReader
   271  }
   272  
   273  // ArchiveWriter is an object that files can be written to.
   274  type ArchiveWriter interface {
   275  	// WriteRecord writes the given file record.
   276  	WriteRecord(cpio.Record) error
   277  
   278  	// Finish finishes the archive.
   279  	Finish() error
   280  }
   281  
   282  // ArchiveReader is an object that files can be read from.
   283  type ArchiveReader interface {
   284  	// ReadRecord reads a file record.
   285  	ReadRecord() (cpio.Record, error)
   286  }
   287  
   288  // GetBuilder returns the Build function for the named build mode.
   289  func GetBuilder(name string) (Build, error) {
   290  	build, ok := builders[name]
   291  	if !ok {
   292  		return nil, fmt.Errorf("couldn't find builder %q", name)
   293  	}
   294  	return build, nil
   295  }
   296  
   297  // GetArchiver returns the archive mode for the named archive.
   298  func GetArchiver(name string) (Archiver, error) {
   299  	archiver, ok := archivers[name]
   300  	if !ok {
   301  		return nil, fmt.Errorf("couldn't find archival format %q", name)
   302  	}
   303  	return archiver, nil
   304  }
   305  
   306  // DefaultPackageImports returns a list of default u-root packages to include.
   307  func DefaultPackageImports(env golang.Environ) ([]string, error) {
   308  	// Find u-root directory.
   309  	urootPkg, err := env.Package("github.com/u-root/u-root")
   310  	if err != nil {
   311  		return nil, fmt.Errorf("Couldn't find u-root src directory: %v", err)
   312  	}
   313  
   314  	matches, err := filepath.Glob(filepath.Join(urootPkg.Dir, "cmds/*"))
   315  	if err != nil {
   316  		return nil, fmt.Errorf("couldn't find u-root cmds: %v", err)
   317  	}
   318  	pkgs := make([]string, 0, len(matches))
   319  	for _, match := range matches {
   320  		pkg, err := env.PackageByPath(match)
   321  		if err == nil {
   322  			pkgs = append(pkgs, pkg.ImportPath)
   323  		}
   324  	}
   325  	return pkgs, nil
   326  }