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