1
0
mirror of https://github.com/containers/podman.git synced 2026-02-05 06:45:31 +01:00

Fix WSL installation check on Windows

Fixes #25234

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
This commit is contained in:
Mario Loriedo
2025-03-10 17:56:35 +01:00
parent 07c1f0b996
commit 87421d9508
4 changed files with 219 additions and 31 deletions

View File

@@ -652,7 +652,7 @@ localunit: test/goecho/goecho test/version/version
UNIT=1 $(GINKGO) \
-r \
$(TESTFLAGS) \
--skip-package test/e2e,pkg/bindings,hack,pkg/machine/e2e \
--skip-package test/e2e,pkg/bindings,hack,pkg/machine/e2e,pkg/machine/wsl \
--cover \
--covermode atomic \
--coverprofile coverprofile \

View File

@@ -317,12 +317,12 @@ func checkAndInstallWSL(reExec bool) (bool, error) {
admin := HasAdminRights()
if !IsWSLFeatureEnabled() {
if !wutil.IsWSLFeatureEnabled() {
return false, attemptFeatureInstall(reExec, admin)
}
skip := false
if reExec && !admin {
if !reExec && !admin {
fmt.Println("Launching WSL Kernel Install...")
if err := launchElevate(wslInstallKernel); err != nil {
return false, err
@@ -363,11 +363,11 @@ func attemptFeatureInstall(reExec, admin bool) error {
message += "NOTE: A system reboot will be required as part of this process. " +
"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 {
if !reExec && MessageBox(message, "Podman Machine", false) != 1 {
return errors.New("the WSL installation aborted")
}
if reExec && !admin {
if !reExec && !admin {
return launchElevate("install the Windows WSL Features")
}
@@ -622,10 +622,6 @@ func obtainGlobalConfigLock() (*fileLock, error) {
return lockFile(filepath.Join(lockDir, "podman-config.lck"))
}
func IsWSLFeatureEnabled() bool {
return wutil.SilentExec(wutil.FindWSL(), "--set-default-version", "2") == nil
}
func isWSLRunning(dist string) (bool, error) {
return wslCheckExists(dist, true)
}

View File

@@ -19,16 +19,26 @@ import (
)
var (
once sync.Once
wslPath string
onceFind, onceStatus sync.Once
wslPath string
status wslStatus
wslNotInstalledMessages = []string{"kernel file is not found", "The Windows Subsystem for Linux is not installed"}
vmpDisabledMessages = []string{"enable the Virtual Machine Platform Windows feature", "Enable \"Virtual Machine Platform\""}
wslDisabledMessages = []string{"enable the \"Windows Subsystem for Linux\" optional component"}
)
type wslStatus struct {
installed bool
vmpFeatureEnabled bool
wslFeatureEnabled bool
}
func FindWSL() string {
// At the time of this writing, a defect appeared in the OS preinstalled WSL executable
// where it no longer reliably locates the preferred Windows App Store variant.
//
// Manually discover (and cache) the wsl.exe location to bypass the problem
once.Do(func() {
onceFind.Do(func() {
var locs []string
// Prefer Windows App Store version
@@ -87,24 +97,44 @@ func SilentExecCmd(command string, args ...string) *exec.Cmd {
return cmd
}
func parseWSLStatus() wslStatus {
onceStatus.Do(func() {
status = wslStatus{
installed: false,
vmpFeatureEnabled: false,
wslFeatureEnabled: false,
}
cmd := SilentExecCmd(FindWSL(), "--status")
out, err := cmd.StdoutPipe()
cmd.Stderr = nil
if err != nil {
return
}
if err = cmd.Start(); err != nil {
return
}
status = matchOutputLine(out)
if err := cmd.Wait(); err != nil {
return
}
})
return status
}
func IsWSLInstalled() bool {
cmd := SilentExecCmd(FindWSL(), "--status")
out, err := cmd.StdoutPipe()
cmd.Stderr = nil
if err != nil {
status := parseWSLStatus()
return status.installed && status.vmpFeatureEnabled
}
func IsWSLFeatureEnabled() bool {
if SilentExec(FindWSL(), "--set-default-version", "2") != nil {
return false
}
if err = cmd.Start(); err != nil {
return false
}
kernelNotFound := matchOutputLine(out, "kernel file is not found")
if err := cmd.Wait(); err != nil {
return false
}
return !kernelNotFound
status := parseWSLStatus()
return status.vmpFeatureEnabled
}
func IsWSLStoreVersionInstalled() bool {
@@ -118,13 +148,30 @@ func IsWSLStoreVersionInstalled() bool {
return true
}
func matchOutputLine(output io.ReadCloser, match string) bool {
func matchOutputLine(output io.ReadCloser) wslStatus {
status := wslStatus{
installed: true,
vmpFeatureEnabled: true,
wslFeatureEnabled: true,
}
scanner := bufio.NewScanner(transform.NewReader(output, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, match) {
return true
for _, match := range wslNotInstalledMessages {
if strings.Contains(line, match) {
status.installed = false
}
}
for _, match := range vmpDisabledMessages {
if strings.Contains(line, match) {
status.vmpFeatureEnabled = false
}
}
for _, match := range wslDisabledMessages {
if strings.Contains(line, match) {
status.wslFeatureEnabled = false
}
}
}
return false
return status
}

View File

@@ -0,0 +1,145 @@
//go:build windows
package wutil
import (
"github.com/stretchr/testify/assert"
"golang.org/x/text/encoding/unicode"
"io"
"strings"
"testing"
)
const (
WSL1InstalledWithWSLAndVMPEnabled = `Default Version: 1`
WSL2InstalledWithWSLAndVMPEnabled = `Default Version: 2`
WSL1NotInstalled = `Default Version: 1
The Windows Subsystem for Linux kernel can be manually updated with 'wsl --update', but automatic updates cannot occur due to your system settings.
To receive automatic kernel updates, please enable the Windows Update setting: 'Receive updates for other Microsoft products when you update Windows'.
For more information please visit https://aka.ms/wsl2kernel.
The WSL 2 kernel file is not found. To update or restore the kernel please run 'wsl --update'.`
WSL2NotInstalled = `The Windows Subsystem for Linux is not installed. You can install by running 'wsl.exe --install'.
For more information please visit https://aka.ms/wslinstall`
WSL2InstalledWithWSLDisabled = `Default Version: 2
WSL1 is not supported with your current machine configuration.
Please enable the "Windows Subsystem for Linux" optional component to use WSL1.`
WSL2InstalledWithVMPDisabled = `Default Version: 2
WSL2 is not supported with your current machine configuration.
Please enable the "Virtual Machine Platform" optional component and ensure virtualization is enabled in the BIOS.
Enable "Virtual Machine Platform" by running: wsl.exe --install --no-distribution
For information please visit https://aka.ms/enablevirtualization`
WSL2InstalledWithWSLAndVMPDisabled = `Default Version: 2
WSL1 is not supported with your current machine configuration.
Please enable the "Windows Subsystem for Linux" optional component to use WSL1.
WSL2 is not supported with your current machine configuration.
Please enable the "Virtual Machine Platform" optional component and ensure virtualization is enabled in the BIOS.
Enable "Virtual Machine Platform" by running: wsl.exe --install --no-distribution
For information please visit https://aka.ms/enablevirtualization`
WSL1InstalledWithVMPDisabled = `Default Version: 1
Please enable the Virtual Machine Platform Windows feature and ensure virtualization is enabled in the BIOS.
For information please visit https://aka.ms/enablevirtualization`
WSL1InstalledWithWSLDisabled = `Default Version: 1
WSL1 is not supported with your current machine configuration.
Please enable the "Windows Subsystem for Linux" optional component to use WSL1.`
)
func TestMatchOutputLine(t *testing.T) {
tests := []struct {
winVariant string
statusOutput string
want wslStatus
}{
{
"WSL1 configured and both Virtual Machine Platform enabled and Windows Subsystem for Linux are enabled",
WSL1InstalledWithWSLAndVMPEnabled,
wslStatus{
installed: true,
vmpFeatureEnabled: true,
wslFeatureEnabled: true,
},
},
{
"WSL2 configured and both Virtual Machine Platform enabled and Windows Subsystem for Linux enabled",
WSL2InstalledWithWSLAndVMPEnabled,
wslStatus{
installed: true,
vmpFeatureEnabled: true,
wslFeatureEnabled: true,
},
},
{
"WSL not installed (was previously configured as version 1)",
WSL1NotInstalled,
wslStatus{
installed: false,
vmpFeatureEnabled: true,
wslFeatureEnabled: true,
},
},
{
"WSL not installed (was previously configured as version 2)",
WSL2NotInstalled,
wslStatus{
installed: false,
vmpFeatureEnabled: true,
wslFeatureEnabled: true,
},
},
{
"WSL2 configured and Virtual Machine Platform is enabled but Windows Subsystem for Linux is disabled",
WSL2InstalledWithWSLDisabled,
wslStatus{
installed: true,
vmpFeatureEnabled: true,
wslFeatureEnabled: false,
},
},
{
"WSL2 configured and Virtual Machine Platform is disabled but Windows Subsystem for Linux is enabled",
WSL2InstalledWithVMPDisabled,
wslStatus{
installed: true,
vmpFeatureEnabled: false,
wslFeatureEnabled: true,
},
},
{
"WSL2 configured and both Virtual Machine Platform and Windows Subsystem for Linux are disabled",
WSL2InstalledWithWSLAndVMPDisabled,
wslStatus{
installed: true,
vmpFeatureEnabled: false,
wslFeatureEnabled: false,
},
},
{
"WSL1 configured and Virtual Machine Platform is disabled but Windows Subsystem for Linux is enabled",
WSL1InstalledWithVMPDisabled,
wslStatus{
installed: true,
vmpFeatureEnabled: false,
wslFeatureEnabled: true,
},
},
{
"WSL1 configured and Virtual Machine Platform is enabled but Windows Subsystem for Linux is disabled",
WSL1InstalledWithWSLDisabled,
wslStatus{
installed: true,
vmpFeatureEnabled: true,
wslFeatureEnabled: false,
},
},
}
for _, tt := range tests {
t.Run(tt.winVariant, func(t *testing.T) {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewEncoder()
encodedOutput, err := encoder.String(tt.statusOutput)
assert.Nil(t, err)
reader := io.NopCloser(strings.NewReader(encodedOutput))
assert.Equal(t, tt.want, matchOutputLine(reader))
})
}
}