Skip to content

Commit

Permalink
Add block package
Browse files Browse the repository at this point in the history
The block package adds an interface to list and detect partitions.
In former Elemental Toolkit it was first implemented around wrapping
lsblk command line and at a later point in time it was reimplemented
around ghw library (we had to contribute there too).

This current block package includes both implementations, one around
ghw and another one around lsblk. I am not convinced it was a wise
move using ghw, hence I'd go back to lsblk because is powerful and provides
configurable JSON outputs which as easy to consume. I kept ghw around
for the time being just in case we discover some limitation with the
lsblk approach.

Signed-off-by: David Cassany <dcassany@suse.com>
  • Loading branch information
davidcassany committed Mar 5, 2025
1 parent ecf35c1 commit 6f1487c
Show file tree
Hide file tree
Showing 9 changed files with 992 additions and 0 deletions.
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/suse/elemental/v3
go 1.24

require (
github.com/jaypipes/ghw v0.14.0
github.com/onsi/ginkgo/v2 v2.22.2
github.com/onsi/gomega v1.36.2
github.com/sirupsen/logrus v1.9.3
Expand All @@ -13,22 +14,29 @@ require (
)

require (
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/jaypipes/pcidb v1.0.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
Expand All @@ -11,6 +13,9 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -19,10 +24,17 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jaypipes/ghw v0.14.0 h1:Z2AunEykaBYXLgpntVQB8SGmIFuCEmCcj6aS5j8xrys=
github.com/jaypipes/ghw v0.14.0/go.mod h1:F4UM7Ix55ONYwD3Lck2S4BI+hKezOwtizuJxXDFsioo=
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
Expand All @@ -31,13 +43,17 @@ github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -56,6 +72,7 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand All @@ -68,9 +85,12 @@ google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/mount-utils v0.32.2 h1:aDwp+ucWiVnDr/LpRg88/dsXf/vm6gI1VZkYH3+3+Vw=
Expand Down
117 changes: 117 additions & 0 deletions pkg/block/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
Copyright © 2022-2025 SUSE LLC

Check failure on line 2 in pkg/block/device.go

View workflow job for this annotation

GitHub Actions / build

Actual:

Check failure on line 2 in pkg/block/device.go

View workflow job for this annotation

GitHub Actions / build

Actual:
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package block

import (
"errors"
"time"

"github.com/suse/elemental/v3/pkg/sys"
)

const Ghw = "ghw"
const Lsblk = "lsblk"

type Device interface {
GetAllPartitions() (PartitionList, error)
GetDevicePartitions(device string) (PartitionList, error)
GetPartitionFS(partition string) (string, error)
}

// Partition struct represents a partition with its commonly configurable values, size in MiB
type Partition struct {
Name string
FilesystemLabel string
Size uint
FS string
Flags []string
MountPoints []string
Path string
Disk string
}

type PartitionList []*Partition

// GetByName gets a partitions by its name from the PartitionList
func (pl PartitionList) GetByName(name string) *Partition {
var part *Partition

for _, p := range pl {
if p.Name == name {
part = p
// Prioritize mounted partitions if there are multiple matches
if len(part.MountPoints) > 0 {
return part
}
}
}
return part
}

// GetByLabel gets a partition by its label from the PartitionList
func (pl PartitionList) GetByLabel(label string) *Partition {
var part *Partition

for _, p := range pl {
if p.FilesystemLabel == label {
part = p
// Prioritize mounted partitions if there are multiple matches
if len(part.MountPoints) > 0 {
return part
}
}
}
return part
}

// GetByNameOrLabel gets a partition by its name or label. It tries by name first
func (pl PartitionList) GetByNameOrLabel(name, label string) *Partition {
part := pl.GetByName(name)
if part == nil {
part = pl.GetByLabel(label)
}
return part
}

// GetPartitionByLabel works like GetPartitionByLabel, but it will try to get as much info as possible from the existing
// partition and return a Partition object
func GetPartitionByLabel(s *sys.System, b Device, label string, attempts int) (*Partition, error) {
for range attempts {
_, _ = s.Runner().Run("udevadm", "settle")
parts, err := b.GetAllPartitions()
if err != nil {
return nil, err
}
part := parts.GetByLabel(label)
if part != nil {
return part, nil
}
time.Sleep(1 * time.Second)
}
return nil, errors.New("no device found")
}

// GetPartitionDeviceByLabel will try to return the device that matches the given label.
// attempts value sets the number of attempts to find the device, it
// waits a second between attempts.
func GetPartitionDeviceByLabel(s *sys.System, b Device, label string, attempts int) (string, error) {
part, err := GetPartitionByLabel(s, b, label, attempts)
if err != nil {
return "", err
}
return part.Path, nil
}
123 changes: 123 additions & 0 deletions pkg/block/ghw/ghw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright © 2022-2025 SUSE LLC

Check failure on line 2 in pkg/block/ghw/ghw.go

View workflow job for this annotation

GitHub Actions / build

Actual:

Check failure on line 2 in pkg/block/ghw/ghw.go

View workflow job for this annotation

GitHub Actions / build

Actual:
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ghw

import (
"fmt"
"path/filepath"
"strings"

"github.com/jaypipes/ghw"
ghwblock "github.com/jaypipes/ghw/pkg/block"
ghwUtil "github.com/jaypipes/ghw/pkg/util"
"github.com/suse/elemental/v3/pkg/block"
"github.com/suse/elemental/v3/pkg/sys"
)

type ghwDevice struct {
runner sys.Runner
mounter sys.Mounter
}

func NewGhwDevice(s *sys.System) *ghwDevice { //nolint:revive
return &ghwDevice{runner: s.Runner(), mounter: s.Mounter()}
}

var _ block.Device = (*ghwDevice)(nil)

// ghwPartitionToInternalPartition transforms a block.Partition from ghw lib to our types.Partition type
func ghwPartitionToInternalPartition(m sys.Mounter, partition *ghwblock.Partition) *block.Partition {
mnts := []string{partition.MountPoint}
if partition.MountPoint != "" {
extraMnts, err := m.GetMountRefs(partition.MountPoint)
if err == nil {
mnts = append(mnts, extraMnts...)
}
}
return &block.Partition{
FilesystemLabel: partition.FilesystemLabel,
Size: uint(partition.SizeBytes / (1024 * 1024)), // Converts B to MB
Name: partition.Label,
FS: partition.Type,
Flags: nil,
MountPoints: mnts,
Path: filepath.Join("/dev", partition.Name),
Disk: filepath.Join("/dev", partition.Disk.Name),
}
}

// GetAllPartitions returns all partitions in the system for all disks
func (b ghwDevice) GetAllPartitions() (block.PartitionList, error) {
var parts []*block.Partition
blockDevices, err := ghwblock.New(ghw.WithDisableTools(), ghw.WithDisableWarnings())
if err != nil {
return nil, err
}
for _, d := range blockDevices.Disks {
for _, part := range d.Partitions {
parts = append(parts, ghwPartitionToInternalPartition(b.mounter, part))
}
}

return parts, nil
}

// GetDevicePartitions gets the partitions for the given disk
func (b ghwDevice) GetDevicePartitions(device string) (block.PartitionList, error) {
var parts []*block.Partition
// We want to have the device always prefixed with a /dev
if !strings.HasPrefix(device, "/dev") {
device = filepath.Join("/dev", device)
}
blockDevices, err := ghwblock.New(ghw.WithDisableTools(), ghw.WithDisableWarnings())
if err != nil {
return parts, err
}

for _, disk := range blockDevices.Disks {
if filepath.Join("/dev", disk.Name) == device {
for _, part := range disk.Partitions {
parts = append(parts, ghwPartitionToInternalPartition(b.mounter, part))
}
}
}
return parts, nil
}

// GetPartitionFS gets the FS of a partition given
func (b ghwDevice) GetPartitionFS(partition string) (string, error) {
// We want to have the device always prefixed with a /dev
if !strings.HasPrefix(partition, "/dev") {
partition = filepath.Join("/dev", partition)
}
blockDevices, err := ghwblock.New(ghw.WithDisableTools(), ghw.WithDisableWarnings())
if err != nil {
return "", err
}

for _, disk := range blockDevices.Disks {
for _, part := range disk.Partitions {
if filepath.Join("/dev", part.Name) == partition {
if part.Type == ghwUtil.UNKNOWN {
return "", fmt.Errorf("could not find filesystem for partition %s", partition)
}
return part.Type, nil
}
}
}
return "", fmt.Errorf("could not find partition %s", partition)
}
Loading

0 comments on commit 6f1487c

Please sign in to comment.