-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
ecf35c1
commit 6f1487c
Showing
9 changed files
with
992 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
|
||
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) | ||
} |
Oops, something went wrong.