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