diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 75cfa61da48..2b3277d3dae 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -3,6 +3,7 @@ package machine import ( + "errors" "fmt" "os" @@ -232,6 +233,14 @@ func initMachine(cmd *cobra.Command, args []string) error { err = shim.Init(initOpts, provider) if err != nil { + // The installation is partially complete and podman should + // exit gracefully with no error and no success message. + // Examples: + // - a user has chosen to perform their own reboot + // - reexec for limited admin operations, returning to parent + if errors.Is(err, define.ErrInitRelaunchAttempt) { + return nil + } return err } diff --git a/pkg/machine/define/errors.go b/pkg/machine/define/errors.go index 5b241c7566c..6252e892548 100644 --- a/pkg/machine/define/errors.go +++ b/pkg/machine/define/errors.go @@ -8,9 +8,11 @@ import ( ) var ( - ErrWrongState = errors.New("VM in wrong state to perform action") - ErrVMAlreadyExists = errors.New("VM already exists") - ErrNotImplemented = errors.New("functionality not implemented") + ErrWrongState = errors.New("VM in wrong state to perform action") + ErrVMAlreadyExists = errors.New("VM already exists") + ErrNotImplemented = errors.New("functionality not implemented") + ErrInitRelaunchAttempt = errors.New("stopping execution: 'init' relaunched with --reexec flag to reinitialize the VM") + ErrRebootInitiated = errors.New("system reboot initiated") ) type ErrVMRunningCannotDestroyed struct { diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go index 198d63f76f1..4ebc41cf734 100644 --- a/pkg/machine/shim/host.go +++ b/pkg/machine/shim/host.go @@ -100,8 +100,12 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error { if err != nil { return err } - machineLock.Lock() - defer machineLock.Unlock() + + // If the machine is being re-launched, the lock is already held + if !opts.ReExec { + machineLock.Lock() + defer machineLock.Unlock() + } mc, err := vmconfigs.NewMachineConfig(opts, dirs, sshIdentityPath, mp.VMType(), machineLock) if err != nil { @@ -111,8 +115,9 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error { mc.Version = vmconfigs.MachineConfigVersion createOpts := machineDefine.CreateVMOpts{ - Name: opts.Name, - Dirs: dirs, + Name: opts.Name, + Dirs: dirs, + ReExec: opts.ReExec, } if umn := opts.UserModeNetworking; umn != nil { @@ -264,6 +269,14 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error { } ignBuilder.WithUnit(readyUnit) + // CreateVM could cause the init command to be re-launched in some cases (e.g. wsl) + // so we need to avoid creating the machine config or connections before this check happens. + // when relaunching, the invoked 'init' command will be responsible to set up the machine + err = mp.CreateVM(createOpts, mc, &ignBuilder) + if err != nil { + return err + } + // TODO AddSSHConnectionToPodmanSocket could take an machineconfig instead if err := connection.AddSSHConnectionsToPodmanSocket(mc.HostUser.UID, mc.SSH.Port, mc.SSH.IdentityPath, mc.Name, mc.SSH.RemoteUsername, opts); err != nil { return err @@ -278,11 +291,6 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error { } callbackFuncs.Add(cleanup) - err = mp.CreateVM(createOpts, mc, &ignBuilder) - if err != nil { - return err - } - if len(opts.IgnitionPath) == 0 { if err := ignBuilder.Build(); err != nil { return err diff --git a/pkg/machine/wsl/declares.go b/pkg/machine/wsl/declares.go index d801eb6f362..fbafe1f390c 100644 --- a/pkg/machine/wsl/declares.go +++ b/pkg/machine/wsl/declares.go @@ -187,17 +187,6 @@ http://docs.microsoft.com/en-us/windows/wsl/install ` -const wslKernelError = `Could not %s. See previous output for any potential failure details. -If you can not resolve the issue, try rerunning the "podman machine init command". If that fails -try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails, -try following the steps outlined in the following article: - -http://docs.microsoft.com/en-us/windows/wsl/install - -` - -const wslInstallKernel = "install the WSL Kernel" - const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows Either update to Build 19041 (or later), or perform the manual installation steps outlined in the following article: diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index d840ed3193f..8d8352e8f06 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -13,7 +13,6 @@ import ( "path/filepath" "strconv" "strings" - "time" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/strongunits" @@ -22,7 +21,6 @@ import ( "github.com/containers/podman/v5/pkg/machine/env" "github.com/containers/podman/v5/pkg/machine/ignition" "github.com/containers/podman/v5/pkg/machine/vmconfigs" - "github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/podman/v5/utils" "github.com/containers/storage/pkg/homedir" "github.com/sirupsen/logrus" @@ -32,7 +30,8 @@ import ( var ( // vmtype refers to qemu (vs libvirt, krun, etc) - vmtype = define.WSLVirt + vmtype = define.WSLVirt + ErrWslNotSupported = errors.New("wsl features not supported or configured correctly") ) type ExitCodeError struct { @@ -95,7 +94,26 @@ func provisionWSLDist(name string, imagePath string, prompt string) (string, err dist := env.WithPodmanPrefix(name) fmt.Println(prompt) - if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil { + + // Run WSL import and analyze output for specific errors. + // If the 'Virtual Machine Platform' feature is disabled, we expect a failure + // with HCS service-related errors such as: + // 1. Wsl/Service/RegisterDistro/CreateVm/HCS/ERROR_NOT_SUPPORTED + // 2. Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE + cmdOutput := &bytes.Buffer{} + err = runCmdPassThroughTee(cmdOutput, "wsl", "--import", dist, distTarget, imagePath, "--version", "2") + decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder() + decoded, _, decodeErr := transform.Bytes(decoder, cmdOutput.Bytes()) + if decodeErr != nil { + return "", fmt.Errorf("failed to decode WSL output: %w", decodeErr) + } + decodedStr := strings.ToLower(string(decoded)) + for _, substr := range []string{"hcs/error_not_supported", "hcs/hcs_e_service_not_available"} { + if strings.Contains(decodedStr, substr) { + return "", ErrWslNotSupported + } + } + if err != nil { return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) } @@ -310,41 +328,6 @@ func writeWslConf(dist string, user string) error { return nil } -func checkAndInstallWSL(reExec bool) (bool, error) { - if wutil.IsWSLInstalled() { - return true, nil - } - - admin := HasAdminRights() - - if !wutil.IsWSLFeatureEnabled() { - return false, attemptFeatureInstall(reExec, admin) - } - - skip := false - if !reExec && !admin { - fmt.Println("Launching WSL Kernel Install...") - if err := launchElevate(wslInstallKernel); err != nil { - return false, err - } - - skip = true - } - - if !skip { - if err := installWslKernel(); err != nil { - fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel) - return false, err - } - - if reExec { - return false, nil - } - } - - return true, nil -} - func attemptFeatureInstall(reExec, admin bool) error { if !winVersionAtLeast(10, 0, 18362) { return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later") @@ -364,18 +347,17 @@ func attemptFeatureInstall(reExec, admin bool) error { "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command." if !reExec && MessageBox(message, "Podman Machine", false) != 1 { - return errors.New("the WSL installation aborted") + return fmt.Errorf("the WSL installation aborted: %w", define.ErrInitRelaunchAttempt) } if !reExec && !admin { return launchElevate("install the Windows WSL Features") } - return installWsl() } func launchElevate(operation string) error { - if err := truncateElevatedOutputFile(); err != nil { + if err := createOrTruncateElevatedOutputFile(); err != nil { return err } err := relaunchElevatedWait() @@ -383,15 +365,16 @@ func launchElevate(operation string) error { if eerr, ok := err.(*ExitCodeError); ok { if eerr.code == ErrorSuccessRebootRequired { fmt.Println("Reboot is required to continue installation, please reboot at your convenience") - return nil + return define.ErrInitRelaunchAttempt } } fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err) dumpOutputFile() fmt.Fprintf(os.Stderr, wslInstallError, operation) + return fmt.Errorf("%w: %w", err, define.ErrInitRelaunchAttempt) } - return err + return define.ErrInitRelaunchAttempt } func installWsl() error { @@ -409,44 +392,10 @@ func installWsl() error { "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) { return fmt.Errorf("could not enable Virtual Machine Feature: %w", err) } - log.Close() return reboot() } -func installWslKernel() error { - log, err := getElevatedOutputFileWrite() - if err != nil { - return err - } - defer log.Close() - - message := "Installing WSL Kernel Update" - fmt.Println(message) - fmt.Fprintln(log, message) - - backoff := 500 * time.Millisecond - for i := 0; i < 5; i++ { - err = runCmdPassThroughTee(log, "wsl", "--update") - if err == nil { - break - } - // In case of unusual circumstances (e.g. race with installer actions) - // retry a few times - message = "An error occurred attempting the WSL Kernel update, retrying..." - fmt.Println(message) - fmt.Fprintln(log, message) - time.Sleep(backoff) - backoff *= 2 - } - - if err != nil { - return fmt.Errorf("could not install WSL Kernel: %w", err) - } - - return nil -} - func getElevatedOutputFileName() (string, error) { dir, err := homedir.GetDataHome() if err != nil { @@ -473,24 +422,14 @@ func getElevatedOutputFileWrite() (*os.File, error) { return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND) } -func appendOutputIfError(write bool, err error) { - if write && err == nil { - return - } - - if file, check := getElevatedOutputFileWrite(); check == nil { - defer file.Close() - fmt.Fprintf(file, "Error: %v\n", err) - } -} - -func truncateElevatedOutputFile() error { +func createOrTruncateElevatedOutputFile() error { name, err := getElevatedOutputFileName() if err != nil { return err } - return os.Truncate(name, 0) + _, err = os.Create(name) + return err } func getElevatedOutputFile(mode int) (*os.File, error) { @@ -572,7 +511,7 @@ func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error { cmd.Stdin = os.Stdin cmd.Stdout = io.MultiWriter(os.Stdout, out) cmd.Stderr = io.MultiWriter(os.Stderr, out) - if err := cmd.Run(); err != nil { + if err := cmd.Run(); isMsiError(err) { return fmt.Errorf("command %s %v failed: %w", name, arg, err) } return nil diff --git a/pkg/machine/wsl/stubber.go b/pkg/machine/wsl/stubber.go index 9759512cbe8..6f6720c5dca 100644 --- a/pkg/machine/wsl/stubber.go +++ b/pkg/machine/wsl/stubber.go @@ -34,11 +34,6 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf go callbackFuncs.CleanOnSignal() mc.WSLHypervisor = new(vmconfigs.WSLConfig) - if cont, err := checkAndInstallWSL(opts.ReExec); !cont { - appendOutputIfError(opts.ReExec, err) - return err - } - _ = setupWslProxyEnv() if opts.UserModeNetworking { @@ -51,6 +46,15 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..." dist, err := provisionWSLDist(mc.Name, mc.ImagePath.GetPath(), prompt) if err != nil { + if errors.Is(err, ErrWslNotSupported) { + // If error is Wsl/Service/RegisterDistro/CreateVm/HCS/ERROR_NOT_SUPPORTED + // or Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE + // it means WSL's VM creation failed, likely due to virtualization features not being enabled. + // Relaunching 'podman machine init' in elevated mode will attempt to reconfigure the WSL machine. + admin := HasAdminRights() + + return attemptFeatureInstall(opts.ReExec, admin) + } return err } diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go index a8efb3c7ab0..08d602d3cbc 100644 --- a/pkg/machine/wsl/util_windows.go +++ b/pkg/machine/wsl/util_windows.go @@ -16,6 +16,7 @@ import ( "unsafe" "github.com/Microsoft/go-winio" + "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/storage/pkg/fileutils" "github.com/containers/storage/pkg/homedir" "github.com/sirupsen/logrus" @@ -216,6 +217,10 @@ func reboot() error { } } + if err := addRunOnceRegistryEntry(command); err != nil { + return err + } + message := "To continue the process of enabling WSL, the system needs to reboot. " + "Alternatively, you can cancel and reboot manually\n\n" + "After rebooting, please wait a minute or two for podman machine to relaunch and continue installing." @@ -226,10 +231,6 @@ func reboot() error { return nil } - if err := addRunOnceRegistryEntry(command); err != nil { - return err - } - if err := winio.RunWithPrivilege(rebootPrivilege, func() error { if err := windows.ExitWindowsEx(rebootFlags, rebootReason); err != nil { return fmt.Errorf("execute ExitWindowsEx to reboot system failed: %w", err) @@ -239,7 +240,7 @@ func reboot() error { return fmt.Errorf("cannot reboot system: %w", err) } - return nil + return define.ErrRebootInitiated } func addRunOnceRegistryEntry(command string) error { diff --git a/pkg/machine/wsl/wutil/wutil.go b/pkg/machine/wsl/wutil/wutil.go index b452d691d13..b65ab7bcb2b 100644 --- a/pkg/machine/wsl/wutil/wutil.go +++ b/pkg/machine/wsl/wutil/wutil.go @@ -78,14 +78,6 @@ func IsWSLInstalled() bool { return status.installed && status.vmpFeatureEnabled } -func IsWSLFeatureEnabled() bool { - if SilentExec("wsl", "--set-default-version", "2") != nil { - return false - } - status := parseWSLStatus() - return status.vmpFeatureEnabled -} - func IsWSLStoreVersionInstalled() bool { cmd := SilentExecCmd("wsl", "--version") cmd.Stdout = nil