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