github.com/jlowellwofford/u-root@v1.0.0/pkg/uroot/files.go (about)

     1  // Copyright 2018 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  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"sort"
    14  
    15  	"github.com/u-root/u-root/pkg/cpio"
    16  	"golang.org/x/sys/unix"
    17  )
    18  
    19  // ArchiveFiles are host files and records to add to the resulting initramfs.
    20  type ArchiveFiles struct {
    21  	// Files is a map of relative archive path -> absolute host file path.
    22  	Files map[string]string
    23  
    24  	// Records is a map of relative archive path -> Record to use.
    25  	//
    26  	// TODO: While the only archive mode is cpio, this will be a
    27  	// cpio.Record. If or when there is another archival mode, we can add a
    28  	// similar uroot.Record type.
    29  	Records map[string]cpio.Record
    30  }
    31  
    32  // NewArchiveFiles returns a new archive files map.
    33  func NewArchiveFiles() ArchiveFiles {
    34  	return ArchiveFiles{
    35  		Files:   make(map[string]string),
    36  		Records: make(map[string]cpio.Record),
    37  	}
    38  }
    39  
    40  // SortedKeys returns a list of sorted paths in the archive.
    41  func (af ArchiveFiles) SortedKeys() []string {
    42  	keys := make([]string, 0, len(af.Files)+len(af.Records))
    43  	for dest := range af.Files {
    44  		keys = append(keys, dest)
    45  	}
    46  	for dest := range af.Records {
    47  		keys = append(keys, dest)
    48  	}
    49  	sort.Sort(sort.StringSlice(keys))
    50  	return keys
    51  }
    52  
    53  // AddFile adds a host file at `src` into the archive at `dest`.
    54  func (af ArchiveFiles) AddFile(src string, dest string) error {
    55  	src = filepath.Clean(src)
    56  	dest = path.Clean(dest)
    57  	if path.IsAbs(dest) {
    58  		return fmt.Errorf("archive path %q must not be absolute (host source %q)", dest, src)
    59  	}
    60  	if !filepath.IsAbs(src) {
    61  		return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest)
    62  	}
    63  
    64  	if _, ok := af.Records[dest]; ok {
    65  		return fmt.Errorf("record for %q already exists in archive", dest)
    66  	}
    67  	if srcFile, ok := af.Files[dest]; ok {
    68  		// Just a duplicate.
    69  		if src == srcFile {
    70  			return nil
    71  		}
    72  		return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src)
    73  	}
    74  
    75  	af.Files[dest] = src
    76  	return nil
    77  }
    78  
    79  // AddRecord adds a cpio.Record into the archive at `r.Name`.
    80  func (af ArchiveFiles) AddRecord(r cpio.Record) error {
    81  	r.Name = path.Clean(r.Name)
    82  	if filepath.IsAbs(r.Name) {
    83  		return fmt.Errorf("record name %q must not be absolute", r.Name)
    84  	}
    85  
    86  	if _, ok := af.Files[r.Name]; ok {
    87  		return fmt.Errorf("record for %q already exists in archive", r.Name)
    88  	}
    89  	if rr, ok := af.Records[r.Name]; ok {
    90  		if rr.Info == r.Info {
    91  			return nil
    92  		}
    93  		return fmt.Errorf("record for %q already exists in archive", r.Name)
    94  	}
    95  
    96  	af.Records[r.Name] = r
    97  	return nil
    98  }
    99  
   100  // Contains returns whether path `dest` is already contained in the archive.
   101  func (af ArchiveFiles) Contains(dest string) bool {
   102  	_, fok := af.Files[dest]
   103  	_, rok := af.Records[dest]
   104  	return fok || rok
   105  }
   106  
   107  // Rename renames a file in the archive.
   108  func (af ArchiveFiles) Rename(name string, newname string) {
   109  	if src, ok := af.Files[name]; ok {
   110  		delete(af.Files, name)
   111  		af.Files[newname] = src
   112  	}
   113  	if record, ok := af.Records[name]; ok {
   114  		delete(af.Records, name)
   115  		record.Name = newname
   116  		af.Records[newname] = record
   117  	}
   118  }
   119  
   120  // addParent recursively adds parent directory records for `name`.
   121  func (af ArchiveFiles) addParent(name string) {
   122  	parent := path.Dir(name)
   123  	if parent == "." {
   124  		return
   125  	}
   126  	if !af.Contains(parent) {
   127  		af.AddRecord(cpio.Directory(parent, 0755))
   128  	}
   129  	af.addParent(parent)
   130  }
   131  
   132  // fillInParents adds parent directory records for unparented files in `af`.
   133  func (af ArchiveFiles) fillInParents() {
   134  	for name := range af.Files {
   135  		af.addParent(name)
   136  	}
   137  	for name := range af.Records {
   138  		af.addParent(name)
   139  	}
   140  }
   141  
   142  // WriteTo writes all records and files in `af` to `w`.
   143  func (af ArchiveFiles) WriteTo(w ArchiveWriter) error {
   144  	// Add parent directories when not added specifically.
   145  	af.fillInParents()
   146  
   147  	// Reproducible builds: Files should be added to the archive in the
   148  	// same order.
   149  	for _, path := range af.SortedKeys() {
   150  		if record, ok := af.Records[path]; ok {
   151  			if err := w.WriteRecord(record); err != nil {
   152  				return err
   153  			}
   154  		}
   155  		if src, ok := af.Files[path]; ok {
   156  			if err := WriteFile(w, src, path); err != nil {
   157  				return err
   158  			}
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // Write uses the given options to determine which files need to be written
   165  // to the output file using the archive format `a` and writes them.
   166  func (opts *ArchiveOpts) Write() error {
   167  	// Add default records.
   168  	for _, rec := range opts.DefaultRecords {
   169  		// Ignore if it doesn't get added. Probably means the user
   170  		// included something for this file or directory already.
   171  		//
   172  		// TODO: ignore only when it already exists in archive.
   173  		opts.ArchiveFiles.AddRecord(rec)
   174  	}
   175  
   176  	// Write base archive.
   177  	if opts.BaseArchive != nil {
   178  		transform := cpio.MakeReproducible
   179  
   180  		// Rename init to inito if user doesn't want the existing init.
   181  		if !opts.UseExistingInit && opts.Contains("init") {
   182  			transform = func(r cpio.Record) cpio.Record {
   183  				if r.Name == "init" {
   184  					r.Name = "inito"
   185  				}
   186  				return cpio.MakeReproducible(r)
   187  			}
   188  		}
   189  		// If user wants the base archive init, but specified another
   190  		// init, make the other one inito.
   191  		if opts.UseExistingInit && opts.Contains("init") {
   192  			opts.Rename("init", "inito")
   193  		}
   194  
   195  		for {
   196  			f, err := opts.BaseArchive.ReadRecord()
   197  			if err == io.EOF {
   198  				break
   199  			}
   200  			if err != nil {
   201  				return err
   202  			}
   203  			// TODO: ignore only the error where it already exists
   204  			// in archive.
   205  			opts.ArchiveFiles.AddRecord(transform(f))
   206  		}
   207  	}
   208  
   209  	if err := opts.ArchiveFiles.WriteTo(opts.OutputFile); err != nil {
   210  		return err
   211  	}
   212  	return opts.OutputFile.Finish()
   213  }
   214  
   215  // WriteFile takes the file at `src` on the host system and adds it to the
   216  // archive `w` at path `dest`.
   217  //
   218  // If `src` is a directory, its children will be added to the archive as well.
   219  func WriteFile(w ArchiveWriter, src, dest string) error {
   220  	record, err := cpio.GetRecord(src)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	// Fix the name.
   226  	record.Name = dest
   227  	if err := w.WriteRecord(cpio.MakeReproducible(record)); err != nil {
   228  		return err
   229  	}
   230  
   231  	if record.Info.Mode&unix.S_IFMT == unix.S_IFDIR {
   232  		return children(src, func(name string) error {
   233  			return WriteFile(w, filepath.Join(src, name), filepath.Join(dest, name))
   234  		})
   235  	}
   236  	return nil
   237  }
   238  
   239  // children calls `fn` on all direct children of directory `dir`.
   240  func children(dir string, fn func(name string) error) error {
   241  	f, err := os.Open(dir)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	names, err := f.Readdirnames(-1)
   246  	f.Close()
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	for _, name := range names {
   252  		if err := fn(name); os.IsNotExist(err) {
   253  			// File was deleted in the meantime.
   254  			continue
   255  		} else if err != nil {
   256  			return err
   257  		}
   258  	}
   259  	return nil
   260  }