2023-05-05 16:43:21 +00:00
/ *
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 .
* /
2023-07-25 13:21:34 +00:00
package partitions
2023-05-05 16:43:21 +00:00
import (
"fmt"
"path/filepath"
2023-06-23 12:49:38 +00:00
"strconv"
2023-05-05 16:43:21 +00:00
"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"
2024-02-21 09:44:32 +00:00
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
2023-07-10 12:39:48 +00:00
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
2023-06-23 12:49:38 +00:00
log "github.com/sirupsen/logrus"
2023-05-05 16:43:21 +00:00
)
2023-06-23 12:49:38 +00:00
// 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 ) ,
2023-05-05 16:43:21 +00:00
}
2023-06-13 11:41:53 +00:00
}
2023-06-23 12:49:38 +00:00
// 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 ( ) )
2023-06-13 11:41:53 +00:00
if err != nil {
2023-06-23 12:49:38 +00:00
return nil , err
2023-06-13 11:41:53 +00:00
}
2023-06-23 12:49:38 +00:00
for _ , d := range blockDevices . Disks {
for _ , part := range d . Partitions {
parts = append ( parts , ghwPartitionToInternalPartition ( part ) )
2023-05-05 16:43:21 +00:00
}
}
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 )
}
2023-06-23 12:49:38 +00:00
// GetPartitionViaDM tries to get the partition via devicemapper for reset
2023-05-05 16:43:21 +00:00
// 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
2023-06-23 12:49:38 +00:00
func GetPartitionViaDM ( fs v1 . FS , label string ) * v1 . Partition {
var part * v1 . Partition
2023-05-05 16:43:21 +00:00
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" ) )
2023-06-23 12:49:38 +00:00
// No device number, empty dm?
2023-05-05 16:43:21 +00:00
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
}
}
}
2023-06-23 12:49:38 +00:00
if udevInfo [ "ID_FS_LABEL" ] == label {
2023-05-05 16:43:21 +00:00
// Found it!
2023-06-23 12:49:38 +00:00
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 ( ) ) )
2023-05-05 16:43:21 +00:00
}
2023-06-23 12:49:38 +00:00
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
2023-05-05 16:43:21 +00:00
}
}
2023-06-23 12:49:38 +00:00
return part
2023-05-05 16:43:21 +00:00
}
2024-02-21 09:44:32 +00:00
// 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
}