github.com/jlowellwofford/u-root@v1.0.0/pkg/uroot/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 uroot 6 7 import ( 8 "fmt" 9 "io" 10 "os" 11 "path" 12 "path/filepath" 13 "sort" 14 15 "github.com/u-root/u-root/pkg/cpio" 16 "golang.org/x/sys/unix" 17 ) 18 19 // ArchiveFiles are host files and records to add to the resulting initramfs. 20 type ArchiveFiles struct { 21 // Files is a map of relative archive path -> absolute host file path. 22 Files map[string]string 23 24 // Records is a map of relative archive path -> Record to use. 25 // 26 // TODO: While the only archive mode is cpio, this will be a 27 // cpio.Record. If or when there is another archival mode, we can add a 28 // similar uroot.Record type. 29 Records map[string]cpio.Record 30 } 31 32 // NewArchiveFiles returns a new archive files map. 33 func NewArchiveFiles() ArchiveFiles { 34 return ArchiveFiles{ 35 Files: make(map[string]string), 36 Records: make(map[string]cpio.Record), 37 } 38 } 39 40 // SortedKeys returns a list of sorted paths in the archive. 41 func (af ArchiveFiles) SortedKeys() []string { 42 keys := make([]string, 0, len(af.Files)+len(af.Records)) 43 for dest := range af.Files { 44 keys = append(keys, dest) 45 } 46 for dest := range af.Records { 47 keys = append(keys, dest) 48 } 49 sort.Sort(sort.StringSlice(keys)) 50 return keys 51 } 52 53 // AddFile adds a host file at `src` into the archive at `dest`. 54 func (af ArchiveFiles) AddFile(src string, dest string) error { 55 src = filepath.Clean(src) 56 dest = path.Clean(dest) 57 if path.IsAbs(dest) { 58 return fmt.Errorf("archive path %q must not be absolute (host source %q)", dest, src) 59 } 60 if !filepath.IsAbs(src) { 61 return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest) 62 } 63 64 if _, ok := af.Records[dest]; ok { 65 return fmt.Errorf("record for %q already exists in archive", dest) 66 } 67 if srcFile, ok := af.Files[dest]; ok { 68 // Just a duplicate. 69 if src == srcFile { 70 return nil 71 } 72 return fmt.Errorf("record for %q already exists in archive (is %q)", dest, src) 73 } 74 75 af.Files[dest] = src 76 return nil 77 } 78 79 // AddRecord adds a cpio.Record into the archive at `r.Name`. 80 func (af ArchiveFiles) AddRecord(r cpio.Record) error { 81 r.Name = path.Clean(r.Name) 82 if filepath.IsAbs(r.Name) { 83 return fmt.Errorf("record name %q must not be absolute", r.Name) 84 } 85 86 if _, ok := af.Files[r.Name]; ok { 87 return fmt.Errorf("record for %q already exists in archive", r.Name) 88 } 89 if rr, ok := af.Records[r.Name]; ok { 90 if rr.Info == r.Info { 91 return nil 92 } 93 return fmt.Errorf("record for %q already exists in archive", r.Name) 94 } 95 96 af.Records[r.Name] = r 97 return nil 98 } 99 100 // Contains returns whether path `dest` is already contained in the archive. 101 func (af ArchiveFiles) Contains(dest string) bool { 102 _, fok := af.Files[dest] 103 _, rok := af.Records[dest] 104 return fok || rok 105 } 106 107 // Rename renames a file in the archive. 108 func (af ArchiveFiles) Rename(name string, newname string) { 109 if src, ok := af.Files[name]; ok { 110 delete(af.Files, name) 111 af.Files[newname] = src 112 } 113 if record, ok := af.Records[name]; ok { 114 delete(af.Records, name) 115 record.Name = newname 116 af.Records[newname] = record 117 } 118 } 119 120 // addParent recursively adds parent directory records for `name`. 121 func (af ArchiveFiles) addParent(name string) { 122 parent := path.Dir(name) 123 if parent == "." { 124 return 125 } 126 if !af.Contains(parent) { 127 af.AddRecord(cpio.Directory(parent, 0755)) 128 } 129 af.addParent(parent) 130 } 131 132 // fillInParents adds parent directory records for unparented files in `af`. 133 func (af ArchiveFiles) fillInParents() { 134 for name := range af.Files { 135 af.addParent(name) 136 } 137 for name := range af.Records { 138 af.addParent(name) 139 } 140 } 141 142 // WriteTo writes all records and files in `af` to `w`. 143 func (af ArchiveFiles) WriteTo(w ArchiveWriter) error { 144 // Add parent directories when not added specifically. 145 af.fillInParents() 146 147 // Reproducible builds: Files should be added to the archive in the 148 // same order. 149 for _, path := range af.SortedKeys() { 150 if record, ok := af.Records[path]; ok { 151 if err := w.WriteRecord(record); err != nil { 152 return err 153 } 154 } 155 if src, ok := af.Files[path]; ok { 156 if err := WriteFile(w, src, path); err != nil { 157 return err 158 } 159 } 160 } 161 return nil 162 } 163 164 // Write uses the given options to determine which files need to be written 165 // to the output file using the archive format `a` and writes them. 166 func (opts *ArchiveOpts) Write() error { 167 // Add default records. 168 for _, rec := range opts.DefaultRecords { 169 // Ignore if it doesn't get added. Probably means the user 170 // included something for this file or directory already. 171 // 172 // TODO: ignore only when it already exists in archive. 173 opts.ArchiveFiles.AddRecord(rec) 174 } 175 176 // Write base archive. 177 if opts.BaseArchive != nil { 178 transform := cpio.MakeReproducible 179 180 // Rename init to inito if user doesn't want the existing init. 181 if !opts.UseExistingInit && opts.Contains("init") { 182 transform = func(r cpio.Record) cpio.Record { 183 if r.Name == "init" { 184 r.Name = "inito" 185 } 186 return cpio.MakeReproducible(r) 187 } 188 } 189 // If user wants the base archive init, but specified another 190 // init, make the other one inito. 191 if opts.UseExistingInit && opts.Contains("init") { 192 opts.Rename("init", "inito") 193 } 194 195 for { 196 f, err := opts.BaseArchive.ReadRecord() 197 if err == io.EOF { 198 break 199 } 200 if err != nil { 201 return err 202 } 203 // TODO: ignore only the error where it already exists 204 // in archive. 205 opts.ArchiveFiles.AddRecord(transform(f)) 206 } 207 } 208 209 if err := opts.ArchiveFiles.WriteTo(opts.OutputFile); err != nil { 210 return err 211 } 212 return opts.OutputFile.Finish() 213 } 214 215 // WriteFile takes the file at `src` on the host system and adds it to the 216 // archive `w` at path `dest`. 217 // 218 // If `src` is a directory, its children will be added to the archive as well. 219 func WriteFile(w ArchiveWriter, src, dest string) error { 220 record, err := cpio.GetRecord(src) 221 if err != nil { 222 return err 223 } 224 225 // Fix the name. 226 record.Name = dest 227 if err := w.WriteRecord(cpio.MakeReproducible(record)); err != nil { 228 return err 229 } 230 231 if record.Info.Mode&unix.S_IFMT == unix.S_IFDIR { 232 return children(src, func(name string) error { 233 return WriteFile(w, filepath.Join(src, name), filepath.Join(dest, name)) 234 }) 235 } 236 return nil 237 } 238 239 // children calls `fn` on all direct children of directory `dir`. 240 func children(dir string, fn func(name string) error) error { 241 f, err := os.Open(dir) 242 if err != nil { 243 return err 244 } 245 names, err := f.Readdirnames(-1) 246 f.Close() 247 if err != nil { 248 return err 249 } 250 251 for _, name := range names { 252 if err := fn(name); os.IsNotExist(err) { 253 // File was deleted in the meantime. 254 continue 255 } else if err != nil { 256 return err 257 } 258 } 259 return nil 260 }