gopkg.in/hugelgupf/u-root.v2@v2.0.0-20180831055005-3f8fdb0ce09d/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 "golang.org/x/sys/unix" 16 ) 17 18 // Files are host files and cpio records to add to an initramfs. 19 // 20 // The initramfs can be written out using Files.WriteTo. 21 type Files struct { 22 // Files is a map of relative archive path -> absolute host file path. 23 Files map[string]string 24 25 // Records is a map of relative archive path -> Record to use. 26 // 27 // TODO: While the only archive mode is cpio, this will be a 28 // cpio.Record. If or when there is another archival mode, we can add a 29 // similar uroot.Record type. 30 Records map[string]cpio.Record 31 } 32 33 // NewFiles returns a new archive files map. 34 func NewFiles() Files { 35 return Files{ 36 Files: make(map[string]string), 37 Records: make(map[string]cpio.Record), 38 } 39 } 40 41 // SortedKeys returns a list of sorted paths in the archive. 42 func (af Files) SortedKeys() []string { 43 keys := make([]string, 0, len(af.Files)+len(af.Records)) 44 for dest := range af.Files { 45 keys = append(keys, dest) 46 } 47 for dest := range af.Records { 48 keys = append(keys, dest) 49 } 50 sort.Sort(sort.StringSlice(keys)) 51 return keys 52 } 53 54 // AddFile adds a host file at `src` into the archive at `dest`. 55 func (af Files) AddFile(src string, dest string) error { 56 src = filepath.Clean(src) 57 dest = path.Clean(dest) 58 if path.IsAbs(dest) { 59 return fmt.Errorf("archive path %q must not be absolute (host source %q)", dest, src) 60 } 61 if !filepath.IsAbs(src) { 62 return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest) 63 } 64 65 if _, ok := af.Records[dest]; ok { 66 return fmt.Errorf("record for %q already exists in archive", dest) 67 } 68 if srcFile, ok := af.Files[dest]; ok { 69 // Just a duplicate. 70 if src == srcFile { 71 return nil 72 } 73 return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src) 74 } 75 76 af.Files[dest] = src 77 return nil 78 } 79 80 // AddRecord adds a cpio.Record into the archive at `r.Name`. 81 func (af Files) AddRecord(r cpio.Record) error { 82 r.Name = path.Clean(r.Name) 83 if filepath.IsAbs(r.Name) { 84 return fmt.Errorf("record name %q must not be absolute", r.Name) 85 } 86 87 if _, ok := af.Files[r.Name]; ok { 88 return fmt.Errorf("record for %q already exists in archive", r.Name) 89 } 90 if rr, ok := af.Records[r.Name]; ok { 91 if rr.Info == r.Info { 92 return nil 93 } 94 return fmt.Errorf("record for %q already exists in archive", r.Name) 95 } 96 97 af.Records[r.Name] = r 98 return nil 99 } 100 101 // Contains returns whether path `dest` is already contained in the archive. 102 func (af Files) Contains(dest string) bool { 103 _, fok := af.Files[dest] 104 _, rok := af.Records[dest] 105 return fok || rok 106 } 107 108 // Rename renames a file in the archive. 109 func (af Files) Rename(name string, newname string) { 110 if src, ok := af.Files[name]; ok { 111 delete(af.Files, name) 112 af.Files[newname] = src 113 } 114 if record, ok := af.Records[name]; ok { 115 delete(af.Records, name) 116 record.Name = newname 117 af.Records[newname] = record 118 } 119 } 120 121 // addParent recursively adds parent directory records for `name`. 122 func (af Files) addParent(name string) { 123 parent := path.Dir(name) 124 if parent == "." { 125 return 126 } 127 if !af.Contains(parent) { 128 af.AddRecord(cpio.Directory(parent, 0755)) 129 } 130 af.addParent(parent) 131 } 132 133 // fillInParents adds parent directory records for unparented files in `af`. 134 func (af Files) fillInParents() { 135 for name := range af.Files { 136 af.addParent(name) 137 } 138 for name := range af.Records { 139 af.addParent(name) 140 } 141 } 142 143 // WriteTo writes all records and files in `af` to `w`. 144 func (af Files) WriteTo(w Writer) error { 145 // Add parent directories when not added specifically. 146 af.fillInParents() 147 148 // Reproducible builds: Files should be added to the archive in the 149 // same order. 150 for _, path := range af.SortedKeys() { 151 if record, ok := af.Records[path]; ok { 152 if err := w.WriteRecord(record); err != nil { 153 return err 154 } 155 } 156 if src, ok := af.Files[path]; ok { 157 if err := WriteFile(w, src, path); err != nil { 158 return err 159 } 160 } 161 } 162 return nil 163 } 164 165 // WriteFile takes the file at `src` on the host system and adds it to the 166 // archive `w` at path `dest`. 167 // 168 // If `src` is a directory, its children will be added to the archive as well. 169 func WriteFile(w Writer, src, dest string) error { 170 record, err := cpio.GetRecord(src) 171 if err != nil { 172 return err 173 } 174 175 // Fix the name. 176 record.Name = dest 177 if err := w.WriteRecord(cpio.MakeReproducible(record)); err != nil { 178 return err 179 } 180 181 if record.Info.Mode&unix.S_IFMT == unix.S_IFDIR { 182 return children(src, func(name string) error { 183 return WriteFile(w, filepath.Join(src, name), filepath.Join(dest, name)) 184 }) 185 } 186 return nil 187 } 188 189 // children calls `fn` on all direct children of directory `dir`. 190 func children(dir string, fn func(name string) error) error { 191 f, err := os.Open(dir) 192 if err != nil { 193 return err 194 } 195 names, err := f.Readdirnames(-1) 196 f.Close() 197 if err != nil { 198 return err 199 } 200 201 for _, name := range names { 202 if err := fn(name); os.IsNotExist(err) { 203 // File was deleted in the meantime. 204 continue 205 } else if err != nil { 206 return err 207 } 208 } 209 return nil 210 }