github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/pkg/uroot/initramfs/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 initramfs
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"sort"
    13  
    14  	"github.com/u-root/u-root/pkg/cpio"
    15  )
    16  
    17  // Files are host files and records to add to the resulting initramfs.
    18  type Files struct {
    19  	// Files is a map of relative archive path -> absolute host file path.
    20  	Files map[string]string
    21  
    22  	// Records is a map of relative archive path -> Record to use.
    23  	//
    24  	// TODO: While the only archive mode is cpio, this will be a
    25  	// cpio.Record. If or when there is another archival mode, we can add a
    26  	// similar uroot.Record type.
    27  	Records map[string]cpio.Record
    28  }
    29  
    30  // NewFiles returns a new archive files map.
    31  func NewFiles() *Files {
    32  	return &Files{
    33  		Files:   make(map[string]string),
    34  		Records: make(map[string]cpio.Record),
    35  	}
    36  }
    37  
    38  // sortedKeys returns a list of sorted paths in the archive.
    39  func (af *Files) sortedKeys() []string {
    40  	keys := make([]string, 0, len(af.Files)+len(af.Records))
    41  	for dest := range af.Files {
    42  		keys = append(keys, dest)
    43  	}
    44  	for dest := range af.Records {
    45  		keys = append(keys, dest)
    46  	}
    47  	sort.Sort(sort.StringSlice(keys))
    48  	return keys
    49  }
    50  
    51  // AddFile adds a host file at src into the archive at dest.
    52  //
    53  // If src is a directory, it and its children will be added to the archive
    54  // relative to dest.
    55  //
    56  // Duplicate files with identical content will be silently ignored.
    57  func (af *Files) AddFile(src string, dest string) error {
    58  	src = filepath.Clean(src)
    59  	dest = path.Clean(dest)
    60  	if path.IsAbs(dest) {
    61  		return fmt.Errorf("archive path %q must not be absolute (host source %q)", dest, src)
    62  	}
    63  	if !filepath.IsAbs(src) {
    64  		return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest)
    65  	}
    66  
    67  	// We check if it's a directory first. If a directory already exists as
    68  	// a record or file, we want to include its children anyway.
    69  	sInfo, err := os.Lstat(src)
    70  	if err != nil {
    71  		return fmt.Errorf("Adding %q to archive failed because Lstat failed: %v", src, err)
    72  	}
    73  
    74  	// Recursively add children.
    75  	if sInfo.Mode().IsDir() {
    76  		err := children(src, func(name string) error {
    77  			return af.AddFile(filepath.Join(src, name), filepath.Join(dest, name))
    78  		})
    79  		if err != nil {
    80  			return err
    81  		}
    82  
    83  		// Only override an existing directory if all children were
    84  		// added successfully.
    85  		af.Files[dest] = src
    86  		return nil
    87  	}
    88  
    89  	if record, ok := af.Records[dest]; ok {
    90  		return fmt.Errorf("record for %q already exists in archive: %v", dest, record)
    91  	}
    92  
    93  	if srcFile, ok := af.Files[dest]; ok {
    94  		// Just a duplicate.
    95  		if src == srcFile {
    96  			return nil
    97  		}
    98  		return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src)
    99  	}
   100  
   101  	af.Files[dest] = src
   102  	return nil
   103  }
   104  
   105  // AddRecord adds a cpio.Record into the archive at `r.Name`.
   106  func (af *Files) AddRecord(r cpio.Record) error {
   107  	r.Name = path.Clean(r.Name)
   108  	if filepath.IsAbs(r.Name) {
   109  		return fmt.Errorf("record name %q must not be absolute", r.Name)
   110  	}
   111  
   112  	if src, ok := af.Files[r.Name]; ok {
   113  		return fmt.Errorf("record for %q already exists in archive: file %q", r.Name, src)
   114  	}
   115  	if rr, ok := af.Records[r.Name]; ok {
   116  		if rr.Info == r.Info {
   117  			return nil
   118  		}
   119  		return fmt.Errorf("record for %q already exists in archive: %v", r.Name, rr)
   120  	}
   121  
   122  	af.Records[r.Name] = r
   123  	return nil
   124  }
   125  
   126  // Contains returns whether path `dest` is already contained in the archive.
   127  func (af *Files) Contains(dest string) bool {
   128  	_, fok := af.Files[dest]
   129  	_, rok := af.Records[dest]
   130  	return fok || rok
   131  }
   132  
   133  // Rename renames a file in the archive.
   134  func (af *Files) Rename(name string, newname string) {
   135  	if src, ok := af.Files[name]; ok {
   136  		delete(af.Files, name)
   137  		af.Files[newname] = src
   138  	}
   139  	if record, ok := af.Records[name]; ok {
   140  		delete(af.Records, name)
   141  		record.Name = newname
   142  		af.Records[newname] = record
   143  	}
   144  }
   145  
   146  // addParent recursively adds parent directory records for `name`.
   147  func (af *Files) addParent(name string) {
   148  	parent := path.Dir(name)
   149  	if parent == "." {
   150  		return
   151  	}
   152  	if !af.Contains(parent) {
   153  		af.AddRecord(cpio.Directory(parent, 0755))
   154  	}
   155  	af.addParent(parent)
   156  }
   157  
   158  // fillInParents adds parent directory records for unparented files in `af`.
   159  func (af *Files) fillInParents() {
   160  	for name := range af.Files {
   161  		af.addParent(name)
   162  	}
   163  	for name := range af.Records {
   164  		af.addParent(name)
   165  	}
   166  }
   167  
   168  // WriteTo writes all records and files in `af` to `w`.
   169  func (af *Files) WriteTo(w Writer) error {
   170  	// Add parent directories when not added specifically.
   171  	af.fillInParents()
   172  
   173  	// Reproducible builds: Files should be added to the archive in the
   174  	// same order.
   175  	for _, path := range af.sortedKeys() {
   176  		if record, ok := af.Records[path]; ok {
   177  			if err := w.WriteRecord(record); err != nil {
   178  				return err
   179  			}
   180  		}
   181  		if src, ok := af.Files[path]; ok {
   182  			if err := writeFile(w, src, path); err != nil {
   183  				return err
   184  			}
   185  		}
   186  	}
   187  	return nil
   188  }
   189  
   190  // writeFile takes the file at `src` on the host system and adds it to the
   191  // archive `w` at path `dest`.
   192  //
   193  // If `src` is a directory, its children will be added to the archive as well.
   194  func writeFile(w Writer, src, dest string) error {
   195  	record, err := cpio.GetRecord(src)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	// Fix the name.
   201  	record.Name = dest
   202  	return w.WriteRecord(cpio.MakeReproducible(record))
   203  }
   204  
   205  // children calls `fn` on all direct children of directory `dir`.
   206  func children(dir string, fn func(name string) error) error {
   207  	f, err := os.Open(dir)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	names, err := f.Readdirnames(-1)
   212  	f.Close()
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	for _, name := range names {
   218  		if err := fn(name); os.IsNotExist(err) {
   219  			// File was deleted in the meantime.
   220  			continue
   221  		} else if err != nil {
   222  			return err
   223  		}
   224  	}
   225  	return nil
   226  }