blob: f41d643b9e7d838d789916ce5005c398ddea85d8 [file] [log] [blame]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
// mkimage is a tool to generate a Metropolis node disk image containing the
// given EFI payload, and optionally, a given external initramfs image and
// node parameters
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
diskfs "github.com/diskfs/go-diskfs"
"github.com/diskfs/go-diskfs/disk"
"github.com/diskfs/go-diskfs/filesystem"
"github.com/diskfs/go-diskfs/partition/gpt"
)
var NodeDataPartition gpt.Type = gpt.Type("9eeec464-6885-414a-b278-4305c51f7966")
var NodeSystemPartition gpt.Type = gpt.Type("ee96055b-f6d0-4267-8bbb-724b2afea74c")
var (
flagEFI string
flagOut string
flagSystemPath string
flagNodeParameters string
flagDataPartitionSize uint64
flagESPPartitionSize uint64
flagSystemPartitionSize uint64
)
func mibToSectors(size uint64) uint64 {
return (size * 1024 * 1024) / 512
}
type devZeroReader struct{}
func (_ devZeroReader) Read(b []byte) (n int, err error) {
for i := range b {
b[i] = 0
}
return len(b), nil
}
// devZero is a /dev/zero-like reader which reads an infinite number of zeroes
var devZero = devZeroReader{}
func main() {
flag.StringVar(&flagEFI, "efi", "", "UEFI payload")
flag.StringVar(&flagOut, "out", "", "Output disk image")
flag.StringVar(&flagSystemPath, "system", "", "System partition [optional]")
flag.StringVar(&flagNodeParameters, "node_parameters", "", "Node parameters [optional]")
flag.Uint64Var(&flagDataPartitionSize, "data_partition_size", 2048, "Override the data partition size (default 2048 MiB)")
flag.Uint64Var(&flagESPPartitionSize, "esp_partition_size", 128, "Override the ESP partition size (default: 128MiB)")
flag.Uint64Var(&flagSystemPartitionSize, "system_partition_size", 1024, "Override the System partition size (default: 1024MiB)")
flag.Parse()
if flagEFI == "" || flagOut == "" {
log.Fatalf("efi and initramfs must be set")
}
_ = os.Remove(flagOut)
diskImg, err := diskfs.Create(flagOut, 4*1024*1024*1024, diskfs.Raw)
if err != nil {
log.Fatalf("diskfs.Create(%q): %v", flagOut, err)
}
table := &gpt.Table{
// This is appropriate at least for virtio disks. Might need to be
// adjusted for real ones.
LogicalSectorSize: 512,
PhysicalSectorSize: 512,
ProtectiveMBR: true,
Partitions: []*gpt.Partition{
{
Type: gpt.EFISystemPartition,
Name: "ESP",
Start: mibToSectors(1),
End: mibToSectors(flagESPPartitionSize) - 1,
},
{
Type: NodeSystemPartition,
Name: "METROPOLIS-SYSTEM",
Start: mibToSectors(flagESPPartitionSize),
End: mibToSectors(flagESPPartitionSize+flagSystemPartitionSize) - 1,
},
{
Type: NodeDataPartition,
Name: "METROPOLIS-NODE-DATA",
Start: mibToSectors(flagESPPartitionSize + flagSystemPartitionSize),
End: mibToSectors(flagESPPartitionSize+flagSystemPartitionSize+flagDataPartitionSize) - 1,
},
},
}
if err := diskImg.Partition(table); err != nil {
log.Fatalf("Failed to apply partition table: %v", err)
}
if flagSystemPath != "" {
systemPart, err := os.Open(flagSystemPath)
if err != nil {
log.Fatalf("Failed to open system partition: %v", err)
}
defer systemPart.Close()
systemPartMeta, err := systemPart.Stat()
if err != nil {
log.Fatalf("Failed to stat system partition: %v", err)
}
padding := int64(flagSystemPartitionSize*1024*1024) - systemPartMeta.Size()
systemPartMulti := io.MultiReader(systemPart, io.LimitReader(devZero, padding))
if _, err := diskImg.WritePartitionContents(2, systemPartMulti); err != nil {
log.Fatalf("Failed to write system partition: %v", err)
}
}
fs, err := diskImg.CreateFilesystem(disk.FilesystemSpec{Partition: 1, FSType: filesystem.TypeFat32, VolumeLabel: "ESP"})
if err != nil {
log.Fatalf("Failed to create filesystem: %v", err)
}
// Create EFI partition structure.
for _, dir := range []string{"/EFI", "/EFI/BOOT", "/EFI/metropolis"} {
if err := fs.Mkdir(dir); err != nil {
log.Fatalf("Mkdir(%q): %v", dir, err)
}
}
put(fs, flagEFI, "/EFI/BOOT/BOOTX64.EFI")
if flagNodeParameters != "" {
put(fs, flagNodeParameters, "/EFI/metropolis/parameters.pb")
}
if err := diskImg.File.Close(); err != nil {
log.Fatalf("Failed to finalize image: %v", err)
}
log.Printf("Success! You can now boot %v", flagOut)
}
// put copies a file from the host filesystem into the target image.
func put(fs filesystem.FileSystem, src, dst string) {
target, err := fs.OpenFile(dst, os.O_CREATE|os.O_RDWR)
if err != nil {
log.Fatalf("fs.OpenFile(%q): %v", dst, err)
}
source, err := os.Open(src)
if err != nil {
log.Fatalf("os.Open(%q): %v", src, err)
}
defer source.Close()
// If this is streamed (e.g. using io.Copy) it exposes a bug in diskfs, so
// do it in one go.
data, err := ioutil.ReadAll(source)
if err != nil {
log.Fatalf("Reading %q: %v", src, err)
}
if _, err := target.Write(data); err != nil {
fmt.Printf("writing file %q: %v", dst, err)
os.Exit(1)
}
}