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