| // 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) |
| } |
| } |