mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-06-03 01:44:53 +00:00
241 lines
8.2 KiB
Go
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
|
|
}
|