blob: 9f04660a243f55674e56446325e68f3702e8f3b2 [file] [log] [blame]
Serge Bazanskie50ec392020-06-30 21:41:39 +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 crypt
18
19import (
20 "context"
21 "fmt"
Serge Bazanskie50ec392020-06-30 21:41:39 +020022 "os"
23 "path/filepath"
24 "strconv"
Tim Windelschmidt35466152023-06-14 20:01:11 +020025 "strings"
Serge Bazanskie50ec392020-06-30 21:41:39 +020026
Lorenz Bruna3904fc2023-05-02 19:33:52 +020027 "github.com/google/uuid"
Serge Bazanskie50ec392020-06-30 21:41:39 +020028 "golang.org/x/sys/unix"
29
Lorenz Brun35fcf032023-06-29 04:15:58 +020030 "source.monogon.dev/metropolis/node/core/update"
Lorenz Brunad131882023-06-28 16:42:20 +020031 "source.monogon.dev/metropolis/pkg/blockdev"
Lorenz Bruna3904fc2023-05-02 19:33:52 +020032 "source.monogon.dev/metropolis/pkg/efivarfs"
33 "source.monogon.dev/metropolis/pkg/gpt"
34 "source.monogon.dev/metropolis/pkg/supervisor"
Serge Bazanski31370b02021-01-07 16:31:14 +010035 "source.monogon.dev/metropolis/pkg/sysfs"
Serge Bazanskie50ec392020-06-30 21:41:39 +020036)
37
Lorenz Bruna3904fc2023-05-02 19:33:52 +020038// NodeDataPartitionType is the partition type value for a Metropolis Node
39// data partition.
40var NodeDataPartitionType = uuid.MustParse("9eeec464-6885-414a-b278-4305c51f7966")
Serge Bazanskie50ec392020-06-30 21:41:39 +020041
Lorenz Brun35fcf032023-06-29 04:15:58 +020042var (
43 SystemAType = uuid.MustParse("ee96054b-f6d0-4267-aaaa-724b2afea74c")
44 SystemBType = uuid.MustParse("ee96054b-f6d0-4267-bbbb-724b2afea74c")
45)
46
Serge Bazanskie50ec392020-06-30 21:41:39 +020047const (
Lorenz Brun35fcf032023-06-29 04:15:58 +020048 ESPDevicePath = "/dev/esp"
49 NodeDataRawPath = "/dev/data-raw"
50 SystemADevicePath = "/dev/system-a"
51 SystemBDevicePath = "/dev/system-b"
Serge Bazanskie50ec392020-06-30 21:41:39 +020052)
53
Tim Windelschmidt35466152023-06-14 20:01:11 +020054// nodePathForPartitionType returns the device node path
55// for a given partition type.
56func nodePathForPartitionType(t uuid.UUID) string {
57 switch t {
58 case gpt.PartitionTypeEFISystem:
59 return ESPDevicePath
60 case NodeDataPartitionType:
61 return NodeDataRawPath
Lorenz Brun35fcf032023-06-29 04:15:58 +020062 case SystemAType:
63 return SystemADevicePath
64 case SystemBType:
65 return SystemBDevicePath
Tim Windelschmidt35466152023-06-14 20:01:11 +020066 }
67 return ""
68}
69
Serge Bazanski216fe7b2021-05-21 18:36:16 +020070// MakeBlockDevices looks for the ESP and the node data partition and maps them
71// to ESPDevicePath and NodeDataCryptPath respectively. This doesn't fail if it
72// doesn't find the partitions, only if something goes catastrophically wrong.
Lorenz Brun35fcf032023-06-29 04:15:58 +020073func MakeBlockDevices(ctx context.Context, updateSvc *update.Service) error {
Lorenz Bruna3904fc2023-05-02 19:33:52 +020074 espUUID, err := efivarfs.ReadLoaderDevicePartUUID()
75 if err != nil {
76 supervisor.Logger(ctx).Warningf("No EFI variable for the loader device partition UUID present")
77 }
Tim Windelschmidt35466152023-06-14 20:01:11 +020078
79 blockDevs, err := os.ReadDir("/sys/class/block")
Serge Bazanskie50ec392020-06-30 21:41:39 +020080 if err != nil {
81 return fmt.Errorf("failed to read sysfs block class: %w", err)
82 }
Tim Windelschmidt35466152023-06-14 20:01:11 +020083
84 for _, blockDev := range blockDevs {
Lorenz Brun35fcf032023-06-29 04:15:58 +020085 if err := handleBlockDevice(blockDev.Name(), blockDevs, espUUID, updateSvc); err != nil {
Tim Windelschmidt35466152023-06-14 20:01:11 +020086 supervisor.Logger(ctx).Errorf("Failed to create block device %s: %w", blockDev.Name(), err)
Serge Bazanskie50ec392020-06-30 21:41:39 +020087 }
Tim Windelschmidt35466152023-06-14 20:01:11 +020088 }
89
90 return nil
91}
92
93// handleBlockDevice reads the uevent data and continues to iterate over all
94// partitions to create all required device nodes.
Lorenz Brun35fcf032023-06-29 04:15:58 +020095func handleBlockDevice(diskBlockDev string, blockDevs []os.DirEntry, espUUID uuid.UUID, updateSvc *update.Service) error {
Tim Windelschmidt35466152023-06-14 20:01:11 +020096 data, err := readUEvent(diskBlockDev)
97 if err != nil {
98 return err
99 }
100
101 // We only care about disks, skip all other dev types.
102 if data["DEVTYPE"] != "disk" {
103 return nil
104 }
105
Lorenz Brunad131882023-06-28 16:42:20 +0200106 blkdev, err := blockdev.Open(fmt.Sprintf("/dev/%v", data["DEVNAME"]))
Tim Windelschmidt35466152023-06-14 20:01:11 +0200107 if err != nil {
Lorenz Brunad131882023-06-28 16:42:20 +0200108 return fmt.Errorf("failed to open block device: %w", err)
Tim Windelschmidt35466152023-06-14 20:01:11 +0200109 }
Lorenz Brunad131882023-06-28 16:42:20 +0200110 defer blkdev.Close()
Tim Windelschmidt35466152023-06-14 20:01:11 +0200111
Lorenz Brunad131882023-06-28 16:42:20 +0200112 table, err := gpt.Read(blkdev)
113 if err != nil {
114 return nil // Probably just not a GPT-partitioned disk
Tim Windelschmidt35466152023-06-14 20:01:11 +0200115 }
116
117 skipDisk := false
118 if espUUID != uuid.Nil {
119 // If we know where we booted from, ignore all disks which do
120 // not contain this partition.
121 skipDisk = true
122 for _, part := range table.Partitions {
123 if part.ID == espUUID {
124 skipDisk = false
125 break
Serge Bazanskie50ec392020-06-30 21:41:39 +0200126 }
127 }
128 }
Tim Windelschmidt35466152023-06-14 20:01:11 +0200129 if skipDisk {
130 return nil
131 }
132
133 seenTypes := make(map[uuid.UUID]bool)
134 for _, dev := range blockDevs {
Lorenz Brun35fcf032023-06-29 04:15:58 +0200135 if err := handlePartition(diskBlockDev, dev.Name(), table, seenTypes, updateSvc); err != nil {
Tim Windelschmidt35466152023-06-14 20:01:11 +0200136 return fmt.Errorf("when creating partition %s: %w", dev.Name(), err)
137 }
138 }
139
Serge Bazanskie50ec392020-06-30 21:41:39 +0200140 return nil
141}
Tim Windelschmidt35466152023-06-14 20:01:11 +0200142
Lorenz Brun35fcf032023-06-29 04:15:58 +0200143func handlePartition(diskBlockDev string, partBlockDev string, table *gpt.Table, seenTypes map[uuid.UUID]bool, updateSvc *update.Service) error {
Tim Windelschmidt35466152023-06-14 20:01:11 +0200144 // Skip all blockdev that dont share the same name/prefix,
145 // also skip the blockdev itself.
146 if !strings.HasPrefix(partBlockDev, diskBlockDev) || partBlockDev == diskBlockDev {
147 return nil
148 }
149
150 data, err := readUEvent(partBlockDev)
151 if err != nil {
152 return err
153 }
154
155 // We only care about partitions, skip all other dev types.
156 if data["DEVTYPE"] != "partition" {
157 return nil
158 }
159
160 pi, err := data.readPartitionInfo()
161 if err != nil {
162 return err
163 }
164
Tim Windelschmidt35466152023-06-14 20:01:11 +0200165 part := table.Partitions[pi.partNumber-1]
166
Tim Windelschmidt8e87a062023-07-31 01:33:10 +0000167 updateSvc.ProvideESP("/esp", uint32(pi.partNumber), part)
Lorenz Brun35fcf032023-06-29 04:15:58 +0200168
Tim Windelschmidt35466152023-06-14 20:01:11 +0200169 nodePath := nodePathForPartitionType(part.Type)
170 if nodePath == "" {
171 // Ignore partitions with an unknown type.
172 return nil
173 }
174
175 if seenTypes[part.Type] {
176 return fmt.Errorf("node for this type (%s) already created/multiple partitions found", part.Type.String())
177 }
178 seenTypes[part.Type] = true
179
180 if err := pi.makeDeviceNode(nodePath); err != nil {
181 return fmt.Errorf("when creating partition node: %w", err)
182 }
183
184 return nil
185}
186
187type partInfo struct {
188 major, minor, partNumber int
189}
190
191// validateDeviceNode tries to open a device node and validates that it
192// has the expected major and minor device numbers. If the path does non exist,
193// no error will be returned.
194func (pi partInfo) validateDeviceNode(path string) error {
195 var s unix.Stat_t
196 if err := unix.Stat(path, &s); err != nil {
197 if os.IsNotExist(err) {
198 return nil
199 }
200
201 return fmt.Errorf("inspecting device node %q: %w", path, err)
202 }
203
204 if unix.Major(s.Rdev) != uint32(pi.major) || unix.Minor(s.Rdev) != uint32(pi.minor) {
205 return fmt.Errorf("device node %q exists for different device %d:%d", path, unix.Major(s.Rdev), unix.Minor(s.Rdev))
206 }
207
208 return nil
209}
210
211// makeDeviceNode creates the device node at the given path based on the
212// major and minor device number. If the device node already exists and points
213// to the same device, no error will be returned.
214func (pi partInfo) makeDeviceNode(path string) error {
215 if err := pi.validateDeviceNode(path); err != nil {
216 return err
217 }
218
219 err := unix.Mknod(path, 0600|unix.S_IFBLK, int(unix.Mkdev(uint32(pi.major), uint32(pi.minor))))
220 if err != nil {
221 return fmt.Errorf("create device node %q: %w", path, err)
222 }
223 return nil
224}
225
226func readUEvent(blockName string) (blockUEvent, error) {
227 data, err := sysfs.ReadUevents(filepath.Join("/sys/class/block", blockName, "uevent"))
228 if err != nil {
229 return nil, fmt.Errorf("when reading uevent: %w", err)
230 }
231 return data, nil
232}
233
234type blockUEvent map[string]string
235
236func (b blockUEvent) readUdevKeyInteger(key string) (int, error) {
237 if _, ok := b[key]; !ok {
238 return 0, fmt.Errorf("missing udev value %s", key)
239 }
240
241 v, err := strconv.Atoi(b[key])
242 if err != nil {
243 return 0, fmt.Errorf("invalid %s: %w", key, err)
244 }
245
246 return v, nil
247}
248
249// readPartitionInfo parses all fields for partInfo from a blockUEvent.
250func (b blockUEvent) readPartitionInfo() (pi partInfo, err error) {
251 pi.major, err = b.readUdevKeyInteger("MAJOR")
252 if err != nil {
253 return
254 }
255
256 pi.minor, err = b.readUdevKeyInteger("MINOR")
257 if err != nil {
258 return
259 }
260
261 pi.partNumber, err = b.readUdevKeyInteger("PARTN")
262 if err != nil {
263 return
264 }
265
266 return
267}