mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-05-06 15:27:06 +00:00
232 lines
8.3 KiB
Go
232 lines
8.3 KiB
Go
/*
|
|
Copyright © 2022 SUSE LLC
|
|
|
|
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 partitions
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jaypipes/ghw"
|
|
"github.com/jaypipes/ghw/pkg/block"
|
|
"github.com/jaypipes/ghw/pkg/context"
|
|
"github.com/jaypipes/ghw/pkg/linuxpath"
|
|
ghwUtil "github.com/jaypipes/ghw/pkg/util"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
|
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ghwPartitionToInternalPartition transforms a block.Partition from ghw lib to our v1.Partition type
|
|
func ghwPartitionToInternalPartition(partition *block.Partition) *v1.Partition {
|
|
return &v1.Partition{
|
|
FilesystemLabel: partition.FilesystemLabel,
|
|
Size: uint(partition.SizeBytes / (1024 * 1024)), // Converts B to MB
|
|
Name: partition.Name,
|
|
FS: partition.Type,
|
|
Flags: nil,
|
|
MountPoint: partition.MountPoint,
|
|
Path: filepath.Join("/dev", partition.Name),
|
|
Disk: filepath.Join("/dev", partition.Disk.Name),
|
|
}
|
|
}
|
|
|
|
// GetAllPartitions returns all partitions in the system for all disks
|
|
func GetAllPartitions() (v1.PartitionList, error) {
|
|
var parts []*v1.Partition
|
|
blockDevices, err := block.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(part))
|
|
}
|
|
}
|
|
return parts, nil
|
|
}
|
|
|
|
// GetPartitionFS gets the FS of a partition given
|
|
func 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 := block.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 filesystem for partition %s", partition)
|
|
}
|
|
|
|
// GetPartitionViaDM tries to get the partition via devicemapper for reset
|
|
// We only need to get all this info due to the fS that we need to use to format the partition
|
|
// Otherwise we could just format with the label ¯\_(ツ)_/¯
|
|
// TODO: store info about persistent and oem in the state.yaml so we can directly load it
|
|
func GetPartitionViaDM(fs v1.FS, label string) *v1.Partition {
|
|
var part *v1.Partition
|
|
rootPath, _ := fs.RawPath("/")
|
|
ctx := context.New(ghw.WithDisableTools(), ghw.WithDisableWarnings(), ghw.WithChroot(rootPath))
|
|
lp := linuxpath.New(ctx)
|
|
devices, _ := fs.ReadDir(lp.SysBlock)
|
|
for _, dev := range devices {
|
|
if !strings.HasPrefix(dev.Name(), "dm-") {
|
|
continue
|
|
}
|
|
// read dev number
|
|
devNo, err := fs.ReadFile(filepath.Join(lp.SysBlock, dev.Name(), "dev"))
|
|
// No device number, empty dm?
|
|
if err != nil || string(devNo) == "" {
|
|
continue
|
|
}
|
|
udevID := "b" + strings.TrimSpace(string(devNo))
|
|
// Read udev info about this device
|
|
udevBytes, _ := fs.ReadFile(filepath.Join(lp.RunUdevData, udevID))
|
|
udevInfo := make(map[string]string)
|
|
for _, udevLine := range strings.Split(string(udevBytes), "\n") {
|
|
if strings.HasPrefix(udevLine, "E:") {
|
|
if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 {
|
|
udevInfo[s[0]] = s[1]
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
if udevInfo["ID_FS_LABEL"] == label {
|
|
// Found it!
|
|
partitionFS := udevInfo["ID_FS_TYPE"]
|
|
partitionName := udevInfo["DM_LV_NAME"]
|
|
|
|
part = &v1.Partition{
|
|
Name: partitionName,
|
|
FilesystemLabel: label,
|
|
FS: partitionFS,
|
|
Path: filepath.Join("/dev/disk/by-label/", label),
|
|
}
|
|
// Read size
|
|
sizeInSectors, err1 := fs.ReadFile(filepath.Join(lp.SysBlock, dev.Name(), "size"))
|
|
sectorSize, err2 := fs.ReadFile(filepath.Join(lp.SysBlock, dev.Name(), "queue", "logical_block_size"))
|
|
if err1 == nil && err2 == nil {
|
|
sizeInSectorsInt, err1 := strconv.Atoi(strings.TrimSpace(string(sizeInSectors)))
|
|
sectorSizeInt, err2 := strconv.Atoi(strings.TrimSpace(string(sectorSize)))
|
|
if err1 == nil && err2 == nil {
|
|
// Multiply size in sectors by sector size
|
|
// Although according to the source this will always be 512: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/types.h?#n120
|
|
finalSize := sizeInSectorsInt * sectorSizeInt
|
|
part.Size = uint(finalSize)
|
|
}
|
|
}
|
|
|
|
// Read slaves to get the device
|
|
slaves, err := fs.ReadDir(filepath.Join(lp.SysBlock, dev.Name(), "slaves"))
|
|
if err != nil {
|
|
log.Debugf("Error getting slaves for %s\n", filepath.Join(lp.SysBlock, dev.Name()))
|
|
}
|
|
if len(slaves) == 1 {
|
|
// We got the partition this dm is associated to, now lets read that partition udev identifier
|
|
partNumber, err := fs.ReadFile(filepath.Join(lp.SysBlock, dev.Name(), "slaves", slaves[0].Name(), "dev"))
|
|
fmt.Println(string(partNumber))
|
|
// If no errors and partNumber not empty read the device from udev
|
|
if err == nil || string(partNumber) != "" {
|
|
// Now for some magic!
|
|
// If udev partition identifier is bXXX:5 then the parent disk should be on bXXX:0
|
|
// At least for block devices that seems to be the pattern
|
|
// So let's get just the first part of the id and append a 0 at the end
|
|
// If we wanted to make this safer we could read the udev data of the partNumber and
|
|
// extract the udevInfo called ID_PART_ENTRY_DISK which gives us the udev ID of the parent disk
|
|
baseID := strings.Split(strings.TrimSpace(string(partNumber)), ":")
|
|
udevID = fmt.Sprintf("b%s:0", baseID[0])
|
|
fmt.Printf("Reading udevdata of device: %s\n", filepath.Join(lp.RunUdevData, udevID))
|
|
// Read udev info about this device
|
|
udevBytes, _ = fs.ReadFile(filepath.Join(lp.RunUdevData, udevID))
|
|
udevInfo = make(map[string]string)
|
|
for _, udevLine := range strings.Split(string(udevBytes), "\n") {
|
|
if strings.HasPrefix(udevLine, "E:") {
|
|
if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 {
|
|
udevInfo[s[0]] = s[1]
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
// Read the disk path.
|
|
// This is the only decent info that udev provides in this case that we can use to identify the device :/
|
|
diskPath := udevInfo["ID_PATH"]
|
|
// Read the full path to the disk using the path
|
|
parentDiskFullPath, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-path/", diskPath))
|
|
if err == nil {
|
|
part.Disk = parentDiskFullPath
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Read /proc/mounts to get the mountpoint if any
|
|
// We need the disk to be filled to get the mountpoint
|
|
if part.Disk != "" {
|
|
mounts, err := fs.ReadFile("/proc/mounts")
|
|
if err == nil {
|
|
for _, l := range strings.Split(string(mounts), "\n") {
|
|
entry := strings.Split(l, " ")
|
|
// entry is `device mountpoint fstype options unused unused`
|
|
// The unused fields are there for compatibility with mtab
|
|
if len(entry) > 1 {
|
|
// Check against the path as lvm devices are not mounted against /dev, they are mounted via label
|
|
if entry[0] == part.Path {
|
|
part.MountPoint = entry[1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return part
|
|
}
|
|
}
|
|
return part
|
|
}
|
|
|
|
// GetEfiPartition returns the EFI partition by looking for the partition with the label "COS_GRUB"
|
|
func GetEfiPartition() (*v1.Partition, error) {
|
|
var efiPartition *v1.Partition
|
|
parts, err := GetAllPartitions()
|
|
if err != nil {
|
|
return efiPartition, fmt.Errorf("could not read host partitions")
|
|
}
|
|
for _, p := range parts {
|
|
if p.FilesystemLabel == constants.EfiLabel {
|
|
efiPartition = p
|
|
break
|
|
}
|
|
}
|
|
if efiPartition == nil {
|
|
return efiPartition, fmt.Errorf("could not find EFI partition")
|
|
}
|
|
return efiPartition, nil
|
|
}
|