github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/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" 10 "io/ioutil" 11 "log" 12 "os" 13 "path/filepath" 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 // These constants are used in DefaultRamfs. 22 const ( 23 // This is the literal timezone file for GMT-0. Given that we have no 24 // idea where we will be running, GMT seems a reasonable guess. If it 25 // matters, setup code should download and change this to something 26 // else. 27 gmt0 = "TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00GMT\x00\x00\x00TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00GMT\x00\x00\x00\nGMT0\n" 28 29 nameserver = "nameserver 8.8.8.8\n" 30 ) 31 32 var ( 33 builders = map[string]Build{ 34 "source": SourceBuild, 35 "bb": BBBuild, 36 "binary": BinaryBuild, 37 } 38 archivers = map[string]Archiver{ 39 "cpio": CPIOArchiver{ 40 Format: "newc", 41 }, 42 "dir": DirArchiver{}, 43 } 44 ) 45 46 // DefaultRamfs are files that are contained in all u-root initramfs archives 47 // by default. 48 var DefaultRamfs = []cpio.Record{ 49 cpio.Directory("tcz", 0755), 50 cpio.Directory("etc", 0755), 51 cpio.Directory("dev", 0755), 52 cpio.Directory("ubin", 0755), 53 cpio.Directory("usr", 0755), 54 cpio.Directory("usr/lib", 0755), 55 cpio.Directory("lib64", 0755), 56 cpio.Directory("bin", 0755), 57 cpio.CharDev("dev/console", 0600, 5, 1), 58 cpio.CharDev("dev/tty", 0666, 5, 0), 59 cpio.CharDev("dev/null", 0666, 1, 3), 60 cpio.CharDev("dev/port", 0640, 1, 4), 61 cpio.CharDev("dev/urandom", 0666, 1, 9), 62 cpio.StaticFile("etc/resolv.conf", nameserver, 0644), 63 cpio.StaticFile("etc/localtime", gmt0, 0644), 64 } 65 66 // Opts are the arguments to CreateInitramfs. 67 type Opts struct { 68 // Env is the build environment (OS, arch, etc). 69 Env golang.Environ 70 71 // Builder is the build format. 72 // 73 // This can currently be "source" or "bb". 74 Builder Build 75 76 // Archiver is the initramfs archival format. 77 // 78 // Only "cpio" is currently supported. 79 Archiver Archiver 80 81 // Packages are the Go packages to add to the archive. 82 // 83 // Currently allowed formats: 84 // Go package imports; e.g. github.com/u-root/u-root/cmds/ls 85 // Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/ls 86 // Globs of paths to Go package directories; e.g. ./cmds/* 87 Packages []string 88 89 // ExtraFiles are files to add to the archive in addition to the Go 90 // packages. 91 // 92 // Shared library dependencies will automatically also be added to the 93 // archive using ldd. 94 ExtraFiles []string 95 96 // TempDir is a temporary directory for the builder to store files in. 97 TempDir string 98 99 // OutputFile is the archive output file. 100 OutputFile ArchiveWriter 101 102 // BaseArchive is an existing initramfs to include in the resulting 103 // initramfs. 104 BaseArchive ArchiveReader 105 106 // UseExistingInit determines whether the existing init from 107 // BaseArchive should be used. 108 // 109 // If this is false, the "init" from BaseArchive will be renamed to 110 // "inito". 111 UseExistingInit bool 112 } 113 114 // CreateInitramfs creates an initramfs built to `opts`' specifications. 115 func CreateInitramfs(opts Opts) error { 116 if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) { 117 return fmt.Errorf("temp dir %q must exist: %v", opts.TempDir, err) 118 } 119 if opts.OutputFile == nil { 120 return fmt.Errorf("must give output file") 121 } 122 123 var importPaths []string 124 // Resolve file system paths to package import paths. 125 for _, pkg := range opts.Packages { 126 matches, err := filepath.Glob(pkg) 127 if len(matches) == 0 || err != nil { 128 if _, perr := opts.Env.Package(pkg); perr != nil { 129 return fmt.Errorf("%q is neither package or path/glob: %v / %v", pkg, err, perr) 130 } 131 importPaths = append(importPaths, pkg) 132 } 133 134 for _, match := range matches { 135 p, err := opts.Env.PackageByPath(match) 136 if err != nil { 137 log.Printf("Skipping package %q: %v", match, err) 138 } else { 139 importPaths = append(importPaths, p.ImportPath) 140 } 141 } 142 } 143 144 builderTmpDir, err := ioutil.TempDir(opts.TempDir, "builder") 145 if err != nil { 146 return err 147 } 148 149 // Build the packages. 150 bOpts := BuildOpts{ 151 Env: opts.Env, 152 Packages: importPaths, 153 TempDir: builderTmpDir, 154 } 155 files, err := opts.Builder(bOpts) 156 if err != nil { 157 return fmt.Errorf("error building %#v: %v", bOpts, err) 158 } 159 160 // Open the target initramfs file. 161 archive := ArchiveOpts{ 162 ArchiveFiles: files, 163 OutputFile: opts.OutputFile, 164 BaseArchive: opts.BaseArchive, 165 UseExistingInit: opts.UseExistingInit, 166 DefaultRecords: DefaultRamfs, 167 } 168 169 // Add files from command line. 170 for _, file := range opts.ExtraFiles { 171 var src, dst string 172 parts := strings.SplitN(file, ":", 2) 173 if len(parts) == 2 { 174 // treat the entry with the new src:dst syntax 175 src = parts[0] 176 dst = parts[1] 177 } else { 178 // plain old syntax 179 src = file 180 dst = file 181 } 182 src, err := filepath.Abs(src) 183 if err != nil { 184 return fmt.Errorf("couldn't find absolute path for %q: %v", src, err) 185 } 186 if err := archive.AddFile(src, dst); err != nil { 187 return fmt.Errorf("couldn't add %q to archive: %v", file, err) 188 } 189 190 // Pull dependencies in the case of binaries. If `path` is not 191 // a binary, `libs` will just be empty. 192 libs, err := ldd.List([]string{src}) 193 if err != nil { 194 return fmt.Errorf("couldn't list ldd dependencies for %q: %v", file, err) 195 } 196 for _, lib := range libs { 197 if err := archive.AddFile(lib, lib[1:]); err != nil { 198 return fmt.Errorf("couldn't add %q to archive: %v", lib, err) 199 } 200 } 201 } 202 203 // Finally, write the archive. 204 if err := archive.Write(); err != nil { 205 return fmt.Errorf("error archiving: %v", err) 206 } 207 return nil 208 } 209 210 // BuildOpts are arguments to the Build function. 211 type BuildOpts struct { 212 // Env is the Go environment to use to compile and link packages. 213 Env golang.Environ 214 215 // Packages are the Go package import paths to compile. 216 // 217 // Builders need not support resolving packages by path. 218 // 219 // E.g. cmd/go or github.com/u-root/u-root/cmds/ls. 220 Packages []string 221 222 // TempDir is a temporary directory where the compilation mode compiled 223 // binaries can be placed. 224 // 225 // TempDir should contain no files. 226 TempDir string 227 } 228 229 // Build uses the given options to build Go packages and returns a list of 230 // files to be included in an initramfs archive. 231 type Build func(BuildOpts) (ArchiveFiles, error) 232 233 // ArchiveOpts are the options for building the initramfs archive. 234 type ArchiveOpts struct { 235 // ArchiveFiles are the files to be included. 236 // 237 // Files in ArchiveFiles generally have priority over files in 238 // DefaultRecords or BaseArchive. 239 ArchiveFiles 240 241 // DefaultRecords is a set of files to be included in the initramfs. 242 DefaultRecords []cpio.Record 243 244 // OutputFile is the file to write to. 245 OutputFile ArchiveWriter 246 247 // BaseArchive is an existing archive to add files to. 248 // 249 // BaseArchive may be nil. 250 BaseArchive ArchiveReader 251 252 // UseExistingInit determines whether the init from BaseArchive is used 253 // or not, if BaseArchive is specified. 254 // 255 // If this is false, the "init" file in BaseArchive will be renamed 256 // "inito" in the output archive. 257 UseExistingInit bool 258 } 259 260 // Archiver is an archive format that builds an archive using a given set of 261 // files. 262 type Archiver interface { 263 // OpenWriter opens an archive writer at `path`. 264 // 265 // If `path` is unspecified, implementations may choose an arbitrary 266 // default location, potentially based on `goos` and `goarch`. 267 OpenWriter(path, goos, goarch string) (ArchiveWriter, error) 268 269 // Reader returns an ArchiveReader wrapper using the given io.Reader. 270 Reader(io.ReaderAt) ArchiveReader 271 } 272 273 // ArchiveWriter is an object that files can be written to. 274 type ArchiveWriter interface { 275 // WriteRecord writes the given file record. 276 WriteRecord(cpio.Record) error 277 278 // Finish finishes the archive. 279 Finish() error 280 } 281 282 // ArchiveReader is an object that files can be read from. 283 type ArchiveReader interface { 284 // ReadRecord reads a file record. 285 ReadRecord() (cpio.Record, error) 286 } 287 288 // GetBuilder returns the Build function for the named build mode. 289 func GetBuilder(name string) (Build, error) { 290 build, ok := builders[name] 291 if !ok { 292 return nil, fmt.Errorf("couldn't find builder %q", name) 293 } 294 return build, nil 295 } 296 297 // GetArchiver returns the archive mode for the named archive. 298 func GetArchiver(name string) (Archiver, error) { 299 archiver, ok := archivers[name] 300 if !ok { 301 return nil, fmt.Errorf("couldn't find archival format %q", name) 302 } 303 return archiver, nil 304 } 305 306 // DefaultPackageImports returns a list of default u-root packages to include. 307 func DefaultPackageImports(env golang.Environ) ([]string, error) { 308 // Find u-root directory. 309 urootPkg, err := env.Package("github.com/u-root/u-root") 310 if err != nil { 311 return nil, fmt.Errorf("Couldn't find u-root src directory: %v", err) 312 } 313 314 matches, err := filepath.Glob(filepath.Join(urootPkg.Dir, "cmds/*")) 315 if err != nil { 316 return nil, fmt.Errorf("couldn't find u-root cmds: %v", err) 317 } 318 pkgs := make([]string, 0, len(matches)) 319 for _, match := range matches { 320 pkg, err := env.PackageByPath(match) 321 if err == nil { 322 pkgs = append(pkgs, pkg.ImportPath) 323 } 324 } 325 return pkgs, nil 326 }