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