|
6 | 6 | "encoding/json"
|
7 | 7 | "errors"
|
8 | 8 | "fmt"
|
| 9 | + "io" |
9 | 10 | "io/fs"
|
10 | 11 | "os"
|
11 | 12 | "os/exec"
|
@@ -598,36 +599,89 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
|
598 | 599 | }
|
599 | 600 | if !legacyBIOS {
|
600 | 601 | var firmware string
|
| 602 | + firmwareInBios := runtime.GOOS == "windows" |
| 603 | + if envVar := os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"); envVar != "" { |
| 604 | + b, err := strconv.ParseBool(os.Getenv("LIMA_QEMU_UEFI_IN_BIOS")) |
| 605 | + if err != nil { |
| 606 | + logrus.WithError(err).Warnf("invalid LIMA_QEMU_UEFI_IN_BIOS value %q", envVar) |
| 607 | + } else { |
| 608 | + firmwareInBios = b |
| 609 | + } |
| 610 | + } |
| 611 | + firmwareInBios = firmwareInBios && *y.Arch == limayaml.X8664 |
601 | 612 | downloadedFirmware := filepath.Join(cfg.InstanceDir, filenames.QemuEfiCodeFD)
|
602 |
| - if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) { |
603 |
| - loop: |
604 |
| - for _, f := range y.Firmware.Images { |
605 |
| - switch f.VMType { |
606 |
| - case "", limayaml.QEMU: |
607 |
| - if f.Arch == *y.Arch { |
608 |
| - if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil { |
609 |
| - logrus.WithError(err).Warnf("failed to download %q", f.Location) |
610 |
| - continue loop |
| 613 | + firmwareWithVars := filepath.Join(cfg.InstanceDir, filenames.QemuEfiFullFD) |
| 614 | + if firmwareInBios { |
| 615 | + if _, stErr := os.Stat(firmwareWithVars); stErr == nil { |
| 616 | + firmware = firmwareWithVars |
| 617 | + logrus.Infof("Using existing firmware (%q)", firmware) |
| 618 | + } |
| 619 | + } else { |
| 620 | + if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) { |
| 621 | + loop: |
| 622 | + for _, f := range y.Firmware.Images { |
| 623 | + switch f.VMType { |
| 624 | + case "", limayaml.QEMU: |
| 625 | + if f.Arch == *y.Arch { |
| 626 | + if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil { |
| 627 | + logrus.WithError(err).Warnf("failed to download %q", f.Location) |
| 628 | + continue loop |
| 629 | + } |
| 630 | + firmware = downloadedFirmware |
| 631 | + logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location) |
| 632 | + break loop |
611 | 633 | }
|
612 |
| - firmware = downloadedFirmware |
613 |
| - logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location) |
614 |
| - break loop |
615 | 634 | }
|
616 | 635 | }
|
| 636 | + } else { |
| 637 | + firmware = downloadedFirmware |
| 638 | + logrus.Infof("Using existing firmware (%q)", firmware) |
617 | 639 | }
|
618 |
| - } else { |
619 |
| - firmware = downloadedFirmware |
620 |
| - logrus.Infof("Using existing firmware (%q)", firmware) |
621 | 640 | }
|
622 | 641 | if firmware == "" {
|
623 | 642 | firmware, err = getFirmware(exe, *y.Arch)
|
624 | 643 | if err != nil {
|
625 | 644 | return "", nil, err
|
626 | 645 | }
|
627 | 646 | logrus.Infof("Using system firmware (%q)", firmware)
|
| 647 | + if firmwareInBios { |
| 648 | + firmwareVars, err := getFirmwareVars(exe, *y.Arch) |
| 649 | + if err != nil { |
| 650 | + return "", nil, err |
| 651 | + } |
| 652 | + logrus.Infof("Using system firmware vars (%q)", firmwareVars) |
| 653 | + varsFile, err := os.Open(firmwareVars) |
| 654 | + if err != nil { |
| 655 | + return "", nil, err |
| 656 | + } |
| 657 | + defer varsFile.Close() |
| 658 | + codeFile, err := os.Open(firmware) |
| 659 | + if err != nil { |
| 660 | + return "", nil, err |
| 661 | + } |
| 662 | + defer codeFile.Close() |
| 663 | + resultFile, err := os.OpenFile(firmwareWithVars, os.O_CREATE|os.O_WRONLY, 0o644) |
| 664 | + if err != nil { |
| 665 | + return "", nil, err |
| 666 | + } |
| 667 | + defer resultFile.Close() |
| 668 | + _, err = io.Copy(resultFile, varsFile) |
| 669 | + if err != nil { |
| 670 | + return "", nil, err |
| 671 | + } |
| 672 | + _, err = io.Copy(resultFile, codeFile) |
| 673 | + if err != nil { |
| 674 | + return "", nil, err |
| 675 | + } |
| 676 | + firmware = firmwareWithVars |
| 677 | + } |
628 | 678 | }
|
629 | 679 | if firmware != "" {
|
630 |
| - args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware)) |
| 680 | + if firmwareInBios { |
| 681 | + args = append(args, "-bios", firmware) |
| 682 | + } else { |
| 683 | + args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware)) |
| 684 | + } |
631 | 685 | }
|
632 | 686 | }
|
633 | 687 |
|
@@ -1121,9 +1175,11 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
|
1121 | 1175 | userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
|
1122 | 1176 |
|
1123 | 1177 | relativePath := fmt.Sprintf("share/qemu/edk2-%s-code.fd", qemuEdk2Arch(arch))
|
| 1178 | + relativePathWin := fmt.Sprintf("share/edk2-%s-code.fd", qemuEdk2Arch(arch)) |
1124 | 1179 | candidates := []string{
|
1125 | 1180 | filepath.Join(userLocalDir, relativePath), // XDG-like
|
1126 | 1181 | filepath.Join(localDir, relativePath), // macOS (homebrew)
|
| 1182 | + filepath.Join(binDir, relativePathWin), // Windows installer |
1127 | 1183 | }
|
1128 | 1184 |
|
1129 | 1185 | switch arch {
|
@@ -1165,3 +1221,40 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
|
1165 | 1221 | qemuArch := strings.TrimPrefix(filepath.Base(qemuExe), "qemu-system-")
|
1166 | 1222 | return "", fmt.Errorf("could not find firmware for %q (hint: try copying the \"edk-%s-code.fd\" firmware to $HOME/.local/share/qemu/)", arch, qemuArch)
|
1167 | 1223 | }
|
| 1224 | + |
| 1225 | +func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) { |
| 1226 | + var targetArch string |
| 1227 | + switch arch { |
| 1228 | + case limayaml.X8664: |
| 1229 | + targetArch = "i386" // vars are unified between i386 and x86_64 and normally only former is bundled |
| 1230 | + default: |
| 1231 | + return "", fmt.Errorf("unexpected architecture: %q", arch) |
| 1232 | + } |
| 1233 | + |
| 1234 | + currentUser, err := user.Current() |
| 1235 | + if err != nil { |
| 1236 | + return "", err |
| 1237 | + } |
| 1238 | + |
| 1239 | + binDir := filepath.Dir(qemuExe) // "/usr/local/bin" |
| 1240 | + localDir := filepath.Dir(binDir) // "/usr/local" |
| 1241 | + userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local" |
| 1242 | + |
| 1243 | + relativePath := fmt.Sprintf("share/qemu/edk2-%s-vars.fd", qemuEdk2Arch(targetArch)) |
| 1244 | + relativePathWin := fmt.Sprintf("share/edk2-%s-vars.fd", qemuEdk2Arch(targetArch)) |
| 1245 | + candidates := []string{ |
| 1246 | + filepath.Join(userLocalDir, relativePath), // XDG-like |
| 1247 | + filepath.Join(localDir, relativePath), // macOS (homebrew) |
| 1248 | + filepath.Join(binDir, relativePathWin), // Windows installer |
| 1249 | + } |
| 1250 | + |
| 1251 | + logrus.Debugf("firmware vars candidates = %v", candidates) |
| 1252 | + |
| 1253 | + for _, f := range candidates { |
| 1254 | + if _, err := os.Stat(f); err == nil { |
| 1255 | + return f, nil |
| 1256 | + } |
| 1257 | + } |
| 1258 | + |
| 1259 | + return "", fmt.Errorf("could not find firmware vars for %q", arch) |
| 1260 | +} |
0 commit comments