blob: a3c17fa65b4308aad47fd35b7b1a8478924935e5 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"thinfs/block/file"
"thinfs/fs"
"thinfs/fs/msdosfs"
"thinfs/fs/msdosfs/clock"
)
var (
mkfs = flag.String("mkfs", "", "Path to mkfs-msdosfs host tool from Zircon")
target = flag.String("output", "", "Target file/disk to write EFI partition to")
size = flag.Uint64("size", 0, "(optional) Size of partition to create")
offset = flag.Uint64("offset", 0, "(optional) Offset into target to write partition to")
zircon = flag.String("zircon", "", "(optional) Path to source file for zircon.bin")
bootdata = flag.String("bootdata", "", "(optional) Path to source file for bootdata.bin")
efiBootloader = flag.String("efi-bootloader", "", "(optional) Path to source file for EFI/BOOT/BOOTX64.EFI")
manifest = flag.String("manifest", "", "(optional) Path to a manifest file of the form `dst=src\n` to import to partition")
zedboot = flag.String("zedboot", "", "(optional) Path to a source file for zedboot.bin")
cmdline = flag.String("cmdline", "", "(optional) Bootloader cmdline file")
timestamp = flag.Int64("timestamp", -1, "(optional) Unix timestamp in seconds used to "+
"create all files. The actual timestamp used may be different due to FAT32's limit time "+
"granularity")
)
// minSize is the minimum image size, as the tool currently always builds fat32.
const minSize = 63 * 1024 * 1024
// Fudge factor (percentage) to account for filesystem metadata.
const fudgeFactor = 5
type fixedClock struct {
now time.Time
}
func (c *fixedClock) Now() time.Time {
return c.now
}
func main() {
flag.Parse()
if *target == "" || *mkfs == "" {
flag.CommandLine.Usage()
fmt.Printf("\nerror: -target and -mkfs are required\n")
os.Exit(1)
}
var err error
*mkfs, err = filepath.Abs(*mkfs)
if err != nil {
log.Fatal(err)
}
*target, err = filepath.Abs(*target)
if err != nil {
log.Fatal(err)
}
// Slurp up all the copies to do, as it will make the final copy code simpler.
// It can also be used to compute the size if the size was not given.
dstSrc := map[string]string{}
if *zircon != "" {
dstSrc["zircon.bin"] = *zircon
}
if *bootdata != "" {
dstSrc["bootdata.bin"] = *bootdata
}
if *efiBootloader != "" {
dstSrc["EFI/BOOT/BOOTX64.EFI"] = *efiBootloader
tf, err := ioutil.TempFile("", "gsetup-boot")
if err != nil {
log.Fatal(err)
}
tf.WriteString("efi\\boot\\bootx64.efi")
tf.Close()
defer os.Remove(tf.Name())
dstSrc["EFI/Google/GSetup/Boot"] = tf.Name()
}
if *zedboot != "" {
dstSrc["zedboot.bin"] = *zedboot
}
if *cmdline != "" {
dstSrc["cmdline"] = *cmdline
}
if *manifest != "" {
r, err := newManifestReader(*manifest)
if err != nil {
log.Fatal(err)
}
for {
dst, src, err := r.Next()
if dst != "" && src != "" {
dstSrc[dst] = src
}
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
}
r.Close()
}
if *size == 0 {
*size = computeSize(dstSrc)
}
*size = alignSize(*size)
if *timestamp >= 0 {
now := time.Unix(*timestamp, 0)
// FAT32 starts at 1980-01-01 to 2107-12-31, so clamp to that range.
if now.Year() < 1980 {
now = time.Date(1980, 1, 1, 0, 0, 0, 0, time.Local)
} else if now.Year() > 2107 {
now = time.Date(2107, 12, 31, 23, 59, 58, 0, time.Local)
}
// FAT stores modification time at 2s granularity
now = now.Truncate(time.Second * 2)
clock.Now = func() time.Time {
return now
}
}
f, err := os.Create(*target)
if err != nil {
log.Fatal(err)
}
f.Close()
args := []string{*mkfs,
"-@", strconv.FormatUint(*offset, 10),
"-S", strconv.FormatUint(*size, 10),
"-F", "32",
"-L", "ESP",
"-O", "Fuchsia",
"-b", "512",
}
if *timestamp >= 0 {
args = append(args, "-T", strconv.FormatInt(*timestamp, 10))
}
args = append(args, *target)
cmd := exec.Command(args[0], args[1:]...)
output, err := cmd.CombinedOutput()
if err != nil {
os.Stderr.Write(output)
log.Fatalf("mkfs did not succceed, see output above.\n%s\n", strings.Join(args, " "))
}
f, err = os.OpenFile(*target, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
dev, err := file.NewRange(f, 512, int64(*offset), int64(*size))
if err != nil {
log.Fatal(err)
}
fatfs, err := msdosfs.New(*target, dev, fs.ReadWrite|fs.Force)
if err != nil {
log.Fatal(err)
}
// Sort the files we are going to copy to make sure the EFI partition
// is reproducible.
var dsts []string
for dst := range dstSrc {
dsts = append(dsts, dst)
}
sort.Strings(dsts)
root := fatfs.RootDirectory()
for _, dst := range dsts {
msCopyIn(root, dstSrc[dst], dst)
}
root.Sync()
if err := root.Close(); err != nil {
log.Fatal(err)
}
if err := fatfs.Close(); err != nil {
log.Fatal(err)
}
f.Sync()
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
// msCopyIn copies src from the host filesystem into dst under the given
// msdosfs root.
func msCopyIn(root fs.Directory, src, dst string) {
d := root
defer d.Sync()
dStack := []fs.Directory{}
defer func() {
for _, d := range dStack {
d.Sync()
d.Close()
}
}()
destdir := filepath.Dir(dst)
name := filepath.Base(dst)
for _, part := range strings.Split(destdir, "/") {
if part == "." {
continue
}
var err error
_, d, _, err = d.Open(part, fs.OpenFlagRead|fs.OpenFlagCreate|fs.OpenFlagDirectory)
if err != nil {
log.Fatalf("open/create %s: %#v %s", part, err, err)
}
d.Sync()
dStack = append(dStack, d)
}
to, _, _, err := d.Open(name, fs.OpenFlagWrite|fs.OpenFlagCreate|fs.OpenFlagFile)
if err != nil {
log.Fatalf("creating %s in msdosfs: %s", name, err)
}
defer to.Close()
from, err := os.Open(src)
if err != nil {
log.Fatal(err)
}
defer from.Close()
b := make([]byte, 4096)
for err == nil {
var n int
n, err = from.Read(b)
if n > 0 {
if _, err := to.Write(b[:n], 0, fs.WhenceFromCurrent); err != nil {
log.Fatalf("writing %s to msdosfs file: %s", name, err)
}
}
}
to.Sync()
if err != nil && err != io.EOF {
log.Fatal(err)
}
}
func getSize(f string) uint64 {
if f == "" {
return 0
}
fi, err := os.Stat(f)
if err != nil {
log.Fatal(err)
}
return uint64(fi.Size())
}
type manifestReader struct {
*os.File
*bufio.Reader
manifestRoot string
}
func newManifestReader(path string) (*manifestReader, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return &manifestReader{
f,
bufio.NewReader(f),
filepath.Dir(path),
}, nil
}
func (r *manifestReader) Next() (dst, src string, err error) {
for err == nil {
line, err := r.ReadString('\n')
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
log.Printf("make-efi: bad manifest line format: %q, skipping", line)
continue
}
return parts[0], filepath.Join(r.manifestRoot, parts[1]), err
}
return "", "", err
}
func computeSize(dstSrc map[string]string) uint64 {
var total uint64
for _, src := range dstSrc {
total += getSize(src)
}
total += total * 100 / fudgeFactor
pad := total % 63
if pad != 0 {
total += pad
}
if total < minSize {
return minSize
}
return total
}
// sectors per track is 63, and a sector is 512, so we must round to the nearest
// 32256.
const sizeAlignment = 32256
func alignSize(total uint64) uint64 {
if d := total % sizeAlignment; d != 0 {
total = total + (sizeAlignment - d)
}
return total
}