github.com/rck/u-root@v0.0.0-20180106144920-7eb602e381bb/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/filepath"
    13  	"sort"
    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  var (
    22  	builders = map[string]Build{
    23  		"source": SourceBuild,
    24  		"bb":     BBBuild,
    25  	}
    26  	archivers = map[string]Archiver{
    27  		"cpio": CPIOArchiver{
    28  			Format: "newc",
    29  		},
    30  	}
    31  )
    32  
    33  // Opts are the arguments to CreateInitramfs.
    34  type Opts struct {
    35  	// Env is the build environment (OS, arch, etc).
    36  	Env golang.Environ
    37  
    38  	// Builder is the build format.
    39  	//
    40  	// This can currently be "source" or "bb".
    41  	Builder Build
    42  
    43  	// Archiver is the initramfs archival format.
    44  	//
    45  	// Only "cpio" is currently supported.
    46  	Archiver Archiver
    47  
    48  	// Packages are the Go packages to add to the archive.
    49  	//
    50  	// Currently allowed formats:
    51  	//   Go package imports; e.g. github.com/u-root/u-root/cmds/ls
    52  	//   Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls
    53  	//   Globs of paths to Go package directories; e.g. ./cmds/*
    54  	Packages []string
    55  
    56  	// ExtraFiles are files to add to the archive in addition to the Go
    57  	// packages.
    58  	//
    59  	// Shared library dependencies will automatically also be added to the
    60  	// archive using ldd.
    61  	ExtraFiles []string
    62  
    63  	// TempDir is a temporary directory for the builder to store files in.
    64  	TempDir string
    65  
    66  	// OutputFile is the archive output file.
    67  	OutputFile *os.File
    68  
    69  	// BaseArchive is an existing initramfs to include in the resulting
    70  	// initramfs.
    71  	BaseArchive *os.File
    72  
    73  	// UseExistingInit determines whether the existing init from
    74  	// BaseArchive should be used.
    75  	//
    76  	// If this is false, the "init" from BaseArchive will be renamed to
    77  	// "inito".
    78  	UseExistingInit bool
    79  }
    80  
    81  // CreateInitramfs creates an initramfs built to `opts`' specifications.
    82  func CreateInitramfs(opts Opts) error {
    83  	if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) {
    84  		return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err)
    85  	}
    86  	if opts.OutputFile == nil {
    87  		return fmt.Errorf("must give output file")
    88  	}
    89  
    90  	var importPaths []string
    91  	// Resolve file system paths to package import paths.
    92  	for _, pkg := range opts.Packages {
    93  		matches, err := filepath.Glob(pkg)
    94  		if len(matches) == 0 || err != nil {
    95  			if _, perr := opts.Env.Package(pkg); perr != nil {
    96  				return fmt.Errorf("%q is neither package or path/glob: %v / %v", pkg, err, perr)
    97  			}
    98  			importPaths = append(importPaths, pkg)
    99  		}
   100  
   101  		for _, match := range matches {
   102  			p, err := opts.Env.PackageByPath(match)
   103  			if err != nil {
   104  				log.Printf("Skipping package %q: %v", match, err)
   105  			} else {
   106  				importPaths = append(importPaths, p.ImportPath)
   107  			}
   108  		}
   109  	}
   110  
   111  	builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder")
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	// Build the packages.
   117  	bOpts := BuildOpts{
   118  		Env:      opts.Env,
   119  		Packages: importPaths,
   120  		TempDir:  builderTmpDir,
   121  	}
   122  	files, err := opts.Builder(bOpts)
   123  	if err != nil {
   124  		return fmt.Errorf("error building %#v: %v", bOpts, err)
   125  	}
   126  
   127  	archiveTmpDir, err := ioutil.TempDir(opts.TempDir, "archive")
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// Open the target initramfs file.
   133  	archive := ArchiveOpts{
   134  		ArchiveFiles:    files,
   135  		OutputFile:      opts.OutputFile,
   136  		BaseArchive:     opts.BaseArchive,
   137  		UseExistingInit: opts.UseExistingInit,
   138  		TempDir:         archiveTmpDir,
   139  	}
   140  
   141  	// Add files from command line.
   142  	for _, file := range opts.ExtraFiles {
   143  		var src, dst string
   144  		parts := strings.SplitN(file, ":", 2)
   145  		if len(parts) == 2 {
   146  			// treat the entry with the new src:dst syntax
   147  			src = parts[0]
   148  			dst = parts[1]
   149  		} else {
   150  			// plain old syntax
   151  			src = file
   152  			dst = file
   153  		}
   154  		src, err := filepath.Abs(src)
   155  		if err != nil {
   156  			return fmt.Errorf("couldn't find absolute path for %q: %v", src, err)
   157  		}
   158  		if err := archive.AddFile(src, dst); err != nil {
   159  			return fmt.Errorf("couldn't add %q to archive: %v", file, err)
   160  		}
   161  
   162  		// Pull dependencies in the case of binaries. If `path` is not
   163  		// a binary, `libs` will just be empty.
   164  		libs, err := ldd.List([]string{src})
   165  		if err != nil {
   166  			return fmt.Errorf("couldn't list ldd dependencies for %q: %v", file, err)
   167  		}
   168  		for _, lib := range libs {
   169  			if err := archive.AddFile(lib, lib[1:]); err != nil {
   170  				return fmt.Errorf("couldn't add %q to archive: %v", lib, err)
   171  			}
   172  		}
   173  	}
   174  
   175  	// Finally, write the archive.
   176  	if err := opts.Archiver.Archive(archive); err != nil {
   177  		return fmt.Errorf("error archiving: %v", err)
   178  	}
   179  	return nil
   180  }
   181  
   182  // BuildOpts are arguments to the Build function.
   183  type BuildOpts struct {
   184  	// Env is the Go environment to use to compile and link packages.
   185  	Env golang.Environ
   186  
   187  	// Packages are the Go package import paths to compile.
   188  	//
   189  	// Builders need not support resolving packages by path.
   190  	//
   191  	// E.g. cmd/go or github.com/u-root/u-root/cmds/ls.
   192  	Packages []string
   193  
   194  	// TempDir is a temporary directory where the compilation mode compiled
   195  	// binaries can be placed.
   196  	//
   197  	// TempDir should contain no files.
   198  	TempDir string
   199  }
   200  
   201  // Build uses the given options to build Go packages and returns a list of
   202  // files to be included in an initramfs archive.
   203  type Build func(BuildOpts) (ArchiveFiles, error)
   204  
   205  // ArchiveOpts are the options for building the initramfs archive.
   206  type ArchiveOpts struct {
   207  	// ArchiveFiles are the files to be included.
   208  	ArchiveFiles
   209  
   210  	// TempDir is a temporary directory that can be used at the archiver's
   211  	// discretion.
   212  	//
   213  	// TempDir should contain no files.
   214  	TempDir string
   215  
   216  	// OutputFile is the file to write to.
   217  	OutputFile *os.File
   218  
   219  	// BaseArchive is an existing archive to add files to.
   220  	//
   221  	// BaseArchive may be nil.
   222  	BaseArchive *os.File
   223  
   224  	// UseExistingInit determines whether the init from BaseArchive is used
   225  	// or not, if BaseArchive is specified.
   226  	//
   227  	// If this is false, the "init" file in BaseArchive will be renamed
   228  	// "inito" in the output archive.
   229  	UseExistingInit bool
   230  }
   231  
   232  // Archiver is an archive format that builds an archive using a given set of
   233  // files.
   234  type Archiver interface {
   235  	// Archive builds an archive file.
   236  	Archive(ArchiveOpts) error
   237  
   238  	// DefaultExtension is the default file extension of the archive format.
   239  	DefaultExtension() string
   240  }
   241  
   242  // GetBuilder returns the Build function for the named build mode.
   243  func GetBuilder(name string) (Build, error) {
   244  	build, ok := builders[name]
   245  	if !ok {
   246  		return nil, fmt.Errorf("couldn't find builder %q", name)
   247  	}
   248  	return build, nil
   249  }
   250  
   251  // GetArchiver returns the archive mode for the named archive.
   252  func GetArchiver(name string) (Archiver, error) {
   253  	archiver, ok := archivers[name]
   254  	if !ok {
   255  		return nil, fmt.Errorf("couldn't find archival format %q", name)
   256  	}
   257  	return archiver, nil
   258  }
   259  
   260  // ArchiveFiles are host files and records to add to
   261  type ArchiveFiles struct {
   262  	// Files is a map of relative archive path -> absolute host file path.
   263  	Files map[string]string
   264  
   265  	// Records is a map of relative archive path -> Record to use.
   266  	//
   267  	// TODO: While the only archive mode is cpio, this will be a
   268  	// cpio.Record. If or when there is another archival mode, we can add a
   269  	// similar uroot.Record type.
   270  	Records map[string]cpio.Record
   271  }
   272  
   273  // NewArchiveFiles returns a new archive files map.
   274  func NewArchiveFiles() ArchiveFiles {
   275  	return ArchiveFiles{
   276  		Files:   make(map[string]string),
   277  		Records: make(map[string]cpio.Record),
   278  	}
   279  }
   280  
   281  // SortedKeys returns a list of sorted paths in the archive.
   282  func (af ArchiveFiles) SortedKeys() []string {
   283  	keys := make([]string, 0, len(af.Files)+len(af.Records))
   284  	for dest := range af.Files {
   285  		keys = append(keys, dest)
   286  	}
   287  	for dest := range af.Records {
   288  		keys = append(keys, dest)
   289  	}
   290  	sort.Sort(sort.StringSlice(keys))
   291  	return keys
   292  }
   293  
   294  // AddFile adds a host file at `src` into the archive at `dest`.
   295  func (af ArchiveFiles) AddFile(src string, dest string) error {
   296  	if filepath.IsAbs(dest) {
   297  		return fmt.Errorf("cannot add absolute path %q (from %q) to archive", dest, src)
   298  	}
   299  	if !filepath.IsAbs(src) {
   300  		return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest)
   301  	}
   302  
   303  	if _, ok := af.Records[dest]; ok {
   304  		return fmt.Errorf("record for %q already exists in archive", dest)
   305  	}
   306  	if srcFile, ok := af.Files[dest]; ok {
   307  		// Just a duplicate.
   308  		if src == srcFile {
   309  			return nil
   310  		}
   311  		return fmt.Errorf("archive file %q already comes from %q", dest, src)
   312  	}
   313  
   314  	af.Files[dest] = src
   315  	return nil
   316  }
   317  
   318  // AddRecord adds a cpio.Record into the archive at `r.Name`.
   319  func (af ArchiveFiles) AddRecord(r cpio.Record) error {
   320  	if filepath.IsAbs(r.Name) {
   321  		return fmt.Errorf("cannot add absolute path %q to archive", r.Name)
   322  	}
   323  
   324  	if _, ok := af.Files[r.Name]; ok {
   325  		return fmt.Errorf("record for %q already exists in archive", r.Name)
   326  	}
   327  	if rr, ok := af.Records[r.Name]; ok {
   328  		if rr.Info == r.Info {
   329  			return nil
   330  		}
   331  		return fmt.Errorf("record for %q already exists", r.Name)
   332  	}
   333  
   334  	af.Records[r.Name] = r
   335  	return nil
   336  }
   337  
   338  // Contains returns whether path `dest` is already contained in the archive.
   339  func (af ArchiveFiles) Contains(dest string) bool {
   340  	_, fok := af.Files[dest]
   341  	_, rok := af.Records[dest]
   342  	return fok || rok
   343  }
   344  
   345  // DefaultPackageImports returns a list of default u-root packages to include.
   346  func DefaultPackageImports(env golang.Environ) ([]string, error) {
   347  	// Find u-root directory.
   348  	urootPkg, err := env.Package("github.com/u-root/u-root")
   349  	if err != nil {
   350  		return nil, fmt.Errorf("Couldn't find u-root src directory: %v", err)
   351  	}
   352  
   353  	matches, err := filepath.Glob(filepath.Join(urootPkg.Dir, "cmds/*"))
   354  	if err != nil {
   355  		return nil, fmt.Errorf("couldn't find u-root cmds: %v", err)
   356  	}
   357  	pkgs := make([]string, 0, len(matches))
   358  	for _, match := range matches {
   359  		pkg, err := env.PackageByPath(match)
   360  		if err == nil {
   361  			pkgs = append(pkgs, pkg.ImportPath)
   362  		}
   363  	}
   364  	return pkgs, nil
   365  }