diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go index ee056ce092e3..636ee645482b 100644 --- a/lxd/apparmor/instance.go +++ b/lxd/apparmor/instance.go @@ -26,6 +26,12 @@ type instance interface { DevicesPath() string } +type instanceVM interface { + instance + + FirmwarePath() string +} + // InstanceProfileName returns the instance's AppArmor profile name. func InstanceProfileName(inst instance) string { path := shared.VarPath("") @@ -194,9 +200,18 @@ func instanceProfile(sysOS *sys.OS, inst instance) (string, error) { return "", err } - qemuFwPathsArr, err := util.GetQemuFwPaths() - if err != nil { - return "", err + vmInst, ok := inst.(instanceVM) + if !ok { + return "", fmt.Errorf("Instance is not VM type") + } + + // Get start time firmware path to allow access to it. + firmwarePath := vmInst.FirmwarePath() + if firmwarePath != "" { + firmwarePath, err = filepath.EvalSymlinks(firmwarePath) + if err != nil { + return "", fmt.Errorf("Failed finding firmware: %w", err) + } } execPath := util.GetExecPath() @@ -216,7 +231,7 @@ func instanceProfile(sysOS *sys.OS, inst instance) (string, error) { "rootPath": rootPath, "snap": shared.InSnap(), "userns": sysOS.RunningInUserNS, - "qemuFwPaths": qemuFwPathsArr, + "firmwarePath": firmwarePath, "snapExtQemuPrefix": os.Getenv("SNAP_QEMU_PREFIX"), }) if err != nil { diff --git a/lxd/apparmor/instance_qemu.go b/lxd/apparmor/instance_qemu.go index 1d7b9bed5073..360f656d9996 100644 --- a/lxd/apparmor/instance_qemu.go +++ b/lxd/apparmor/instance_qemu.go @@ -102,13 +102,9 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { {{- end }} {{- end }} -{{if .qemuFwPaths -}} - # Entries from LXD_OVMF_PATH or LXD_QEMU_FW_PATH -{{range $index, $element := .qemuFwPaths}} - {{$element}}/OVMF_CODE.fd kr, - {{$element}}/OVMF_CODE.*.fd kr, - {{$element}}/*bios*.bin kr, -{{- end }} +{{if .firmwarePath -}} + # Firmware path + {{ .firmwarePath }} kr, {{- end }} {{- if .raw }} diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 2e54f1cb0812..e274e801c8bb 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -47,6 +47,7 @@ import ( deviceConfig "github.com/canonical/lxd/lxd/device/config" "github.com/canonical/lxd/lxd/device/nictype" "github.com/canonical/lxd/lxd/instance" + "github.com/canonical/lxd/lxd/instance/drivers/edk2" "github.com/canonical/lxd/lxd/instance/drivers/qmp" "github.com/canonical/lxd/lxd/instance/drivers/uefi" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -107,37 +108,6 @@ const qemuDeviceNameMaxLength = 31 // qemuMigrationNBDExportName is the name of the disk device export by the migration NBD server. const qemuMigrationNBDExportName = "lxd_root" -// VM firmwares. -type vmFirmware struct { - code string - vars string -} - -// Debug version of the "default" firmware. -var vmDebugFirmware = "OVMF_CODE.4MB.debug.fd" - -var vmGenericFirmwares = []vmFirmware{ - {code: "OVMF_CODE.4MB.fd", vars: "OVMF_VARS.4MB.fd"}, - {code: "OVMF_CODE.2MB.fd", vars: "OVMF_VARS.2MB.fd"}, - {code: "OVMF_CODE.fd", vars: "OVMF_VARS.fd"}, - {code: "OVMF_CODE.fd", vars: "qemu.nvram"}, -} - -var vmSecurebootFirmwares = []vmFirmware{ - {code: "OVMF_CODE.4MB.fd", vars: "OVMF_VARS.4MB.ms.fd"}, - {code: "OVMF_CODE.2MB.fd", vars: "OVMF_VARS.2MB.ms.fd"}, - {code: "OVMF_CODE.fd", vars: "OVMF_VARS.ms.fd"}, - {code: "OVMF_CODE.fd", vars: "qemu.nvram"}, -} - -// Only valid for x86_64. -var vmLegacyFirmwares = []vmFirmware{ - {code: "bios-256k.bin", vars: "bios-256k.bin"}, - {code: "OVMF_CODE.4MB.CSM.fd", vars: "OVMF_VARS.4MB.CSM.fd"}, - {code: "OVMF_CODE.2MB.CSM.fd", vars: "OVMF_VARS.2MB.CSM.fd"}, - {code: "OVMF_CODE.CSM.fd", vars: "OVMF_VARS.CSM.fd"}, -} - // qemuSparseUSBPorts is the amount of sparse USB ports for VMs. // 4 are reserved, and the other 4 can be used for any USB device. const qemuSparseUSBPorts = 8 @@ -356,6 +326,9 @@ func qemuCreate(s *state.State, args db.InstanceArgs, p api.Project) (instance.I type qemu struct { common + // Path to firmware, set at start time. + firmwarePath string + // Cached handles. // Do not use these variables directly, instead use their associated get functions so they // will be initialised on demand. @@ -788,29 +761,6 @@ func (d *qemu) Rebuild(img *api.Image, op *operations.Operation) error { return d.rebuildCommon(d, img, op) } -func (*qemu) fwPath(filename string) string { - qemuFwPathsArr, err := util.GetQemuFwPaths() - if err != nil { - return "" - } - - // GetQemuFwPaths resolves symlinks for us, but we still need EvalSymlinks() in here, - // because filename itself can be a symlink. - for _, path := range qemuFwPathsArr { - filePath := filepath.Join(path, filename) - filePath, err := filepath.EvalSymlinks(filePath) - if err != nil { - continue - } - - if shared.PathExists(filePath) { - return filePath - } - } - - return "" -} - // killQemuProcess kills specified process. Optimistically attempts to wait for the process to fully exit, but does // not return an error if the Wait call fails. This is because this function is used in scenarios where LXD has // been restarted after the VM has been started and is no longer the parent of the QEMU process. @@ -1272,7 +1222,7 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error { return err } - // Copy VM firmware settings firmware to nvram file if needed. + // Copy EDK2 settings firmware to nvram file if needed. // This firmware file can be modified by the VM so it must be copied from the defaults. if d.architectureSupportsUEFI(d.architecture) && (!shared.PathExists(d.nvramPath()) || shared.IsTrue(d.localConfig["volatile.apply_nvram"])) { err = d.setupNvram() @@ -1818,6 +1768,11 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error { return nil } +// FirmwarePath returns the path to firmware, set at start time. +func (d *qemu) FirmwarePath() string { + return d.firmwarePath +} + func (d *qemu) setupSEV(fdFiles *[]*os.File) (*qemuSevOpts, error) { if d.architecture != osarch.ARCH_64BIT_INTEL_X86 { return nil, errors.New("AMD SEV support is only available on x86_64 systems") @@ -1984,51 +1939,54 @@ func (d *qemu) setupNvram() error { d.logger.Debug("Generating NVRAM") // Cleanup existing variables. - for _, firmwares := range [][]vmFirmware{vmGenericFirmwares, vmSecurebootFirmwares, vmLegacyFirmwares} { - for _, firmware := range firmwares { - err := os.Remove(filepath.Join(d.Path(), firmware.vars)) - if err != nil && !os.IsNotExist(err) { - return err - } + for _, firmwarePair := range edk2.GetAchitectureFirmwarePairs(d.architecture) { + err := os.Remove(filepath.Join(d.Path(), filepath.Base(firmwarePair.Vars))) + if err != nil && !os.IsNotExist(err) { + return err } } // Determine expected firmware. - firmwares := vmGenericFirmwares + var firmwares []edk2.FirmwarePair if shared.IsTrue(d.expandedConfig["security.csm"]) { - firmwares = vmLegacyFirmwares + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.CSM) } else if shared.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) { - firmwares = vmSecurebootFirmwares + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SECUREBOOT) + } else { + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.GENERIC) } // Find the template file. - var vmfVarsPath string - var vmfVarsName string + var vmFirmwarePath string + var vmFirmwareName string for _, firmware := range firmwares { - varsPath := d.fwPath(firmware.vars) + varsPath, err := filepath.EvalSymlinks(firmware.Vars) + if err != nil { + continue + } - if varsPath != "" { - vmfVarsPath = varsPath - vmfVarsName = firmware.vars + if shared.PathExists(varsPath) { + vmFirmwarePath = varsPath + vmFirmwareName = filepath.Base(firmware.Vars) break } } - if vmfVarsPath == "" { - return fmt.Errorf("Couldn't find one of the required firmware files: %+v", firmwares) + if vmFirmwarePath == "" { + return fmt.Errorf("Couldn't find one of the required VM firmware files: %+v", firmwares) } // Copy the template. - err = shared.FileCopy(vmfVarsPath, filepath.Join(d.Path(), vmfVarsName)) + err = shared.FileCopy(vmFirmwarePath, filepath.Join(d.Path(), vmFirmwareName)) if err != nil { return err } // Generate a symlink if needed. - // This is so qemu.nvram can always be assumed to be the VM firmware vars file. + // This is so qemu.nvram can always be assumed to be the EDK2 vars file. // The real file name is then used to determine what firmware must be selected. if !shared.PathExists(d.nvramPath()) { - err = os.Symlink(vmfVarsName, d.nvramPath()) + err = os.Symlink(vmFirmwareName, d.nvramPath()) if err != nil { return err } @@ -3183,29 +3141,31 @@ func (d *qemu) generateQemuConfigFile(cpuInfo *cpuTopology, mountInfo *storagePo } // Determine expected firmware. - firmwares := vmGenericFirmwares + var firmwares []edk2.FirmwarePair if shared.IsTrue(d.expandedConfig["security.csm"]) { - firmwares = vmLegacyFirmwares + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.CSM) } else if shared.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) { - firmwares = vmSecurebootFirmwares + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SECUREBOOT) + } else { + firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.GENERIC) } - var vmfCode string + var efiCode string for _, firmware := range firmwares { - if shared.PathExists(filepath.Join(d.Path(), firmware.vars)) { - vmfCode = firmware.code + if shared.PathExists(filepath.Join(d.Path(), filepath.Base(firmware.Vars))) { + efiCode = firmware.Code break } } - if vmfCode == "" { - return "", nil, fmt.Errorf("Unable to locate matching firmware: %+v", firmwares) + if efiCode == "" { + return "", nil, fmt.Errorf("Unable to locate matching VM firmware: %+v", firmwares) } // As 2MB firmware was deprecated in the LXD snap we have to regenerate NVRAM for VMs which used the 2MB one. // As EDK2-based CSM firmwares were deprecated in the LXD snap we want to force VMs to start using SeaBIOS directly. - isOVMF2MB := (strings.Contains(vmfCode, "OVMF") && !strings.Contains(vmfCode, "4MB")) - isOVMFCSM := (strings.Contains(vmfCode, "OVMF") && strings.Contains(vmfCode, "CSM")) + isOVMF2MB := (strings.Contains(efiCode, "OVMF") && !strings.Contains(efiCode, "4MB")) + isOVMFCSM := (strings.Contains(efiCode, "OVMF") && strings.Contains(efiCode, "CSM")) if shared.InSnap() && (isOVMF2MB || isOVMFCSM) { err = d.setupNvram() if err != nil { @@ -3213,24 +3173,22 @@ func (d *qemu) generateQemuConfigFile(cpuInfo *cpuTopology, mountInfo *storagePo } // force to use a top-priority firmware - vmfCode = firmwares[0].code - } - - // Use debug version of firmware. (Only works for "default" (4MB, no CSM) firmware flavor) - if shared.IsTrue(d.localConfig["boot.debug_edk2"]) && vmfCode == vmGenericFirmwares[0].code { - vmfCode = vmDebugFirmware + efiCode = firmwares[0].Code } - fwPath := d.fwPath(vmfCode) - if fwPath == "" { - return "", nil, fmt.Errorf("Unable to locate the file for firmware %q", vmfCode) + // Use debug version of firmware. (Only works for "preferred" (OVMF 4MB, no CSM) firmware flavor) + if shared.IsTrue(d.localConfig["boot.debug_edk2"]) && efiCode == firmwares[0].Code { + efiCode = filepath.Join(filepath.Dir(efiCode), edk2.OVMFDebugFirmware) } driveFirmwareOpts := qemuDriveFirmwareOpts{ - roPath: fwPath, + roPath: efiCode, nvramPath: fmt.Sprintf("/dev/fd/%d", d.addFileDescriptor(fdFiles, nvRAMFile)), } + // Set firmware path for apparmor profile. + d.firmwarePath = driveFirmwareOpts.roPath + cfg = append(cfg, qemuDriveFirmware(&driveFirmwareOpts)...) } @@ -8720,18 +8678,21 @@ func (d *qemu) checkFeatures(hostArch int, qemuPath string) (map[string]any, err } if d.architectureSupportsUEFI(hostArch) { - vmfCode := "OVMF_CODE.fd" - - if shared.InSnap() { - vmfCode = vmGenericFirmwares[0].code + // Try to locate a UEFI firmware. + var efiPath string + for _, firmwarePair := range edk2.GetArchitectureFirmwarePairsForUsage(hostArch, edk2.GENERIC) { + if shared.PathExists(firmwarePair.Code) { + logger.Info("Found VM UEFI firmware", logger.Ctx{"code": firmwarePair.Code, "vars": firmwarePair.Vars}) + efiPath = firmwarePair.Code + break + } } - fwPath := d.fwPath(vmfCode) - if fwPath == "" { - return nil, fmt.Errorf("Unable to locate the file for firmware %q", vmfCode) + if efiPath == "" { + return nil, fmt.Errorf("Unable to locate a VM UEFI firmware") } - qemuArgs = append(qemuArgs, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", fwPath)) + qemuArgs = append(qemuArgs, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", efiPath)) } var stderr bytes.Buffer diff --git a/lxd/instance/drivers/edk2/edk2.go b/lxd/instance/drivers/edk2/edk2.go new file mode 100644 index 000000000000..380d4e22eaa4 --- /dev/null +++ b/lxd/instance/drivers/edk2/edk2.go @@ -0,0 +1,202 @@ +package edk2 + +import ( + "os" + "path/filepath" + "strings" + + "github.com/canonical/lxd/shared/osarch" +) + +// FirmwarePair represents a combination of firmware code (Code) and storage (Vars). +type FirmwarePair struct { + Code string + Vars string +} + +// Installation represents a set of available firmware at a given location on the system. +type Installation struct { + Paths []string + Usage map[FirmwareUsage][]FirmwarePair +} + +// FirmwareUsage represents the situation in which a given firmware file will be used. +type FirmwareUsage int + +const ( + // GENERIC is a generic EDK2 firmware. + GENERIC FirmwareUsage = iota + + // SECUREBOOT is a UEFI Secure Boot enabled firmware. + SECUREBOOT + + // CSM is a firmware with the UEFI Compatibility Support Module enabled to boot BIOS-only operating systems. + CSM +) + +// OVMFDebugFirmware is the debug version of the "preferred" firmware. +const OVMFDebugFirmware = "OVMF_CODE.4MB.debug.fd" + +var architectureInstallations = map[int][]Installation{ + osarch.ARCH_64BIT_INTEL_X86: {{ + Paths: GetenvEdk2Paths("/usr/share/OVMF"), + Usage: map[FirmwareUsage][]FirmwarePair{ + GENERIC: { + {Code: "OVMF_CODE.4MB.fd", Vars: "OVMF_VARS.4MB.fd"}, + {Code: "OVMF_CODE_4M.fd", Vars: "OVMF_VARS_4M.fd"}, + {Code: "OVMF_CODE.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.2MB.fd", Vars: "OVMF_VARS.2MB.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.fd"}, + {Code: "OVMF_CODE.fd", Vars: "qemu.nvram"}, + }, + SECUREBOOT: { + {Code: "OVMF_CODE.4MB.fd", Vars: "OVMF_VARS.4MB.ms.fd"}, + {Code: "OVMF_CODE_4M.ms.fd", Vars: "OVMF_VARS_4M.ms.fd"}, + {Code: "OVMF_CODE_4M.secboot.fd", Vars: "OVMF_VARS_4M.secboot.fd"}, + {Code: "OVMF_CODE.secboot.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.secboot.fd", Vars: "OVMF_VARS.secboot.fd"}, + {Code: "OVMF_CODE.secboot.fd", Vars: "OVMF_VARS.fd"}, + {Code: "OVMF_CODE.2MB.fd", Vars: "OVMF_VARS.2MB.ms.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.ms.fd"}, + {Code: "OVMF_CODE.fd", Vars: "qemu.nvram"}, + }, + CSM: { + {Code: "seabios.bin", Vars: "seabios.bin"}, + {Code: "OVMF_CODE.4MB.CSM.fd", Vars: "OVMF_VARS.4MB.CSM.fd"}, + {Code: "OVMF_CODE.csm.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.2MB.CSM.fd", Vars: "OVMF_VARS.2MB.CSM.fd"}, + {Code: "OVMF_CODE.CSM.fd", Vars: "OVMF_VARS.CSM.fd"}, + {Code: "OVMF_CODE.csm.fd", Vars: "OVMF_VARS.fd"}, + }, + }, + }, { + Paths: GetenvEdk2Paths("/usr/share/qemu"), + Usage: map[FirmwareUsage][]FirmwarePair{ + GENERIC: { + {Code: "ovmf-x86_64-4m-code.bin", Vars: "ovmf-x86_64-4m-vars.bin"}, + {Code: "ovmf-x86_64.bin", Vars: "ovmf-x86_64-code.bin"}, + }, + SECUREBOOT: { + {Code: "ovmf-x86_64-ms-4m-vars.bin", Vars: "ovmf-x86_64-ms-4m-code.bin"}, + {Code: "ovmf-x86_64-ms-code.bin", Vars: "ovmf-x86_64-ms-vars.bin"}, + }, + CSM: { + {Code: "seabios.bin", Vars: "seabios.bin"}, + }, + }, + }, { + Paths: GetenvEdk2Paths("/usr/share/edk2/x64"), + Usage: map[FirmwareUsage][]FirmwarePair{ + GENERIC: { + {Code: "OVMF_CODE.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.fd"}, + }, + SECUREBOOT: { + {Code: "OVMF_CODE.secure.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.secure.fd", Vars: "OVMF_VARS.fd"}, + }, + }, + }, { + Paths: GetenvEdk2Paths("/usr/share/OVMF/x64"), + Usage: map[FirmwareUsage][]FirmwarePair{ + GENERIC: { + {Code: "OVMF_CODE.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.fd"}, + }, + CSM: { + {Code: "OVMF_CODE.csm.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.csm.fd", Vars: "OVMF_VARS.fd"}, + }, + SECUREBOOT: { + {Code: "OVMF_CODE.secboot.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.secboot.fd", Vars: "OVMF_VARS.fd"}, + }, + }, + }, { + Paths: GetenvEdk2Paths("/usr/share/seabios"), + Usage: map[FirmwareUsage][]FirmwarePair{ + CSM: { + {Code: "bios-256k.bin", Vars: "bios-256k.bin"}, + }, + }, + }}, + osarch.ARCH_64BIT_ARMV8_LITTLE_ENDIAN: {{ + Paths: GetenvEdk2Paths("/usr/share/AAVMF"), + Usage: map[FirmwareUsage][]FirmwarePair{ + GENERIC: { + {Code: "AAVMF_CODE.fd", Vars: "AAVMF_VARS.fd"}, + {Code: "OVMF_CODE.4MB.fd", Vars: "OVMF_VARS.4MB.fd"}, + {Code: "OVMF_CODE_4M.fd", Vars: "OVMF_VARS_4M.fd"}, + {Code: "OVMF_CODE.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.2MB.fd", Vars: "OVMF_VARS.2MB.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.fd"}, + {Code: "OVMF_CODE.fd", Vars: "qemu.nvram"}, + }, + SECUREBOOT: { + {Code: "AAVMF_CODE.ms.fd", Vars: "AAVMF_VARS.ms.fd"}, + {Code: "OVMF_CODE.4MB.fd", Vars: "OVMF_VARS.4MB.ms.fd"}, + {Code: "OVMF_CODE_4M.ms.fd", Vars: "OVMF_VARS_4M.ms.fd"}, + {Code: "OVMF_CODE_4M.secboot.fd", Vars: "OVMF_VARS_4M.secboot.fd"}, + {Code: "OVMF_CODE.secboot.4m.fd", Vars: "OVMF_VARS.4m.fd"}, + {Code: "OVMF_CODE.secboot.fd", Vars: "OVMF_VARS.secboot.fd"}, + {Code: "OVMF_CODE.secboot.fd", Vars: "OVMF_VARS.fd"}, + {Code: "OVMF_CODE.2MB.fd", Vars: "OVMF_VARS.2MB.ms.fd"}, + {Code: "OVMF_CODE.fd", Vars: "OVMF_VARS.ms.fd"}, + {Code: "OVMF_CODE.fd", Vars: "qemu.nvram"}, + }, + }, + }}, +} + +// GetAchitectureFirmwarePairs creates an array of FirmwarePair for a +// specific host architecture. +func GetAchitectureFirmwarePairs(hostArch int) []FirmwarePair { + firmwares := make([]FirmwarePair, 0) + + for _, usage := range []FirmwareUsage{GENERIC, SECUREBOOT, CSM} { + firmwares = append(firmwares, GetArchitectureFirmwarePairsForUsage(hostArch, usage)...) + } + + return firmwares +} + +// GetArchitectureFirmwarePairsForUsage creates an array of FirmwarePair +// for a specific host architecture and usage combination. +func GetArchitectureFirmwarePairsForUsage(hostArch int, usage FirmwareUsage) []FirmwarePair { + firmwares := make([]FirmwarePair, 0) + + for _, installation := range architectureInstallations[hostArch] { + usage, found := installation.Usage[usage] + if found { + for _, firmwarePair := range usage { + for _, searchPath := range installation.Paths { + firmwares = append(firmwares, FirmwarePair{ + Code: filepath.Join(searchPath, firmwarePair.Code), + Vars: filepath.Join(searchPath, firmwarePair.Vars), + }) + } + } + } + } + + return firmwares +} + +// GetenvEdk2Paths returns a list of paths to search for VM firmwares. +// If LXD_QEMU_FW_PATH or LXD_OVMF_PATH env vars are set then these values are split on ":" and prefixed to the +// returned slice of paths. +// The defaultPath argument is returned as the last element in the slice. +func GetenvEdk2Paths(defaultPath string) []string { + var qemuFwPaths []string + + for _, v := range []string{"LXD_QEMU_FW_PATH", "LXD_OVMF_PATH"} { + searchPaths := os.Getenv(v) + if searchPaths == "" { + continue + } + + qemuFwPaths = append(qemuFwPaths, strings.Split(searchPaths, ":")...) + } + + return append(qemuFwPaths, defaultPath) +} diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go index 8763436848d0..91a057240e29 100644 --- a/lxd/instance/instance_interface.go +++ b/lxd/instance/instance_interface.go @@ -193,6 +193,8 @@ type VM interface { AgentCertificate() *x509.Certificate + FirmwarePath() string + // UEFI vars handling. UEFIVars() (*api.InstanceUEFIVars, error) UEFIVarsUpdate(newUEFIVarsSet api.InstanceUEFIVars) error diff --git a/lxd/util/sys.go b/lxd/util/sys.go index 606ece092ae9..0be5f9934891 100644 --- a/lxd/util/sys.go +++ b/lxd/util/sys.go @@ -3,9 +3,7 @@ package util import ( - "fmt" "os" - "path/filepath" "strings" "golang.org/x/sys/unix" @@ -65,40 +63,3 @@ func ReplaceDaemon() error { return nil } - -// GetQemuFwPaths returns a list of directory paths to search for QEMU firmware files. -func GetQemuFwPaths() ([]string, error) { - var qemuFwPaths []string - - for _, v := range []string{"LXD_QEMU_FW_PATH", "LXD_OVMF_PATH"} { - searchPaths := os.Getenv(v) - if searchPaths == "" { - continue - } - - qemuFwPaths = append(qemuFwPaths, strings.Split(searchPaths, ":")...) - } - - // Append default paths after ones extracted from env vars so they take precedence. - qemuFwPaths = append(qemuFwPaths, "/usr/share/OVMF", "/usr/share/seabios") - - count := 0 - for i, path := range qemuFwPaths { - var err error - resolvedPath, err := filepath.EvalSymlinks(path) - if err != nil { - // don't fail, just skip as some search paths can be optional - continue - } - - count++ - qemuFwPaths[i] = resolvedPath - } - - // We want to have at least one valid path to search for firmware. - if count == 0 { - return nil, fmt.Errorf("Failed to find a valid search path for firmware") - } - - return qemuFwPaths, nil -}