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