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 }