github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/pkg/uroot/files.go (about)

     1  package uroot
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"sort"
    10  
    11  	"github.com/u-root/u-root/pkg/cpio"
    12  	"golang.org/x/sys/unix"
    13  )
    14  
    15  // ArchiveFiles are host files and records to add to the resulting initramfs.
    16  type ArchiveFiles struct {
    17  	// Files is a map of relative archive path -> absolute host file path.
    18  	Files map[string]string
    19  
    20  	// Records is a map of relative archive path -> Record to use.
    21  	//
    22  	// TODO: While the only archive mode is cpio, this will be a
    23  	// cpio.Record. If or when there is another archival mode, we can add a
    24  	// similar uroot.Record type.
    25  	Records map[string]cpio.Record
    26  }
    27  
    28  // NewArchiveFiles returns a new archive files map.
    29  func NewArchiveFiles() ArchiveFiles {
    30  	return ArchiveFiles{
    31  		Files:   make(map[string]string),
    32  		Records: make(map[string]cpio.Record),
    33  	}
    34  }
    35  
    36  // SortedKeys returns a list of sorted paths in the archive.
    37  func (af ArchiveFiles) SortedKeys() []string {
    38  	keys := make([]string, 0, len(af.Files)+len(af.Records))
    39  	for dest := range af.Files {
    40  		keys = append(keys, dest)
    41  	}
    42  	for dest := range af.Records {
    43  		keys = append(keys, dest)
    44  	}
    45  	sort.Sort(sort.StringSlice(keys))
    46  	return keys
    47  }
    48  
    49  // AddFile adds a host file at `src` into the archive at `dest`.
    50  func (af ArchiveFiles) AddFile(src string, dest string) error {
    51  	src = filepath.Clean(src)
    52  	dest = path.Clean(dest)
    53  	if path.IsAbs(dest) {
    54  		return fmt.Errorf("archive path %q must not be absolute (host source %q)", dest, src)
    55  	}
    56  	if !filepath.IsAbs(src) {
    57  		return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest)
    58  	}
    59  
    60  	if _, ok := af.Records[dest]; ok {
    61  		return fmt.Errorf("record for %q already exists in archive", dest)
    62  	}
    63  	if srcFile, ok := af.Files[dest]; ok {
    64  		// Just a duplicate.
    65  		if src == srcFile {
    66  			return nil
    67  		}
    68  		return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src)
    69  	}
    70  
    71  	af.Files[dest] = src
    72  	return nil
    73  }
    74  
    75  // AddRecord adds a cpio.Record into the archive at `r.Name`.
    76  func (af ArchiveFiles) AddRecord(r cpio.Record) error {
    77  	r.Name = path.Clean(r.Name)
    78  	if filepath.IsAbs(r.Name) {
    79  		return fmt.Errorf("record name %q must not be absolute", r.Name)
    80  	}
    81  
    82  	if _, ok := af.Files[r.Name]; ok {
    83  		return fmt.Errorf("record for %q already exists in archive", r.Name)
    84  	}
    85  	if rr, ok := af.Records[r.Name]; ok {
    86  		if rr.Info == r.Info {
    87  			return nil
    88  		}
    89  		return fmt.Errorf("record for %q already exists in archive", r.Name)
    90  	}
    91  
    92  	af.Records[r.Name] = r
    93  	return nil
    94  }
    95  
    96  // Contains returns whether path `dest` is already contained in the archive.
    97  func (af ArchiveFiles) Contains(dest string) bool {
    98  	_, fok := af.Files[dest]
    99  	_, rok := af.Records[dest]
   100  	return fok || rok
   101  }
   102  
   103  // Rename renames a file in the archive.
   104  func (af ArchiveFiles) Rename(name string, newname string) {
   105  	if src, ok := af.Files[name]; ok {
   106  		delete(af.Files, name)
   107  		af.Files[newname] = src
   108  	}
   109  	if record, ok := af.Records[name]; ok {
   110  		delete(af.Records, name)
   111  		record.Name = newname
   112  		af.Records[newname] = record
   113  	}
   114  }
   115  
   116  // addParent recursively adds parent directory records for `name`.
   117  func (af ArchiveFiles) addParent(name string) {
   118  	parent := path.Dir(name)
   119  	if parent == "." {
   120  		return
   121  	}
   122  	if !af.Contains(parent) {
   123  		af.AddRecord(cpio.Directory(parent, 0755))
   124  	}
   125  	af.addParent(parent)
   126  }
   127  
   128  // fillInParents adds parent directory records for unparented files in `af`.
   129  func (af ArchiveFiles) fillInParents() {
   130  	for name := range af.Files {
   131  		af.addParent(name)
   132  	}
   133  	for name := range af.Records {
   134  		af.addParent(name)
   135  	}
   136  }
   137  
   138  // WriteTo writes all records and files in `af` to `w`.
   139  func (af ArchiveFiles) WriteTo(w ArchiveWriter) error {
   140  	// Add parent directories when not added specifically.
   141  	af.fillInParents()
   142  
   143  	// Reproducible builds: Files should be added to the archive in the
   144  	// same order.
   145  	for _, path := range af.SortedKeys() {
   146  		if record, ok := af.Records[path]; ok {
   147  			if err := w.WriteRecord(record); err != nil {
   148  				return err
   149  			}
   150  		}
   151  		if src, ok := af.Files[path]; ok {
   152  			if err := WriteFile(w, src, path); err != nil {
   153  				return err
   154  			}
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  // Write uses the given options to determine which files need to be written
   161  // to the output file using the archive format `a` and writes them.
   162  func (opts *ArchiveOpts) Write() error {
   163  	// Add default records.
   164  	for _, rec := range opts.DefaultRecords {
   165  		// Ignore if it doesn't get added. Probably means the user
   166  		// included something for this file or directory already.
   167  		//
   168  		// TODO: ignore only when it already exists in archive.
   169  		opts.ArchiveFiles.AddRecord(rec)
   170  	}
   171  
   172  	// Write base archive.
   173  	if opts.BaseArchive != nil {
   174  		transform := cpio.MakeReproducible
   175  
   176  		// Rename init to inito if user doesn't want the existing init.
   177  		if !opts.UseExistingInit && opts.Contains("init") {
   178  			transform = func(r cpio.Record) cpio.Record {
   179  				if r.Name == "init" {
   180  					r.Name = "inito"
   181  				}
   182  				return cpio.MakeReproducible(r)
   183  			}
   184  		}
   185  		// If user wants the base archive init, but specified another
   186  		// init, make the other one inito.
   187  		if opts.UseExistingInit && opts.Contains("init") {
   188  			opts.Rename("init", "inito")
   189  		}
   190  
   191  		for {
   192  			f, err := opts.BaseArchive.ReadRecord()
   193  			if err == io.EOF {
   194  				break
   195  			}
   196  			if err != nil {
   197  				return err
   198  			}
   199  			// TODO: ignore only the error where it already exists
   200  			// in archive.
   201  			opts.ArchiveFiles.AddRecord(transform(f))
   202  		}
   203  	}
   204  
   205  	if err := opts.ArchiveFiles.WriteTo(opts.OutputFile); err != nil {
   206  		return err
   207  	}
   208  	return opts.OutputFile.Finish()
   209  }
   210  
   211  // WriteFile takes the file at `src` on the host system and adds it to the
   212  // archive `w` at path `dest`.
   213  //
   214  // If `src` is a directory, its children will be added to the archive as well.
   215  func WriteFile(w ArchiveWriter, src, dest string) error {
   216  	record, err := cpio.GetRecord(src)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	// Fix the name.
   222  	record.Name = dest
   223  	if err := w.WriteRecord(cpio.MakeReproducible(record)); err != nil {
   224  		return err
   225  	}
   226  
   227  	if record.Info.Mode&unix.S_IFMT == unix.S_IFDIR {
   228  		return children(src, func(name string) error {
   229  			return WriteFile(w, filepath.Join(src, name), filepath.Join(dest, name))
   230  		})
   231  	}
   232  	return nil
   233  }
   234  
   235  // children calls `fn` on all direct children of directory `dir`.
   236  func children(dir string, fn func(name string) error) error {
   237  	f, err := os.Open(dir)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	names, err := f.Readdirnames(-1)
   242  	f.Close()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	for _, name := range names {
   248  		if err := fn(name); os.IsNotExist(err) {
   249  			// File was deleted in the meantime.
   250  			continue
   251  		} else if err != nil {
   252  			return err
   253  		}
   254  	}
   255  	return nil
   256  }