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