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