-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bring GHW clone and partition type into sdk (#492)
- Loading branch information
Showing
6 changed files
with
586 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
package ghw | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/kairos-io/kairos-sdk/types" | ||
) | ||
|
||
const ( | ||
sectorSize = 512 | ||
UNKNOWN = "unknown" | ||
) | ||
|
||
type Disk struct { | ||
Name string `json:"name,omitempty" yaml:"name,omitempty"` | ||
SizeBytes uint64 `json:"size_bytes,omitempty" yaml:"size_bytes,omitempty"` | ||
UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` | ||
Partitions types.PartitionList `json:"partitions,omitempty" yaml:"partitions,omitempty"` | ||
} | ||
|
||
type Paths struct { | ||
SysBlock string | ||
RunUdevData string | ||
ProcMounts string | ||
} | ||
|
||
func NewPaths(withOptionalPrefix string) *Paths { | ||
p := &Paths{ | ||
SysBlock: "/sys/block/", | ||
RunUdevData: "/run/udev/data", | ||
ProcMounts: "/proc/mounts", | ||
} | ||
if withOptionalPrefix != "" { | ||
withOptionalPrefix = strings.TrimSuffix(withOptionalPrefix, "/") | ||
p.SysBlock = fmt.Sprintf("%s%s", withOptionalPrefix, p.SysBlock) | ||
p.RunUdevData = fmt.Sprintf("%s%s", withOptionalPrefix, p.RunUdevData) | ||
p.ProcMounts = fmt.Sprintf("%s%s", withOptionalPrefix, p.ProcMounts) | ||
} | ||
return p | ||
} | ||
|
||
func GetDisks(paths *Paths, logger *types.KairosLogger) []*Disk { | ||
if logger == nil { | ||
newLogger := types.NewKairosLogger("ghw", "info", false) | ||
logger = &newLogger | ||
} | ||
disks := make([]*Disk, 0) | ||
logger.Logger.Debug().Str("path", paths.SysBlock).Msg("Scanning for disks") | ||
files, err := os.ReadDir(paths.SysBlock) | ||
if err != nil { | ||
return nil | ||
} | ||
for _, file := range files { | ||
logger.Logger.Debug().Str("file", file.Name()).Msg("Reading file") | ||
dname := file.Name() | ||
size := diskSizeBytes(paths, dname, logger) | ||
|
||
if strings.HasPrefix(dname, "loop") && size == 0 { | ||
// We don't care about unused loop devices... | ||
continue | ||
} | ||
d := &Disk{ | ||
Name: dname, | ||
SizeBytes: size, | ||
UUID: diskUUID(paths, dname, "", logger), | ||
} | ||
|
||
parts := diskPartitions(paths, dname, logger) | ||
d.Partitions = parts | ||
|
||
disks = append(disks, d) | ||
} | ||
|
||
return disks | ||
} | ||
|
||
func diskSizeBytes(paths *Paths, disk string, logger *types.KairosLogger) uint64 { | ||
// We can find the number of 512-byte sectors by examining the contents of | ||
// /sys/block/$DEVICE/size and calculate the physical bytes accordingly. | ||
path := filepath.Join(paths.SysBlock, disk, "size") | ||
logger.Logger.Debug().Str("path", path).Msg("Reading disk size") | ||
contents, err := os.ReadFile(path) | ||
if err != nil { | ||
logger.Logger.Error().Str("path", path).Err(err).Msg("Failed to read file") | ||
return 0 | ||
} | ||
size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) | ||
if err != nil { | ||
logger.Logger.Error().Str("path", path).Err(err).Str("content", string(contents)).Msg("Failed to parse size") | ||
return 0 | ||
} | ||
logger.Logger.Trace().Uint64("size", size*sectorSize).Msg("Got disk size") | ||
return size * sectorSize | ||
} | ||
|
||
// diskPartitions takes the name of a disk (note: *not* the path of the disk, | ||
// but just the name. In other words, "sda", not "/dev/sda" and "nvme0n1" not | ||
// "/dev/nvme0n1") and returns a slice of pointers to Partition structs | ||
// representing the partitions in that disk | ||
func diskPartitions(paths *Paths, disk string, logger *types.KairosLogger) types.PartitionList { | ||
out := make(types.PartitionList, 0) | ||
path := filepath.Join(paths.SysBlock, disk) | ||
logger.Logger.Debug().Str("file", path).Msg("Reading disk file") | ||
files, err := os.ReadDir(path) | ||
if err != nil { | ||
logger.Logger.Error().Err(err).Msg("failed to read disk partitions") | ||
return out | ||
} | ||
for _, file := range files { | ||
fname := file.Name() | ||
if !strings.HasPrefix(fname, disk) { | ||
continue | ||
} | ||
logger.Logger.Debug().Str("file", fname).Msg("Reading partition file") | ||
size := partitionSizeBytes(paths, disk, fname, logger) | ||
mp, pt := partitionInfo(paths, fname, logger) | ||
du := diskPartUUID(paths, disk, fname, logger) | ||
if pt == "" { | ||
pt = diskPartTypeUdev(paths, disk, fname, logger) | ||
} | ||
fsLabel := diskFSLabel(paths, disk, fname, logger) | ||
p := &types.Partition{ | ||
Name: fname, | ||
Size: uint(size / (1024 * 1024)), | ||
MountPoint: mp, | ||
UUID: du, | ||
FilesystemLabel: fsLabel, | ||
FS: pt, | ||
Path: filepath.Join("/dev", fname), | ||
Disk: filepath.Join("/dev", disk), | ||
} | ||
out = append(out, p) | ||
} | ||
return out | ||
} | ||
|
||
func partitionSizeBytes(paths *Paths, disk string, part string, logger *types.KairosLogger) uint64 { | ||
path := filepath.Join(paths.SysBlock, disk, part, "size") | ||
logger.Logger.Debug().Str("file", path).Msg("Reading size file") | ||
contents, err := os.ReadFile(path) | ||
if err != nil { | ||
logger.Logger.Error().Str("file", path).Err(err).Msg("failed to read disk partition size") | ||
return 0 | ||
} | ||
size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) | ||
if err != nil { | ||
logger.Logger.Error().Str("contents", string(contents)).Err(err).Msg("failed to parse disk partition size") | ||
return 0 | ||
} | ||
logger.Logger.Trace().Str("disk", disk).Str("partition", part).Uint64("size", size*sectorSize).Msg("Got partition size") | ||
return size * sectorSize | ||
} | ||
|
||
func partitionInfo(paths *Paths, part string, logger *types.KairosLogger) (string, string) { | ||
// Allow calling PartitionInfo with either the full partition name | ||
// "/dev/sda1" or just "sda1" | ||
if !strings.HasPrefix(part, "/dev") { | ||
part = "/dev/" + part | ||
} | ||
|
||
// mount entries for mounted partitions look like this: | ||
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 | ||
var r io.ReadCloser | ||
logger.Logger.Debug().Str("file", paths.ProcMounts).Msg("Reading mounts file") | ||
r, err := os.Open(paths.ProcMounts) | ||
if err != nil { | ||
logger.Logger.Error().Str("file", paths.ProcMounts).Err(err).Msg("failed to open mounts") | ||
return "", "" | ||
} | ||
defer r.Close() | ||
|
||
scanner := bufio.NewScanner(r) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
logger.Logger.Debug().Str("line", line).Msg("Parsing mount info") | ||
entry := parseMountEntry(line, logger) | ||
if entry == nil || entry.Partition != part { | ||
continue | ||
} | ||
|
||
return entry.Mountpoint, entry.FilesystemType | ||
} | ||
return "", "" | ||
} | ||
|
||
type mountEntry struct { | ||
Partition string | ||
Mountpoint string | ||
FilesystemType string | ||
} | ||
|
||
func parseMountEntry(line string, logger *types.KairosLogger) *mountEntry { | ||
// mount entries for mounted partitions look like this: | ||
// /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 | ||
if line[0] != '/' { | ||
return nil | ||
} | ||
fields := strings.Fields(line) | ||
|
||
if len(fields) < 4 { | ||
logger.Logger.Debug().Interface("fields", fields).Msg("Mount line has less than 4 fields") | ||
return nil | ||
} | ||
|
||
// 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) | ||
|
||
res := &mountEntry{ | ||
Partition: fields[0], | ||
Mountpoint: mp, | ||
FilesystemType: fields[2], | ||
} | ||
return res | ||
} | ||
|
||
func diskUUID(paths *Paths, disk string, partition string, logger *types.KairosLogger) string { | ||
info, err := udevInfoPartition(paths, disk, partition, logger) | ||
logger.Logger.Trace().Interface("info", info).Msg("Disk UUID") | ||
if err != nil { | ||
logger.Logger.Error().Str("disk", disk).Str("partition", partition).Interface("info", info).Err(err).Msg("failed to read disk UUID") | ||
return UNKNOWN | ||
} | ||
|
||
if pType, ok := info["ID_PART_TABLE_UUID"]; ok { | ||
logger.Logger.Trace().Str("disk", disk).Str("partition", partition).Str("uuid", pType).Msg("Got disk uuid") | ||
return pType | ||
} | ||
|
||
return UNKNOWN | ||
} | ||
|
||
func diskPartUUID(paths *Paths, disk string, partition string, logger *types.KairosLogger) string { | ||
info, err := udevInfoPartition(paths, disk, partition, logger) | ||
logger.Logger.Trace().Interface("info", info).Msg("Disk Part UUID") | ||
if err != nil { | ||
logger.Logger.Error().Str("disk", disk).Str("partition", partition).Interface("info", info).Err(err).Msg("Disk Part UUID") | ||
return UNKNOWN | ||
} | ||
|
||
if pType, ok := info["ID_PART_ENTRY_UUID"]; ok { | ||
logger.Logger.Trace().Str("disk", disk).Str("partition", partition).Str("uuid", pType).Msg("Got partition uuid") | ||
return pType | ||
} | ||
return UNKNOWN | ||
} | ||
|
||
// diskPartTypeUdev gets the partition type from the udev database directly and its only used as fallback when | ||
// the partition is not mounted, so we cannot get the type from paths.ProcMounts from the partitionInfo function | ||
func diskPartTypeUdev(paths *Paths, disk string, partition string, logger *types.KairosLogger) string { | ||
info, err := udevInfoPartition(paths, disk, partition, logger) | ||
logger.Logger.Trace().Interface("info", info).Msg("Disk Part Type") | ||
if err != nil { | ||
logger.Logger.Error().Str("disk", disk).Str("partition", partition).Interface("info", info).Err(err).Msg("Disk Part Type") | ||
return UNKNOWN | ||
} | ||
|
||
if pType, ok := info["ID_FS_TYPE"]; ok { | ||
logger.Logger.Trace().Str("disk", disk).Str("partition", partition).Str("FS", pType).Msg("Got partition fs type") | ||
return pType | ||
} | ||
return UNKNOWN | ||
} | ||
|
||
func diskFSLabel(paths *Paths, disk string, partition string, logger *types.KairosLogger) string { | ||
info, err := udevInfoPartition(paths, disk, partition, logger) | ||
logger.Logger.Trace().Interface("info", info).Msg("Disk FS label") | ||
if err != nil { | ||
logger.Logger.Error().Str("disk", disk).Str("partition", partition).Interface("info", info).Err(err).Msg("Disk FS label") | ||
return UNKNOWN | ||
} | ||
|
||
if label, ok := info["ID_FS_LABEL"]; ok { | ||
logger.Logger.Trace().Str("disk", disk).Str("partition", partition).Str("uuid", label).Msg("Got partition label") | ||
return label | ||
} | ||
return UNKNOWN | ||
} | ||
|
||
func udevInfoPartition(paths *Paths, disk string, partition string, logger *types.KairosLogger) (map[string]string, error) { | ||
// Get device major:minor numbers | ||
devNo, err := os.ReadFile(filepath.Join(paths.SysBlock, disk, partition, "dev")) | ||
if err != nil { | ||
logger.Logger.Error().Err(err).Str("path", filepath.Join(paths.SysBlock, disk, partition, "dev")).Msg("failed to read udev info") | ||
return nil, err | ||
} | ||
return UdevInfo(paths, string(devNo), logger) | ||
} | ||
|
||
// UdevInfo will return information on udev database about a device number | ||
func UdevInfo(paths *Paths, devNo string, logger *types.KairosLogger) (map[string]string, error) { | ||
// Look up block device in udev runtime database | ||
udevID := "b" + strings.TrimSpace(devNo) | ||
udevBytes, err := os.ReadFile(filepath.Join(paths.RunUdevData, udevID)) | ||
if err != nil { | ||
logger.Logger.Error().Err(err).Str("path", filepath.Join(paths.RunUdevData, udevID)).Msg("failed to read udev info for device") | ||
return nil, err | ||
} | ||
|
||
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] | ||
} | ||
} | ||
} | ||
return udevInfo, nil | ||
} |
Oops, something went wrong.