github.com/rck/u-root@v0.0.0-20180106144920-7eb602e381bb/pkg/uroot/uroot.go (about) 1 // Copyright 2015-2017 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/ioutil" 10 "log" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 16 "github.com/u-root/u-root/pkg/cpio" 17 "github.com/u-root/u-root/pkg/golang" 18 "github.com/u-root/u-root/pkg/ldd" 19 ) 20 21 var ( 22 builders = map[string]Build{ 23 "source": SourceBuild, 24 "bb": BBBuild, 25 } 26 archivers = map[string]Archiver{ 27 "cpio": CPIOArchiver{ 28 Format: "newc", 29 }, 30 } 31 ) 32 33 // Opts are the arguments to CreateInitramfs. 34 type Opts struct { 35 // Env is the build environment (OS, arch, etc). 36 Env golang.Environ 37 38 // Builder is the build format. 39 // 40 // This can currently be "source" or "bb". 41 Builder Build 42 43 // Archiver is the initramfs archival format. 44 // 45 // Only "cpio" is currently supported. 46 Archiver Archiver 47 48 // Packages are the Go packages to add to the archive. 49 // 50 // Currently allowed formats: 51 // Go package imports; e.g. github.com/u-root/u-root/cmds/ls 52 // Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 53 // Globs of paths to Go package directories; e.g. ./cmds/* 54 Packages []string 55 56 // ExtraFiles are files to add to the archive in addition to the Go 57 // packages. 58 // 59 // Shared library dependencies will automatically also be added to the 60 // archive using ldd. 61 ExtraFiles []string 62 63 // TempDir is a temporary directory for the builder to store files in. 64 TempDir string 65 66 // OutputFile is the archive output file. 67 OutputFile *os.File 68 69 // BaseArchive is an existing initramfs to include in the resulting 70 // initramfs. 71 BaseArchive *os.File 72 73 // UseExistingInit determines whether the existing init from 74 // BaseArchive should be used. 75 // 76 // If this is false, the "init" from BaseArchive will be renamed to 77 // "inito". 78 UseExistingInit bool 79 } 80 81 // CreateInitramfs creates an initramfs built to `opts`' specifications. 82 func CreateInitramfs(opts Opts) error { 83 if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) { 84 return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err) 85 } 86 if opts.OutputFile == nil { 87 return fmt.Errorf("must give output file") 88 } 89 90 var importPaths []string 91 // Resolve file system paths to package import paths. 92 for _, pkg := range opts.Packages { 93 matches, err := filepath.Glob(pkg) 94 if len(matches) == 0 || err != nil { 95 if _, perr := opts.Env.Package(pkg); perr != nil { 96 return fmt.Errorf("%q is neither package or path/glob: %v / %v", pkg, err, perr) 97 } 98 importPaths = append(importPaths, pkg) 99 } 100 101 for _, match := range matches { 102 p, err := opts.Env.PackageByPath(match) 103 if err != nil { 104 log.Printf("Skipping package %q: %v", match, err) 105 } else { 106 importPaths = append(importPaths, p.ImportPath) 107 } 108 } 109 } 110 111 builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder") 112 if err != nil { 113 return err 114 } 115 116 // Build the packages. 117 bOpts := BuildOpts{ 118 Env: opts.Env, 119 Packages: importPaths, 120 TempDir: builderTmpDir, 121 } 122 files, err := opts.Builder(bOpts) 123 if err != nil { 124 return fmt.Errorf("error building %#v: %v", bOpts, err) 125 } 126 127 archiveTmpDir, err := ioutil.TempDir(opts.TempDir, "archive") 128 if err != nil { 129 return err 130 } 131 132 // Open the target initramfs file. 133 archive := ArchiveOpts{ 134 ArchiveFiles: files, 135 OutputFile: opts.OutputFile, 136 BaseArchive: opts.BaseArchive, 137 UseExistingInit: opts.UseExistingInit, 138 TempDir: archiveTmpDir, 139 } 140 141 // Add files from command line. 142 for _, file := range opts.ExtraFiles { 143 var src, dst string 144 parts := strings.SplitN(file, ":", 2) 145 if len(parts) == 2 { 146 // treat the entry with the new src:dst syntax 147 src = parts[0] 148 dst = parts[1] 149 } else { 150 // plain old syntax 151 src = file 152 dst = file 153 } 154 src, err := filepath.Abs(src) 155 if err != nil { 156 return fmt.Errorf("couldn't find absolute path for %q: %v", src, err) 157 } 158 if err := archive.AddFile(src, dst); err != nil { 159 return fmt.Errorf("couldn't add %q to archive: %v", file, err) 160 } 161 162 // Pull dependencies in the case of binaries. If `path` is not 163 // a binary, `libs` will just be empty. 164 libs, err := ldd.List([]string{src}) 165 if err != nil { 166 return fmt.Errorf("couldn't list ldd dependencies for %q: %v", file, err) 167 } 168 for _, lib := range libs { 169 if err := archive.AddFile(lib, lib[1:]); err != nil { 170 return fmt.Errorf("couldn't add %q to archive: %v", lib, err) 171 } 172 } 173 } 174 175 // Finally, write the archive. 176 if err := opts.Archiver.Archive(archive); err != nil { 177 return fmt.Errorf("error archiving: %v", err) 178 } 179 return nil 180 } 181 182 // BuildOpts are arguments to the Build function. 183 type BuildOpts struct { 184 // Env is the Go environment to use to compile and link packages. 185 Env golang.Environ 186 187 // Packages are the Go package import paths to compile. 188 // 189 // Builders need not support resolving packages by path. 190 // 191 // E.g. cmd/go or github.com/u-root/u-root/cmds/ls. 192 Packages []string 193 194 // TempDir is a temporary directory where the compilation mode compiled 195 // binaries can be placed. 196 // 197 // TempDir should contain no files. 198 TempDir string 199 } 200 201 // Build uses the given options to build Go packages and returns a list of 202 // files to be included in an initramfs archive. 203 type Build func(BuildOpts) (ArchiveFiles, error) 204 205 // ArchiveOpts are the options for building the initramfs archive. 206 type ArchiveOpts struct { 207 // ArchiveFiles are the files to be included. 208 ArchiveFiles 209 210 // TempDir is a temporary directory that can be used at the archiver's 211 // discretion. 212 // 213 // TempDir should contain no files. 214 TempDir string 215 216 // OutputFile is the file to write to. 217 OutputFile *os.File 218 219 // BaseArchive is an existing archive to add files to. 220 // 221 // BaseArchive may be nil. 222 BaseArchive *os.File 223 224 // UseExistingInit determines whether the init from BaseArchive is used 225 // or not, if BaseArchive is specified. 226 // 227 // If this is false, the "init" file in BaseArchive will be renamed 228 // "inito" in the output archive. 229 UseExistingInit bool 230 } 231 232 // Archiver is an archive format that builds an archive using a given set of 233 // files. 234 type Archiver interface { 235 // Archive builds an archive file. 236 Archive(ArchiveOpts) error 237 238 // DefaultExtension is the default file extension of the archive format. 239 DefaultExtension() string 240 } 241 242 // GetBuilder returns the Build function for the named build mode. 243 func GetBuilder(name string) (Build, error) { 244 build, ok := builders[name] 245 if !ok { 246 return nil, fmt.Errorf("couldn't find builder %q", name) 247 } 248 return build, nil 249 } 250 251 // GetArchiver returns the archive mode for the named archive. 252 func GetArchiver(name string) (Archiver, error) { 253 archiver, ok := archivers[name] 254 if !ok { 255 return nil, fmt.Errorf("couldn't find archival format %q", name) 256 } 257 return archiver, nil 258 } 259 260 // ArchiveFiles are host files and records to add to 261 type ArchiveFiles struct { 262 // Files is a map of relative archive path -> absolute host file path. 263 Files map[string]string 264 265 // Records is a map of relative archive path -> Record to use. 266 // 267 // TODO: While the only archive mode is cpio, this will be a 268 // cpio.Record. If or when there is another archival mode, we can add a 269 // similar uroot.Record type. 270 Records map[string]cpio.Record 271 } 272 273 // NewArchiveFiles returns a new archive files map. 274 func NewArchiveFiles() ArchiveFiles { 275 return ArchiveFiles{ 276 Files: make(map[string]string), 277 Records: make(map[string]cpio.Record), 278 } 279 } 280 281 // SortedKeys returns a list of sorted paths in the archive. 282 func (af ArchiveFiles) SortedKeys() []string { 283 keys := make([]string, 0, len(af.Files)+len(af.Records)) 284 for dest := range af.Files { 285 keys = append(keys, dest) 286 } 287 for dest := range af.Records { 288 keys = append(keys, dest) 289 } 290 sort.Sort(sort.StringSlice(keys)) 291 return keys 292 } 293 294 // AddFile adds a host file at `src` into the archive at `dest`. 295 func (af ArchiveFiles) AddFile(src string, dest string) error { 296 if filepath.IsAbs(dest) { 297 return fmt.Errorf("cannot add absolute path %q (from %q) to archive", dest, src) 298 } 299 if !filepath.IsAbs(src) { 300 return fmt.Errorf("source file %q (-> %q) must be absolute", src, dest) 301 } 302 303 if _, ok := af.Records[dest]; ok { 304 return fmt.Errorf("record for %q already exists in archive", dest) 305 } 306 if srcFile, ok := af.Files[dest]; ok { 307 // Just a duplicate. 308 if src == srcFile { 309 return nil 310 } 311 return fmt.Errorf("archive file %q already comes from %q", dest, src) 312 } 313 314 af.Files[dest] = src 315 return nil 316 } 317 318 // AddRecord adds a cpio.Record into the archive at `r.Name`. 319 func (af ArchiveFiles) AddRecord(r cpio.Record) error { 320 if filepath.IsAbs(r.Name) { 321 return fmt.Errorf("cannot add absolute path %q to archive", r.Name) 322 } 323 324 if _, ok := af.Files[r.Name]; ok { 325 return fmt.Errorf("record for %q already exists in archive", r.Name) 326 } 327 if rr, ok := af.Records[r.Name]; ok { 328 if rr.Info == r.Info { 329 return nil 330 } 331 return fmt.Errorf("record for %q already exists", r.Name) 332 } 333 334 af.Records[r.Name] = r 335 return nil 336 } 337 338 // Contains returns whether path `dest` is already contained in the archive. 339 func (af ArchiveFiles) Contains(dest string) bool { 340 _, fok := af.Files[dest] 341 _, rok := af.Records[dest] 342 return fok || rok 343 } 344 345 // DefaultPackageImports returns a list of default u-root packages to include. 346 func DefaultPackageImports(env golang.Environ) ([]string, error) { 347 // Find u-root directory. 348 urootPkg, err := env.Package("github.com/u-root/u-root") 349 if err != nil { 350 return nil, fmt.Errorf("Couldn't find u-root src directory: %v", err) 351 } 352 353 matches, err := filepath.Glob(filepath.Join(urootPkg.Dir, "cmds/*")) 354 if err != nil { 355 return nil, fmt.Errorf("couldn't find u-root cmds: %v", err) 356 } 357 pkgs := make([]string, 0, len(matches)) 358 for _, match := range matches { 359 pkg, err := env.PackageByPath(match) 360 if err == nil { 361 pkgs = append(pkgs, pkg.ImportPath) 362 } 363 } 364 return pkgs, nil 365 }