blob: c7cbbd70259c2bdb3af331b84db6cf085d4ddefd [file] [log] [blame]
Mateusz Zalega43e21072021-10-08 18:05:29 +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
17// Implementation included in this file was written with the aim of easing
18// integration with the interface exposed at /sys/class/block. It assumes sysfs
19// is already mounted at /sys.
20package sysfs
21
22import (
Lorenz Brun57d06a72022-01-13 14:12:27 +010023 "errors"
Mateusz Zalega43e21072021-10-08 18:05:29 +020024 "fmt"
25 "os"
26 "path/filepath"
27 "strconv"
28 "strings"
Lorenz Brunca9cfcf2023-05-02 19:29:14 +020029
30 "github.com/google/uuid"
Mateusz Zalega43e21072021-10-08 18:05:29 +020031)
32
33// PartUUIDMap returns a mapping between partition UUIDs and block device
34// names based on information exposed by uevent. UUID keys of the returned
35// map are represented as lowercase strings.
36func PartUUIDMap() (map[string]string, error) {
37 m := make(map[string]string)
38 // Get a list of block device symlinks from sysfs.
39 const blkDirPath = "/sys/class/block"
40 blkDevs, err := os.ReadDir(blkDirPath)
41 if err != nil {
42 return m, fmt.Errorf("couldn't read %q: %w", blkDirPath, err)
43 }
44 // Iterate over block device symlinks present in blkDevs, creating a mapping
45 // in m for each device with both PARTUUID and DEVNAME keys present in their
46 // respective uevent files.
47 for _, devInfo := range blkDevs {
48 // Read the uevent file and transform it into a string->string map.
49 kv, err := ReadUevents(filepath.Join(blkDirPath, devInfo.Name(), "uevent"))
50 if err != nil {
51 return m, fmt.Errorf("while reading uevents: %w", err)
52 }
53 // Check that the required keys are present in the map.
54 if uuid, name := kv["PARTUUID"], kv["DEVNAME"]; uuid != "" && name != "" {
55 m[uuid] = name
56 }
57 }
58 return m, nil
59}
60
Lorenz Brun57d06a72022-01-13 14:12:27 +010061var ErrDevNotFound = errors.New("device not found")
62
Mateusz Zalega43e21072021-10-08 18:05:29 +020063// DeviceByPartUUID returns a block device name, given its corresponding
64// partition UUID.
Lorenz Brunca9cfcf2023-05-02 19:29:14 +020065func DeviceByPartUUID(id uuid.UUID) (string, error) {
Mateusz Zalega43e21072021-10-08 18:05:29 +020066 pm, err := PartUUIDMap()
67 if err != nil {
68 return "", err
69 }
Lorenz Brunca9cfcf2023-05-02 19:29:14 +020070 if bdev, ok := pm[id.String()]; ok {
Mateusz Zalega43e21072021-10-08 18:05:29 +020071 return bdev, nil
72 }
Lorenz Brun57d06a72022-01-13 14:12:27 +010073 return "", ErrDevNotFound
Mateusz Zalega43e21072021-10-08 18:05:29 +020074}
75
76// ParentBlockDevice transforms the block device name of a partition, eg
77// "sda1", to the name of the block device hosting it, eg "sda".
78func ParentBlockDevice(dev string) (string, error) {
79 // Build a path pointing to a sysfs block device symlink.
80 partLink := filepath.Join("/sys/class/block", dev)
81 // Read the symlink at partLink. This should leave us with a path of the form
82 // (...)/sda/sdaN.
83 linkTgt, err := os.Readlink(partLink)
84 if err != nil {
85 return "", fmt.Errorf("couldn't read the block device symlink at %q: %w", partLink, err)
86 }
87 // Remove the last element from the path, leaving us with a path pointing to
88 // the block device containting the installer partition, of the form
89 // (...)/sda.
90 devPath := filepath.Dir(linkTgt)
91 // Get the last element of the path, leaving us with just the block device
92 // name, eg sda
93 devName := filepath.Base(devPath)
94 return devName, nil
95}
96
97// PartitionBlockDevice returns the name of a block device associated with the
98// partition at index in the containing block device dev, eg "nvme0n1pN" for
99// "nvme0n1" or "sdaN" for "sda".
100func PartitionBlockDevice(dev string, index int) (string, error) {
101 dp := filepath.Join("/sys/class/block", dev)
102 dir, err := os.ReadDir(dp)
103 if err != nil {
104 return "", err
105 }
106 for _, info := range dir {
107 // Skip non-directories
108 if !info.IsDir() {
109 continue
110 }
111 // Check whether the directory contains a file named 'partition'. If that's
112 // the case, read the partition index from it and compare it with the one
113 // supplied as a function parameter. If they're equal, return the directory
114 // name.
115 istr, err := os.ReadFile(filepath.Join(dp, info.Name(), "partition"))
116 if os.IsNotExist(err) {
117 continue
118 }
119 if err != nil {
120 return "", err
121 }
122 // istr holds a newline-terminated ASCII-encoded decimal number.
123 pi, err := strconv.Atoi(strings.TrimSuffix(string(istr), "\n"))
124 if err != nil {
125 return "", fmt.Errorf("failed to parse partition index: %w", err)
126 }
127 if pi == index {
128 return info.Name(), nil
129 }
130 }
131 return "", fmt.Errorf("couldn't find partition %d of %q", index, dev)
132}