github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+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  	"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  	if follow {
    65  		s, err := filepath.EvalSymlinks(src)
    66  		if err != nil {
    67  			return err
    68  		}
    69  		src = s
    70  	}
    71  
    72  	// We check if it's a directory first. If a directory already exists as
    73  	// a record or file, we want to include its children anyway.
    74  	sInfo, err := os.Lstat(src)
    75  	if err != nil {
    76  		return fmt.Errorf("adding %q to archive failed because Lstat failed: %v", src, err)
    77  	}
    78  
    79  	// Recursively add children.
    80  	if sInfo.Mode().IsDir() {
    81  		err := children(src, func(name string) error {
    82  			return af.addFile(filepath.Join(src, name), filepath.Join(dest, name), follow)
    83  		})
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		// Only override an existing directory if all children were
    89  		// added successfully.
    90  		af.Files[dest] = src
    91  		return nil
    92  	}
    93  
    94  	if record, ok := af.Records[dest]; ok {
    95  		return fmt.Errorf("record for %q already exists in archive: %v", dest, record)
    96  	}
    97  
    98  	if srcFile, ok := af.Files[dest]; ok {
    99  		// Just a duplicate.
   100  		if src == srcFile {
   101  			return nil
   102  		}
   103  		return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src)
   104  	}
   105  
   106  	af.Files[dest] = src
   107  	return nil
   108  }
   109  
   110  // AddFile adds a host file at src into the archive at dest.
   111  // It follows symlinks.
   112  //
   113  // If src is a directory, it and its children will be added to the archive
   114  // relative to dest.
   115  //
   116  // Duplicate files with identical content will be silently ignored.
   117  func (af *Files) AddFile(src string, dest string) error {
   118  	return af.addFile(src, dest, true)
   119  }
   120  
   121  // AddFileNoFollow adds a host file at src into the archive at dest.
   122  // It does not follow symlinks.
   123  //
   124  // If src is a directory, it and its children will be added to the archive
   125  // relative to dest.
   126  //
   127  // Duplicate files with identical content will be silently ignored.
   128  func (af *Files) AddFileNoFollow(src string, dest string) error {
   129  	return af.addFile(src, dest, false)
   130  }
   131  
   132  // AddRecord adds a cpio.Record into the archive at `r.Name`.
   133  func (af *Files) AddRecord(r cpio.Record) error {
   134  	r.Name = path.Clean(r.Name)
   135  	if filepath.IsAbs(r.Name) {
   136  		return fmt.Errorf("record name %q must not be absolute", r.Name)
   137  	}
   138  
   139  	if src, ok := af.Files[r.Name]; ok {
   140  		return fmt.Errorf("record for %q already exists in archive: file %q", r.Name, src)
   141  	}
   142  	if rr, ok := af.Records[r.Name]; ok {
   143  		if rr.Info == r.Info {
   144  			return nil
   145  		}
   146  		return fmt.Errorf("record for %q already exists in archive: %v", r.Name, rr)
   147  	}
   148  
   149  	af.Records[r.Name] = r
   150  	return nil
   151  }
   152  
   153  // Contains returns whether path `dest` is already contained in the archive.
   154  func (af *Files) Contains(dest string) bool {
   155  	_, fok := af.Files[dest]
   156  	_, rok := af.Records[dest]
   157  	return fok || rok
   158  }
   159  
   160  // Rename renames a file in the archive.
   161  func (af *Files) Rename(name string, newname string) {
   162  	if src, ok := af.Files[name]; ok {
   163  		delete(af.Files, name)
   164  		af.Files[newname] = src
   165  	}
   166  	if record, ok := af.Records[name]; ok {
   167  		delete(af.Records, name)
   168  		record.Name = newname
   169  		af.Records[newname] = record
   170  	}
   171  }
   172  
   173  // addParent recursively adds parent directory records for `name`.
   174  func (af *Files) addParent(name string) {
   175  	parent := path.Dir(name)
   176  	if parent == "." {
   177  		return
   178  	}
   179  	if !af.Contains(parent) {
   180  		af.AddRecord(cpio.Directory(parent, 0755))
   181  	}
   182  	af.addParent(parent)
   183  }
   184  
   185  // fillInParents adds parent directory records for unparented files in `af`.
   186  func (af *Files) fillInParents() {
   187  	for name := range af.Files {
   188  		af.addParent(name)
   189  	}
   190  	for name := range af.Records {
   191  		af.addParent(name)
   192  	}
   193  }
   194  
   195  // WriteTo writes all records and files in `af` to `w`.
   196  func (af *Files) WriteTo(w Writer) error {
   197  	// Add parent directories when not added specifically.
   198  	af.fillInParents()
   199  	cr := cpio.NewRecorder()
   200  
   201  	// Reproducible builds: Files should be added to the archive in the
   202  	// same order.
   203  	for _, path := range af.sortedKeys() {
   204  		if record, ok := af.Records[path]; ok {
   205  			if err := w.WriteRecord(record); err != nil {
   206  				return err
   207  			}
   208  		}
   209  		if src, ok := af.Files[path]; ok {
   210  			if err := writeFile(w, cr, src, path); err != nil {
   211  				return err
   212  			}
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  // writeFile takes the file at `src` on the host system and adds it to the
   219  // archive `w` at path `dest`.
   220  //
   221  // If `src` is a directory, its children will be added to the archive as well.
   222  func writeFile(w Writer, r *cpio.Recorder, src, dest string) error {
   223  	record, err := r.GetRecord(src)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	// Fix the name.
   229  	record.Name = dest
   230  	return w.WriteRecord(cpio.MakeReproducible(record))
   231  }
   232  
   233  // children calls `fn` on all direct children of directory `dir`.
   234  func children(dir string, fn func(name string) error) error {
   235  	f, err := os.Open(dir)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	names, err := f.Readdirnames(-1)
   240  	f.Close()
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	for _, name := range names {
   246  		if err := fn(name); os.IsNotExist(err) {
   247  			// File was deleted in the meantime.
   248  			continue
   249  		} else if err != nil {
   250  			return err
   251  		}
   252  	}
   253  	return nil
   254  }