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