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 (
2024-09-12 15:24:35 +00:00
"bufio"
2023-05-05 16:43:21 +00:00
"fmt"
2024-09-12 15:24:35 +00:00
"io"
"os"
2023-05-05 16:43:21 +00:00
"path/filepath"
2023-06-23 12:49:38 +00:00
"strconv"
2023-05-05 16:43:21 +00:00
"strings"
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"
2024-09-17 13:27:31 +00:00
"github.com/kairos-io/kairos-sdk/ghw"
"github.com/kairos-io/kairos-sdk/types"
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
// GetAllPartitions returns all partitions in the system for all disks
2024-09-17 13:27:31 +00:00
func GetAllPartitions ( logger * types . KairosLogger ) ( types . PartitionList , error ) {
var parts [ ] * types . Partition
for _ , d := range ghw . GetDisks ( ghw . NewPaths ( "" ) , logger ) {
2023-06-23 12:49:38 +00:00
for _ , part := range d . Partitions {
2024-09-17 13:27:31 +00:00
parts = append ( parts , part )
2023-05-05 16:43:21 +00:00
}
}
return parts , nil
}
2024-09-12 15:24:35 +00:00
// 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
}
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
2024-09-17 13:27:31 +00:00
func GetPartitionViaDM ( fs v1 . FS , label string ) * types . Partition {
var part * types . Partition
2023-05-05 16:43:21 +00:00
rootPath , _ := fs . RawPath ( "/" )
2024-09-17 13:27:31 +00:00
lp := ghw . NewPaths ( rootPath )
2023-05-05 16:43:21 +00:00
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" ]
2024-09-17 13:27:31 +00:00
part = & types . Partition {
2023-06-23 12:49:38 +00:00
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" ) )
// 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
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"
2024-09-17 13:27:31 +00:00
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
}
2024-02-21 09:44:32 +00:00
}
}
2024-09-17 13:27:31 +00:00
2024-02-21 09:44:32 +00:00
if efiPartition == nil {
return efiPartition , fmt . Errorf ( "could not find EFI partition" )
}
return efiPartition , nil
}