1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-06 03:46:32 +01:00
Files
incus/shared/resources/system.go
Stéphane Graber f5ef7a8402 shared/resources: Restrict to Linux
Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
2025-08-12 15:57:04 -04:00

433 lines
11 KiB
Go

//go:build linux
package resources
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/subprocess"
)
var (
sysClassDMIID = "/sys/class/dmi/id"
systemType string
)
// GetSystem returns a filled api.ResourcesSystem struct ready for use by Incus.
func GetSystem() (*api.ResourcesSystem, error) {
var err error
system := api.ResourcesSystem{}
// Cache the system type
if systemType == "" {
systemType = systemGetType()
}
system.Type = systemType
if !sysfsExists(sysClassDMIID) {
lshwSystem := getSystemFromLshw()
if lshwSystem == nil {
return &system, nil
}
lshwSystem.Type = systemType
return lshwSystem, nil
}
// Product UUID
productUUIDPath := filepath.Join(sysClassDMIID, "product_uuid")
if sysfsExists(productUUIDPath) {
content, err := os.ReadFile(productUUIDPath)
if err != nil && !os.IsPermission(err) {
return nil, fmt.Errorf("Failed to read %q: %w", productUUIDPath, err)
}
system.UUID = strings.TrimSpace(string(content))
}
// Vendor
vendorPath := filepath.Join(sysClassDMIID, "sys_vendor")
if sysfsExists(vendorPath) {
content, err := os.ReadFile(vendorPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", vendorPath, err)
}
system.Vendor = strings.TrimSpace(string(content))
}
// Product name
productNamePath := filepath.Join(sysClassDMIID, "product_name")
if sysfsExists(productNamePath) {
content, err := os.ReadFile(productNamePath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", productNamePath, err)
}
system.Product = strings.TrimSpace(string(content))
}
// Product family
productFamilyPath := filepath.Join(sysClassDMIID, "product_family")
if sysfsExists(productFamilyPath) {
content, err := os.ReadFile(productFamilyPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", productFamilyPath, err)
}
system.Family = strings.TrimSpace(string(content))
}
// Product version
productVersion := filepath.Join(sysClassDMIID, "product_version")
if sysfsExists(productVersion) {
content, err := os.ReadFile(productVersion)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", productVersion, err)
}
system.Version = strings.TrimSpace(string(content))
}
// Product SKU
productSKUPath := filepath.Join(sysClassDMIID, "product_sku")
if sysfsExists(productSKUPath) {
content, err := os.ReadFile(productSKUPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", productSKUPath, err)
}
system.Sku = strings.TrimSpace(string(content))
}
// Product serial
productSerialPath := filepath.Join(sysClassDMIID, "product_serial")
if sysfsExists(productSerialPath) {
content, err := os.ReadFile(productSerialPath)
if err != nil && !os.IsPermission(err) {
return nil, fmt.Errorf("Failed to read %q: %w", productSerialPath, err)
}
system.Serial = strings.TrimSpace(string(content))
}
system.Firmware, err = systemGetFirmware()
if err != nil {
return nil, err
}
system.Chassis, err = systemGetChassis()
if err != nil {
return nil, err
}
system.Motherboard, err = systemGetMotherboard()
if err != nil {
return nil, err
}
return &system, nil
}
// getSystemFromLshw gets the system information from the `lshw` command.
func getSystemFromLshw() *api.ResourcesSystem {
output, err := subprocess.RunCommandCLocale("lshw", "-json")
if err != nil {
return nil
}
type systemConfiguration struct {
Chassis string `json:"chassis"` // system.chassis.type
Family string `json:"family"` // system.family
SKU string `json:"sku"` // system.sku
UUID string `json:"uuid"` // system.uuid
}
type systemChildren struct {
Children []systemChildren `json:"children"`
Description string `json:"description"`
ID string `json:"id"`
Date string `json:"date"` // system.firmware.date
Product string `json:"product"` // system.motherboard.product
Serial string `json:"serial"` // system.motherboard.serial
Vendor string `json:"vendor"` // system.firmware.vendor, system.motherboard.vendor
Version string `json:"version"` // system.firmware.version, system.motherboard.version
}
type system struct {
Configuration systemConfiguration `json:"configuration"`
Children []systemChildren `json:"children"`
Product string `json:"product"` // system.product
Serial string `json:"serial"` // system.serial
Vendor string `json:"vendor"` // system.vendor
Version string `json:"version"` // system.version
}
systemInfo := system{}
err = json.Unmarshal([]byte(output), &systemInfo)
if err != nil {
return nil
}
ret := api.ResourcesSystem{
Chassis: &api.ResourcesSystemChassis{
Type: systemInfo.Configuration.Chassis,
},
Family: systemInfo.Configuration.Family,
Product: systemInfo.Product,
Serial: systemInfo.Serial,
Sku: systemInfo.Configuration.SKU,
UUID: systemInfo.Configuration.UUID,
Vendor: systemInfo.Vendor,
Version: systemInfo.Version,
}
for _, child := range systemInfo.Children {
if child.Description != "Motherboard" {
continue
}
for _, subChild := range child.Children {
if subChild.ID != "firmware" {
continue
}
ret.Firmware = &api.ResourcesSystemFirmware{
Vendor: subChild.Vendor,
Date: subChild.Date,
Version: subChild.Version,
}
break
}
ret.Motherboard = &api.ResourcesSystemMotherboard{
Product: child.Product,
Serial: child.Serial,
Vendor: child.Vendor,
Version: child.Version,
}
break
}
return &ret
}
func systemGetType() string {
// If systemd-detect-virt is unavailable, the system type is unknown.
_, err := exec.LookPath("systemd-detect-virt")
if err != nil {
return "unknown"
}
runDetectVirt := func(flag string) error {
cmd := exec.Command("systemd-detect-virt", flag)
return cmd.Run()
}
// If this returns 0, we're in a container.
err = runDetectVirt("--container")
if err == nil {
return "container"
}
// If this returns 0, we're in a VM.
err = runDetectVirt("--vm")
if err == nil {
return "virtual-machine"
}
// Since we're neither in a container nor a VM, we must be on a physical
// machine.
return "physical"
}
func systemGetFirmware() (*api.ResourcesSystemFirmware, error) {
firmware := api.ResourcesSystemFirmware{}
// Firmware vendor
biosVendorPath := filepath.Join(sysClassDMIID, "bios_vendor")
if sysfsExists(biosVendorPath) {
content, err := os.ReadFile(biosVendorPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", biosVendorPath, err)
}
firmware.Vendor = strings.TrimSpace(string(content))
}
// Firmware date
biosDatePath := filepath.Join(sysClassDMIID, "bios_date")
if sysfsExists(biosDatePath) {
content, err := os.ReadFile(biosDatePath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", biosDatePath, err)
}
firmware.Date = strings.TrimSpace(string(content))
}
// Firmware version
biosVersionPath := filepath.Join(sysClassDMIID, "bios_version")
if sysfsExists(biosVersionPath) {
content, err := os.ReadFile(biosVersionPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", biosVersionPath, err)
}
firmware.Version = strings.TrimSpace(string(content))
}
return &firmware, nil
}
func systemGetChassis() (*api.ResourcesSystemChassis, error) {
chassis := api.ResourcesSystemChassis{}
// Chassis vendor
chassisVendorPath := filepath.Join(sysClassDMIID, "chassis_vendor")
if sysfsExists(chassisVendorPath) {
content, err := os.ReadFile(chassisVendorPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", chassisVendorPath, err)
}
chassis.Vendor = strings.TrimSpace(string(content))
}
// Chassis types according to the DMTF SMBIOS Spec
chassisTypes := map[uint64]string{
0x1: "Other",
0x2: "Unknown",
0x3: "Desktop",
0x4: "Low Profile Desktop",
0x5: "Pizza Box",
0x6: "Mini Tower",
0x7: "Tower",
0x8: "Portable",
0x9: "Laptop",
0xA: "Notebook",
0xB: "Hand Held",
0xC: "Docking Station",
0xD: "All in One",
0xE: "Sub Notebook",
0xF: "Space-saving",
0x10: "Lunch Box",
0x11: "Main Server Chassis",
0x12: "Expansion Chassis",
0x13: "SubChassis",
0x14: "Bus Expansion Chassis",
0x15: "Peripheral Chassis",
0x16: "RAID Chassis",
0x17: "Rack Mount Chassis",
0x18: "Sealed-case PC",
0x19: "Multi-system chassis",
0x1A: "Compact PCI",
0x1B: "Advanced TCA",
0x1C: "Blade",
0x1D: "Blade Enclosure",
0x1E: "Tablet",
0x1F: "Convertible",
0x20: "Detachable",
0x21: "IoT Gateway",
0x22: "Embedded PC",
0x23: "Mini PC",
0x24: "Stick PC",
}
// Chassis type
chassisTypePath := filepath.Join(sysClassDMIID, "chassis_type")
if sysfsExists(chassisTypePath) {
chassisType, err := readUint(chassisTypePath)
if err != nil {
return nil, fmt.Errorf("Failed to parse %q: %w", chassisTypePath, err)
}
chassis.Type = chassisTypes[chassisType]
}
// Chassis serial
chassisSerialPath := filepath.Join(sysClassDMIID, "chassis_serial")
if sysfsExists(chassisSerialPath) {
content, err := os.ReadFile(chassisSerialPath)
if err != nil && !os.IsPermission(err) {
return nil, fmt.Errorf("Failed to read %q: %w", chassisSerialPath, err)
}
chassis.Serial = strings.TrimSpace(string(content))
}
// Chassis version
chassisVersionPath := filepath.Join(sysClassDMIID, "chassis_version")
if sysfsExists(chassisVersionPath) {
content, err := os.ReadFile(chassisVersionPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", chassisVersionPath, err)
}
chassis.Version = strings.TrimSpace(string(content))
}
return &chassis, nil
}
func systemGetMotherboard() (*api.ResourcesSystemMotherboard, error) {
motherboard := api.ResourcesSystemMotherboard{}
// Motherboard vendor name
boardVendorPath := filepath.Join(sysClassDMIID, "board_vendor")
if sysfsExists(boardVendorPath) {
content, err := os.ReadFile(boardVendorPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", boardVendorPath, err)
}
motherboard.Vendor = strings.TrimSpace(string(content))
}
// Motherboard product name
boardNamePath := filepath.Join(sysClassDMIID, "board_name")
if sysfsExists(boardNamePath) {
content, err := os.ReadFile(boardNamePath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", boardNamePath, err)
}
motherboard.Product = strings.TrimSpace(string(content))
}
// Motherboard serial
boardSerialPath := filepath.Join(sysClassDMIID, "board_serial")
if sysfsExists(boardSerialPath) {
content, err := os.ReadFile(boardSerialPath)
if err != nil && !os.IsPermission(err) {
return nil, fmt.Errorf("Failed to read %q: %w", boardSerialPath, err)
}
motherboard.Serial = strings.TrimSpace(string(content))
}
// Motherboard version
boardVersionPath := filepath.Join(sysClassDMIID, "board_version")
if sysfsExists(boardVersionPath) {
content, err := os.ReadFile(boardVersionPath)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %w", boardVersionPath, err)
}
motherboard.Version = strings.TrimSpace(string(content))
}
return &motherboard, nil
}