blob: f41d643b9e7d838d789916ce5005c398ddea85d8 [file] [log] [blame]
Lorenz Brunae0d90d2019-09-05 17:53:56 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package main
18
Serge Bazanski662b5b32020-12-21 13:49:00 +010019// mkimage is a tool to generate a Metropolis node disk image containing the
20// given EFI payload, and optionally, a given external initramfs image and
Serge Bazanski0ed2f962021-03-15 16:39:30 +010021// node parameters
Serge Bazanski032ca182020-06-09 20:17:13 +020022
Lorenz Brunae0d90d2019-09-05 17:53:56 +020023import (
Leopold Schabel65493072019-11-06 13:40:44 +000024 "flag"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020025 "fmt"
Lorenz Brun3a99c592021-01-26 19:57:21 +010026 "io"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020027 "io/ioutil"
Serge Bazanski032ca182020-06-09 20:17:13 +020028 "log"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020029 "os"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020030
Hendrik Hofstadt8efe51e2020-02-28 12:53:41 +010031 diskfs "github.com/diskfs/go-diskfs"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020032 "github.com/diskfs/go-diskfs/disk"
33 "github.com/diskfs/go-diskfs/filesystem"
34 "github.com/diskfs/go-diskfs/partition/gpt"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020035)
36
Serge Bazanski662b5b32020-12-21 13:49:00 +010037var NodeDataPartition gpt.Type = gpt.Type("9eeec464-6885-414a-b278-4305c51f7966")
Lorenz Brun3a99c592021-01-26 19:57:21 +010038var NodeSystemPartition gpt.Type = gpt.Type("ee96055b-f6d0-4267-8bbb-724b2afea74c")
Lorenz Brunae0d90d2019-09-05 17:53:56 +020039
Leopold Schabel65493072019-11-06 13:40:44 +000040var (
Serge Bazanski0ed2f962021-03-15 16:39:30 +010041 flagEFI string
42 flagOut string
43 flagSystemPath string
44 flagNodeParameters string
45 flagDataPartitionSize uint64
46 flagESPPartitionSize uint64
47 flagSystemPartitionSize uint64
Leopold Schabel65493072019-11-06 13:40:44 +000048)
49
Lorenz Brunae0d90d2019-09-05 17:53:56 +020050func mibToSectors(size uint64) uint64 {
51 return (size * 1024 * 1024) / 512
52}
53
Lorenz Brun3a99c592021-01-26 19:57:21 +010054type devZeroReader struct{}
55
56func (_ devZeroReader) Read(b []byte) (n int, err error) {
57 for i := range b {
58 b[i] = 0
59 }
60 return len(b), nil
61}
62
63// devZero is a /dev/zero-like reader which reads an infinite number of zeroes
64var devZero = devZeroReader{}
65
Lorenz Brunae0d90d2019-09-05 17:53:56 +020066func main() {
Serge Bazanski032ca182020-06-09 20:17:13 +020067 flag.StringVar(&flagEFI, "efi", "", "UEFI payload")
68 flag.StringVar(&flagOut, "out", "", "Output disk image")
Lorenz Brun3a99c592021-01-26 19:57:21 +010069 flag.StringVar(&flagSystemPath, "system", "", "System partition [optional]")
Serge Bazanski0ed2f962021-03-15 16:39:30 +010070 flag.StringVar(&flagNodeParameters, "node_parameters", "", "Node parameters [optional]")
Serge Bazanski032ca182020-06-09 20:17:13 +020071 flag.Uint64Var(&flagDataPartitionSize, "data_partition_size", 2048, "Override the data partition size (default 2048 MiB)")
Lorenz Brun3a99c592021-01-26 19:57:21 +010072 flag.Uint64Var(&flagESPPartitionSize, "esp_partition_size", 128, "Override the ESP partition size (default: 128MiB)")
73 flag.Uint64Var(&flagSystemPartitionSize, "system_partition_size", 1024, "Override the System partition size (default: 1024MiB)")
Leopold Schabel65493072019-11-06 13:40:44 +000074 flag.Parse()
Serge Bazanski032ca182020-06-09 20:17:13 +020075
76 if flagEFI == "" || flagOut == "" {
77 log.Fatalf("efi and initramfs must be set")
Lorenz Brunf95909d2019-09-11 19:48:26 +020078 }
Leopold Schabel65493072019-11-06 13:40:44 +000079
Serge Bazanski032ca182020-06-09 20:17:13 +020080 _ = os.Remove(flagOut)
Lorenz Brun3a99c592021-01-26 19:57:21 +010081 diskImg, err := diskfs.Create(flagOut, 4*1024*1024*1024, diskfs.Raw)
Lorenz Brunae0d90d2019-09-05 17:53:56 +020082 if err != nil {
Serge Bazanski032ca182020-06-09 20:17:13 +020083 log.Fatalf("diskfs.Create(%q): %v", flagOut, err)
Lorenz Brunae0d90d2019-09-05 17:53:56 +020084 }
85
86 table := &gpt.Table{
Serge Bazanski216fe7b2021-05-21 18:36:16 +020087 // This is appropriate at least for virtio disks. Might need to be
88 // adjusted for real ones.
Lorenz Brunae0d90d2019-09-05 17:53:56 +020089 LogicalSectorSize: 512,
90 PhysicalSectorSize: 512,
91 ProtectiveMBR: true,
92 Partitions: []*gpt.Partition{
93 {
94 Type: gpt.EFISystemPartition,
95 Name: "ESP",
96 Start: mibToSectors(1),
Serge Bazanski032ca182020-06-09 20:17:13 +020097 End: mibToSectors(flagESPPartitionSize) - 1,
Lorenz Brunae0d90d2019-09-05 17:53:56 +020098 },
99 {
Lorenz Brun3a99c592021-01-26 19:57:21 +0100100 Type: NodeSystemPartition,
101 Name: "METROPOLIS-SYSTEM",
102 Start: mibToSectors(flagESPPartitionSize),
103 End: mibToSectors(flagESPPartitionSize+flagSystemPartitionSize) - 1,
104 },
105 {
Serge Bazanski662b5b32020-12-21 13:49:00 +0100106 Type: NodeDataPartition,
107 Name: "METROPOLIS-NODE-DATA",
Lorenz Brun3a99c592021-01-26 19:57:21 +0100108 Start: mibToSectors(flagESPPartitionSize + flagSystemPartitionSize),
109 End: mibToSectors(flagESPPartitionSize+flagSystemPartitionSize+flagDataPartitionSize) - 1,
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200110 },
111 },
112 }
113 if err := diskImg.Partition(table); err != nil {
Serge Bazanski032ca182020-06-09 20:17:13 +0200114 log.Fatalf("Failed to apply partition table: %v", err)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200115 }
116
Lorenz Brun3a99c592021-01-26 19:57:21 +0100117 if flagSystemPath != "" {
118 systemPart, err := os.Open(flagSystemPath)
119 if err != nil {
120 log.Fatalf("Failed to open system partition: %v", err)
121 }
122 defer systemPart.Close()
123 systemPartMeta, err := systemPart.Stat()
124 if err != nil {
125 log.Fatalf("Failed to stat system partition: %v", err)
126 }
127 padding := int64(flagSystemPartitionSize*1024*1024) - systemPartMeta.Size()
128 systemPartMulti := io.MultiReader(systemPart, io.LimitReader(devZero, padding))
129 if _, err := diskImg.WritePartitionContents(2, systemPartMulti); err != nil {
130 log.Fatalf("Failed to write system partition: %v", err)
131 }
132 }
133
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200134 fs, err := diskImg.CreateFilesystem(disk.FilesystemSpec{Partition: 1, FSType: filesystem.TypeFat32, VolumeLabel: "ESP"})
135 if err != nil {
Serge Bazanski032ca182020-06-09 20:17:13 +0200136 log.Fatalf("Failed to create filesystem: %v", err)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200137 }
Serge Bazanski032ca182020-06-09 20:17:13 +0200138
139 // Create EFI partition structure.
Serge Bazanski662b5b32020-12-21 13:49:00 +0100140 for _, dir := range []string{"/EFI", "/EFI/BOOT", "/EFI/metropolis"} {
Serge Bazanski032ca182020-06-09 20:17:13 +0200141 if err := fs.Mkdir(dir); err != nil {
142 log.Fatalf("Mkdir(%q): %v", dir, err)
Lorenz Brun0bcaaee2019-11-06 12:42:39 +0100143 }
144 }
Serge Bazanski032ca182020-06-09 20:17:13 +0200145
146 put(fs, flagEFI, "/EFI/BOOT/BOOTX64.EFI")
147
Serge Bazanski0ed2f962021-03-15 16:39:30 +0100148 if flagNodeParameters != "" {
149 put(fs, flagNodeParameters, "/EFI/metropolis/parameters.pb")
Serge Bazanski032ca182020-06-09 20:17:13 +0200150 }
151
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200152 if err := diskImg.File.Close(); err != nil {
Serge Bazanski032ca182020-06-09 20:17:13 +0200153 log.Fatalf("Failed to finalize image: %v", err)
154 }
155 log.Printf("Success! You can now boot %v", flagOut)
156}
157
158// put copies a file from the host filesystem into the target image.
159func put(fs filesystem.FileSystem, src, dst string) {
160 target, err := fs.OpenFile(dst, os.O_CREATE|os.O_RDWR)
161 if err != nil {
162 log.Fatalf("fs.OpenFile(%q): %v", dst, err)
163 }
164 source, err := os.Open(src)
165 if err != nil {
166 log.Fatalf("os.Open(%q): %v", src, err)
167 }
168 defer source.Close()
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200169 // If this is streamed (e.g. using io.Copy) it exposes a bug in diskfs, so
170 // do it in one go.
Serge Bazanski032ca182020-06-09 20:17:13 +0200171 data, err := ioutil.ReadAll(source)
172 if err != nil {
173 log.Fatalf("Reading %q: %v", src, err)
174 }
175 if _, err := target.Write(data); err != nil {
176 fmt.Printf("writing file %q: %v", dst, err)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200177 os.Exit(1)
178 }
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200179}