kairos-agent/pkg/utils/partitions/getpartitions.go
Itxaka 4975b9b914
Bump yip and diskfs (#717)
* Bump yip and diskfs

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-26 11:57:29 +01:00

241 lines
8.2 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 (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-sdk/ghw"
"github.com/kairos-io/kairos-sdk/types"
log "github.com/sirupsen/logrus"
)
// GetAllPartitions returns all partitions in the system for all disks
func GetAllPartitions(logger *types.KairosLogger) (types.PartitionList, error) {
var parts []*types.Partition
for _, d := range ghw.GetDisks(ghw.NewPaths(""), logger) {
for _, part := range d.Partitions {
parts = append(parts, part)
}
}
return parts, nil
}
// GetMountPointByLabel will try to get the mountpoint by using the label only
// so we can identify mounts the have been mounted with /dev/disk/by-label stanzas
func GetMountPointByLabel(label string) string {
// mount entries for mounted partitions look like this:
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
var r io.ReadCloser
r, err := os.Open("/proc/mounts")
if err != nil {
return ""
}
defer r.Close()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
partition, mountpoint := parseMountEntry(line)
if partition == fmt.Sprintf("/dev/disk/by-label/%s", label) {
return mountpoint
}
}
return ""
}
func parseMountEntry(line string) (string, string) {
// mount entries for mounted partitions look like this:
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
if line[0] != '/' {
return "", ""
}
fields := strings.Fields(line)
if len(fields) < 4 {
return "", ""
}
// We do some special parsing of the mountpoint, which may contain space,
// tab and newline characters, encoded into the mount entry line using their
// octal-to-string representations. From the GNU mtab man pages:
//
// "Therefore these characters are encoded in the files and the getmntent
// function takes care of the decoding while reading the entries back in.
// '\040' is used to encode a space character, '\011' to encode a tab
// character, '\012' to encode a newline character, and '\\' to encode a
// backslash."
mp := fields[1]
r := strings.NewReplacer(
"\\011", "\t", "\\012", "\n", "\\040", " ", "\\\\", "\\",
)
mp = r.Replace(mp)
return fields[0], mp
}
// 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) *types.Partition {
var part *types.Partition
rootPath, _ := fs.RawPath("/")
lp := ghw.NewPaths(rootPath)
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 = &types.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"))
// 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])
// 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(logger *types.KairosLogger) (*types.Partition, error) {
var efiPartition *types.Partition
for _, d := range ghw.GetDisks(ghw.NewPaths(""), logger) {
for _, part := range d.Partitions {
if part.FilesystemLabel == constants.EfiLabel {
efiPartition = part
break
}
}
}
if efiPartition == nil {
return efiPartition, fmt.Errorf("could not find EFI partition")
}
return efiPartition, nil
}