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