Skip to content

Commit adb724c

Browse files
committed
WWID handling wip
Implements #111 Implements #112
1 parent fff05fb commit adb724c

12 files changed

+425
-8
lines changed

Diff for: docs/manpage.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,15 @@ Some parts of booster boot functionality can be modified with kernel boot parame
131131
Device reference is a way to specify a device or partition in kernel parameters. It is labeled as `$deviceref` above.
132132
Device reference has one of the following values:
133133

134-
* `/dev/XXX` path to specific device file, it can be either a path to real device/partition like `/dev/sda1`, `/dev/nvme0n1` or path to dm-mapper virtual device like
134+
* `/dev/XXX` path to specific device file, it can be either a path to real device/partition like `/dev/sda1`, `/dev/nvme0n1` or path to dm-mapper virtual device like
135135
`/dev/mapper/root` or `/dev/vg_mesos/lv_mesos_containers`.
136136
* `UUID=$UUID` or `/dev/disk/by-uuid/$UUID` references device by its filesystem/LUKS UUID. See notes about UUID formatting rules below.
137137
* `LABEL=$LABEL` or `/dev/disk/by-label/$LABEL` references device by its filesystem/LUKS label.
138138
* `PARTUUID=$UUID` or `/dev/disk/by-partuuid/$UUID` references device by GPT partition UUID.
139139
* `PARTUUID=$UUID/PARTNROFF=$OFFSET` references device by $OFFSET from a GPT partition specified by $UUID e.g. `PARTUUID=fd59d06d-ffa8-473b-94f0-6584cb2b6665/PARTNROFF=2`.
140140
* `PARTLABEL=$LABEL` or `/dev/disk/by-partlabel/$LABEL` references device by GPT partition label.
141+
* `HWPATH=$PATH` or `/dev/disk/by-path/$PATH` references device by determenistic hardware path e.g. `pci-0000:02:00.0-nvme-1-part2`.
142+
* `WWID=$ID` or `/dev/disk/by-id/$ID` references device by its wwid e.g. `nvme-KXG6AZNV256G_TOSHIBA_40SA13GZF6B1-part3`
141143

142144
### UUID parameters
143145
Boot parameters such as `root=UUID=$UUID` and `rd.luks.uuid=$UUID` allow you to specify the block device by its UUID.

Diff for: init/blkinfo.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,18 @@ func readBlkInfo(path string) (*blkInfo, error) {
3434
probes := []probeFn{probeGpt, probeFat, probeMbr, probeLuks, probeExt4, probeBtrfs, probeXfs, probeF2fs, probeLvmPv, probeMdraid, probeSwap}
3535
for _, fn := range probes {
3636
blk := fn(r)
37-
if blk != nil {
38-
info("blkinfo for %s: type=%s UUID=%s LABEL=%s", path, blk.format, blk.uuid.toString(), blk.label)
39-
blk.path = path
40-
return blk, nil
37+
if blk == nil {
38+
continue
4139
}
40+
41+
// the following code mimics https://github.com/systemd/systemd/blob/main/rules.d/60-persistent-storage.rules
42+
// hwpath
43+
44+
// wwid
45+
46+
info("blkinfo for %s: type=%s UUID=%s LABEL=%s", path, blk.format, blk.uuid.toString(), blk.label)
47+
blk.path = path
48+
return blk, nil
4249
}
4350

4451
return nil, errUnknownBlockType

Diff for: init/deviceref.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const (
1919
refGptLabel
2020
refFsUUID
2121
refFsLabel
22+
refHwPath
23+
refWwID
2224
)
2325

2426
// The are many ways a user can specify root partition (using name, fs uuid, fs label, gpt attribute, ...).
@@ -112,7 +114,10 @@ func (d *deviceRef) dependsOnGpt() bool {
112114
return d.format == refGptType ||
113115
d.format == refGptUUID ||
114116
d.format == refGptUUIDPartoff ||
115-
d.format == refGptLabel
117+
d.format == refGptLabel ||
118+
// both hwpath and wwid might include the parent device reference + "-partNN" suffix
119+
d.format == refHwPath ||
120+
d.format == refWwID
116121
}
117122

118123
// checks whether given partition table contains active EFI service partition
@@ -199,6 +204,22 @@ func parseDeviceRef(param string) (*deviceRef, error) {
199204
label := strings.TrimPrefix(param, "/dev/disk/by-partlabel/")
200205
return &deviceRef{refGptLabel, label}, nil
201206
}
207+
if strings.HasPrefix(param, "HWPATH=") {
208+
path := strings.TrimPrefix(param, "HWPATH=")
209+
return &deviceRef{refHwPath, path}, nil
210+
}
211+
if strings.HasPrefix(param, "/dev/disk/by-path/") {
212+
path := strings.TrimPrefix(param, "/dev/disk/by-path/")
213+
return &deviceRef{refHwPath, path}, nil
214+
}
215+
if strings.HasPrefix(param, "WWID=") {
216+
id := strings.TrimPrefix(param, "WWID=")
217+
return &deviceRef{refWwID, id}, nil
218+
}
219+
if strings.HasPrefix(param, "/dev/disk/by-id/") {
220+
id := strings.TrimPrefix(param, "/dev/disk/by-id/")
221+
return &deviceRef{refWwID, id}, nil
222+
}
202223
if strings.HasPrefix(param, "/dev/") {
203224
return &deviceRef{refPath, param}, nil
204225
}

Diff for: init/go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ require (
2222
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
2323
)
2424

25+
require github.com/anatol/smart.go v0.0.0-20220218195151-5ee9e8fa73f0
26+
2527
require (
2628
github.com/davecgh/go-spew v1.1.1 // indirect
2729
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect

Diff for: init/go.sum

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ github.com/anatol/devmapper.go v0.0.0-20211210164347-f67e20c4e7f7 h1:/7MxcPFdUY7
1111
github.com/anatol/devmapper.go v0.0.0-20211210164347-f67e20c4e7f7/go.mod h1:zrna3BRNXKHSyOiw0ynGV2ASklN+IQBl4A4J+mXghCY=
1212
github.com/anatol/luks.go v0.0.0-20211210165108-5d9a15b4f614 h1:6nOeQoay/QVdEippGzfI9TxBTqPXV2EEfsY4jOBHJLc=
1313
github.com/anatol/luks.go v0.0.0-20211210165108-5d9a15b4f614/go.mod h1:88QkZxTPcAlsKkZvVVTs8YMn2Avp5H5itbmFNjGusaY=
14+
github.com/anatol/smart.go v0.0.0-20220218195151-5ee9e8fa73f0 h1:rnURfQKi7StDt+WxNWW0LSHZp4sexCTwV+QOFEEZJjg=
15+
github.com/anatol/smart.go v0.0.0-20220218195151-5ee9e8fa73f0/go.mod h1:mPog5NmM5znyLbUO9WQPJ4xQdVtuHPptPavY0055POc=
1416
github.com/anatol/tang.go v0.0.0-20211208011427-4f66a321d8ef/go.mod h1:GZLaH5+MzHEIY9aJpo6NU4Gess1AbA2P9AFFQkG8B2M=
1517
github.com/anatol/tang.go v0.0.0-20211230003118-4e3b011304ee h1:3OVqAwN2dpmvc1Evern4TyGvoPLiPAwiUn7MG5NV0KY=
1618
github.com/anatol/tang.go v0.0.0-20211230003118-4e3b011304ee/go.mod h1:6sIiHp2IvVRl6LTFvGO1OGW2Rzjl64gg5UHL0Jir42c=
1719
github.com/anatol/uevent.go v1.0.1-0.20210811163347-3e166d38c549 h1:CPuCjtWK5UI9KQPDhylGHfZe7Pp8j58WK7i3wpLemA0=
1820
github.com/anatol/uevent.go v1.0.1-0.20210811163347-3e166d38c549/go.mod h1:yO7hm0VhhOujsh+j0nn8ExPhAqIJh50MZOcKetKhAPA=
19-
github.com/anatol/vmtest v0.0.0-20211004221854-3a36b6b86cc0 h1:1my48uvB1J9Gpe9Jl03rdyHY2U4j0uRGQ6zmeGAMFao=
2021
github.com/anatol/vmtest v0.0.0-20211004221854-3a36b6b86cc0/go.mod h1:JiDFhD1zjgMx9ONsHhhucGwMvCLrJMl/yu/l5qP4XFw=
22+
github.com/anatol/vmtest v0.0.0-20211215032353-afd7b1dd38ef h1:YQzm9r8/ArTsQ6C3/h+w4Dz5dfkYkQbK/3ETLWW2i7Q=
23+
github.com/anatol/vmtest v0.0.0-20211215032353-afd7b1dd38ef/go.mod h1:JiDFhD1zjgMx9ONsHhhucGwMvCLrJMl/yu/l5qP4XFw=
2124
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
2225
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
2326
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -209,6 +212,7 @@ golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWP
209212
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
210213
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
211214
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
215+
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
212216
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
213217
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
214218
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -269,6 +273,7 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
269273
golang.org/x/sys v0.0.0-20211209171907-798191bca915/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
270274
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
271275
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
276+
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
272277
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
273278
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
274279
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=

Diff for: init/hwpath.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"path"
8+
"regexp"
9+
"strings"
10+
)
11+
12+
func hwPath(device string) (string, error) {
13+
p := ""
14+
parent := device
15+
for parent != "/" {
16+
subsystem, err := sysfsSubsystem(parent)
17+
if err != nil {
18+
return "", err
19+
}
20+
21+
sysname := path.Base(parent)
22+
23+
skipped := false
24+
switch subsystem {
25+
case "pci":
26+
p = prependPath("pci-"+sysname, p)
27+
parent, err = sysfsSkipSubsystem(parent, "pci")
28+
if err != nil {
29+
return "", err
30+
}
31+
skipped = true
32+
case "scsi":
33+
data, err := sysfsAttributeValue(parent, "uevent")
34+
if err != nil {
35+
return "", err
36+
}
37+
props := parseProperties(data)
38+
if props["DEVTYPE"] != "scsi_device" {
39+
break
40+
}
41+
42+
var host, bus, target, lun int
43+
num, err := fmt.Sscanf(sysname, "%d:%d:%d:%d", &host, &bus, &target, &lun)
44+
if err != nil {
45+
return "", err
46+
}
47+
if num != 4 {
48+
break
49+
}
50+
51+
ataRE, err := regexp.Compile(`^.*/ata\d+/`)
52+
if err != nil {
53+
return "", err
54+
}
55+
if ata := ataRE.FindString(parent); ata != "" {
56+
base := path.Base(ata)
57+
portNo, err := sysfsAttributeValue(ata+"/ata_port/"+base, "port_no")
58+
if err != nil {
59+
return "", err
60+
}
61+
if bus != 0 {
62+
/* Devices behind port multiplier have a bus != 0 */
63+
p = prependPath(fmt.Sprintf("ata-%s.%d.0", portNo, bus), p)
64+
} else {
65+
/* Master/slave are distinguished by target id */
66+
p = prependPath(fmt.Sprintf("ata-%s.%d", portNo, target), p)
67+
}
68+
} else {
69+
p = prependPath("scsi-"+sysname, p)
70+
}
71+
case "nvme":
72+
nsid, err := sysfsAttributeValue(device, "nsid")
73+
if err != nil {
74+
return "", err
75+
}
76+
p = prependPath("nvme-"+nsid, p)
77+
parent, err = sysfsSkipSubsystem(parent, "nvme")
78+
if err != nil {
79+
return "", err
80+
}
81+
skipped = true
82+
case "usb":
83+
port := sysname[strings.IndexByte(sysname, '-')+1:]
84+
p = prependPath("usb-0:"+port, p)
85+
parent, err = sysfsSkipSubsystem(parent, "usb")
86+
if err != nil {
87+
return "", err
88+
}
89+
skipped = true
90+
}
91+
if !skipped {
92+
parent = path.Dir(parent)
93+
}
94+
}
95+
return p, nil
96+
}
97+
98+
func prependPath(prefix, hwPath string) string {
99+
if hwPath != "" {
100+
return prefix + "-" + hwPath
101+
}
102+
return prefix
103+
}
104+
105+
func sysfsSkipSubsystem(p, subsystem string) (string, error) {
106+
for p != "/" {
107+
s, err := sysfsSubsystem(p)
108+
if err != nil {
109+
return "", err
110+
}
111+
if s != subsystem {
112+
break
113+
}
114+
p = path.Dir(p)
115+
}
116+
117+
return p, nil
118+
}
119+
120+
func sysfsSubsystem(sysPath string) (string, error) {
121+
l, err := os.Readlink(path.Join(sysPath, "subsystem"))
122+
if os.IsNotExist(err) {
123+
return "", nil
124+
}
125+
return path.Base(l), err
126+
}
127+
128+
func sysfsAttributeValue(sysPath, attr string) (string, error) {
129+
for sysPath != "/" {
130+
data, err := os.ReadFile(path.Join(sysPath, attr))
131+
sysPath = path.Dir(sysPath)
132+
if os.IsNotExist(err) {
133+
continue
134+
}
135+
if err != nil {
136+
return "", err
137+
}
138+
return string(bytes.TrimSpace(data)), nil
139+
}
140+
return "", os.ErrNotExist
141+
}
142+
143+
func sysfsSubsystems(sysPath string) (map[string]bool, error) {
144+
result := make(map[string]bool)
145+
for sysPath != "/" {
146+
l, err := sysfsSubsystem(sysPath)
147+
if err != nil {
148+
return nil, err
149+
}
150+
sysPath = path.Dir(sysPath)
151+
if l == "" {
152+
continue
153+
}
154+
result[path.Base(l)] = true
155+
}
156+
return result, nil
157+
}

Diff for: init/hwpath_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"regexp"
6+
"sort"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
/*func TestHwPath(t *testing.T) {
13+
check := func(input, expected string) {
14+
output, err := hwPath(input)
15+
require.NoError(t, err)
16+
require.Equal(t, expected, output)
17+
}
18+
19+
check("/sys/devices/pci0000:00/0000:00:17.0/ata3/host2/target2:0:0/2:0:0:0/block/sda", "pci-0000:00:17.0-ata-3.0") // or pci-0000:00:17.0-ata-3.0 ?
20+
check("/sys/devices/pci0000:00/0000:00:1d.0/0000:02:00.0/nvme/nvme1/nvme1n1", "pci-0000:02:00.0-nvme-1")
21+
check("/sys/devices/pci0000:00/0000:00:1b.0/0000:03:00.0/nvme/nvme0/nvme0n1", "pci-0000:03:00.0-nvme-1")
22+
}*/
23+
24+
// Goes over host's block devices and verifies its hw path
25+
func TestHostHwPath(t *testing.T) {
26+
ents, err := os.ReadDir("/sys/block")
27+
require.NoError(t, err)
28+
var generatedPaths []string
29+
for _, e := range ents {
30+
sysfs, err := sysfsPathForBlock(e.Name())
31+
require.NoError(t, err)
32+
path, err := hwPath(sysfs)
33+
require.NoError(t, err)
34+
if path == "" {
35+
continue // the block has no hardware path
36+
}
37+
generatedPaths = append(generatedPaths, path)
38+
}
39+
40+
ents, err = os.ReadDir("/dev/disk/by-path")
41+
require.NoError(t, err)
42+
var existedPaths []string
43+
44+
partitionRe, err := regexp.Compile(`-part\d+$`)
45+
require.NoError(t, err)
46+
47+
compatAtaRe, err := regexp.Compile(`-ata-\d+$`)
48+
require.NoError(t, err)
49+
50+
for _, e := range ents {
51+
if partitionRe.MatchString(e.Name()) {
52+
// ignore partitions
53+
continue
54+
}
55+
if compatAtaRe.MatchString(e.Name()) {
56+
// booster does not support compat ATA paths
57+
continue
58+
}
59+
60+
existedPaths = append(existedPaths, e.Name())
61+
}
62+
63+
sort.Strings(existedPaths)
64+
sort.Strings(generatedPaths)
65+
require.Equal(t, existedPaths, generatedPaths)
66+
}

Diff for: init/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func addTableNameForDevice(devPath string, tablePath string) {
8787
devicesMutex.Unlock()
8888
}
8989

90+
// waitForTableToProcess checks whether `dev` has a parent partition table (e.g. GPT table) and if yes
91+
// then waits till the parent device is completely processed.
9092
func waitForTableToProcess(dev string) {
9193
devicesMutex.Lock()
9294

Diff for: init/udev.go

-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ func handleBlockDeviceUevent(ev *uevent.Uevent) error {
144144
return nil
145145
}
146146
return handleMapperDeviceUevent(ev)
147-
148147
}
149148

150149
if ev.Action != "add" {

Diff for: init/util_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/stretchr/testify/require"
10+
"github.com/yookoala/realpath"
1011
"golang.org/x/sys/unix"
1112
)
1213

@@ -124,3 +125,7 @@ func TestFromUnicode16(t *testing.T) {
124125
check([]byte{0x3f, 0x04, 0x40, 0x04, 0x38, 0x04, 0x32, 0x04, 0x35, 0x04, 0x42, 0x04, 0x0, 0x0, 0x0, 0x0}, binary.LittleEndian, "привет")
125126
check([]byte{0x04, 0x3f, 0x04, 0x40, 0x04, 0x38, 0x04, 0x32, 0x04, 0x35, 0x04, 0x42, 0x0, 0x0}, binary.BigEndian, "привет")
126127
}
128+
129+
func sysfsPathForBlock(dev string) (string, error) {
130+
return realpath.Realpath("/sys/block/" + dev)
131+
}

0 commit comments

Comments
 (0)