mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
Driver: WSL2: Better handle no distributions
This fixes the case where WSL is installed, but no distributions are installed at all. In that case, newer versions of WSL no longer prints the error code `Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND` (or indeed, any error code at all). Instead of trying to parse error messages, use the WSL API to check if the distribution exists. However, the API is very limited and does not allow us to check the state of the distribution in the case it _does_ exist, so we still need to parse the text for that. If WSL is not installed, using the WSL API causes a command prompt window to appear with a prompt to install WSL. To avoid showing that, try to detect whether WSL is installed by opening its registry key. Signed-off-by: Mark Yen <mark.yen@suse.com> Signed-off-by: Jan Dubois <jan.dubois@suse.com>
This commit is contained in:
1 parent
b1fab39fd9
commit
bee804c485
2 files changed
+83
-22
No files matched your search
@@ -12,15 +12,24 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/executil"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
|
||||
"github.com/lima-vm/lima/v2/pkg/textutil"
|
||||
limawindows "github.com/lima-vm/lima/v2/pkg/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
wslAPI = windows.NewLazySystemDLL("api-ms-win-wsl-api-l1-1-0.dll")
|
||||
wslIsDistributionRegistered = wslAPI.NewProc("WslIsDistributionRegistered")
|
||||
)
|
||||
|
||||
// startVM calls WSL to start a VM.
|
||||
@@ -197,7 +206,6 @@ func unregisterVM(ctx context.Context, distroName string) error {
|
||||
//
|
||||
// Distributions can also be installed by visiting the Microsoft Store:
|
||||
// https://aka.ms/wslstore
|
||||
// Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND
|
||||
//
|
||||
// (3) Expected output when no distros are installed, and WSL2 has no kernel installed:
|
||||
//
|
||||
@@ -206,7 +214,39 @@ func unregisterVM(ctx context.Context, distroName string) error {
|
||||
// Distributions can be installed by visiting the Microsoft Store:
|
||||
// https://aka.ms/wslstore
|
||||
func getWslStatus(ctx context.Context, instName string) (string, error) {
|
||||
errWSLNotInstalled := fmt.Errorf(
|
||||
"failed to read instance state for instance %#q because WSL is not installed, "+
|
||||
"try running `wsl --update` and then re-running Lima", instName)
|
||||
// Check whether WSL is installed at all. If it is not, then the
|
||||
// distribution is considered to be in a broken state. We do this check
|
||||
// first because wslIsDistributionRegistered will open a command prompt
|
||||
// window requesting the user to install WSL if it is not installed.
|
||||
isWSLInstalled, err := limawindows.IsWSLInstalled()
|
||||
if err != nil {
|
||||
return limatype.StatusBroken, fmt.Errorf("failed to check if WSL is installed: %w", err)
|
||||
}
|
||||
if !isWSLInstalled {
|
||||
return limatype.StatusBroken, errWSLNotInstalled
|
||||
}
|
||||
|
||||
// At this point, WSL is installed; check if the distribution is registered.
|
||||
distroName := "lima-" + instName
|
||||
distroNameUTF16, err := windows.UTF16PtrFromString(distroName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode distro name to UTF16: %w", err)
|
||||
}
|
||||
if err := wslIsDistributionRegistered.Find(); err != nil {
|
||||
// Given that WSL is installed, this should not happen.
|
||||
return limatype.StatusBroken, fmt.Errorf("failed to load WSL API: %w", err)
|
||||
}
|
||||
isRegistered, _, _ := wslIsDistributionRegistered.Call(uintptr(unsafe.Pointer(distroNameUTF16)))
|
||||
runtime.KeepAlive(distroNameUTF16)
|
||||
if isRegistered == 0 {
|
||||
// WSL distribution is not installed.
|
||||
return limatype.StatusUninitialized, nil
|
||||
}
|
||||
|
||||
// The distribution is registered; check the status, as there is no API for that.
|
||||
out, err := executil.RunUTF16leCommand([]string{
|
||||
"wsl.exe",
|
||||
"--list",
|
||||
@@ -221,16 +261,13 @@ func getWslStatus(ctx context.Context, instName string) (string, error) {
|
||||
}
|
||||
|
||||
// Check for edge cases first
|
||||
if strings.Contains(out, "Windows Subsystem for Linux has no installed distributions.") {
|
||||
if strings.Contains(out, "Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND") {
|
||||
return limatype.StatusBroken, fmt.Errorf(
|
||||
"failed to read instance state for instance %#q because no distro is installed,"+
|
||||
"try running `wsl --install -d Ubuntu` and then re-running Lima", instName)
|
||||
}
|
||||
return limatype.StatusBroken, fmt.Errorf(
|
||||
"failed to read instance state for instance %#q because there is no WSL kernel installed,"+
|
||||
"this usually happens when WSL was installed for another user, but never for your user."+
|
||||
"Try running `wsl --install -d Ubuntu` and `wsl --update`, and then re-running Lima", instName)
|
||||
if strings.Contains(out, "https://aka.ms/wslinstall") {
|
||||
// Error message about WSL is not installed.
|
||||
return limatype.StatusBroken, errWSLNotInstalled
|
||||
}
|
||||
if strings.Contains(out, "wsl.exe --list --online") {
|
||||
// No WSL distributions are installed. We should not get here.
|
||||
return limatype.StatusUninitialized, nil
|
||||
}
|
||||
|
||||
var instState string
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
const (
|
||||
guestCommunicationsPrefix = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices`
|
||||
magicVSOCKSuffix = "-facb-11e6-bd58-64006a7986d3"
|
||||
wslDistroInfoPrefix = `SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss`
|
||||
wslInfoKeyPath = `SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss`
|
||||
)
|
||||
|
||||
// AddVSockRegistryKey makes a vsock server running on the host accessible in guests.
|
||||
@@ -96,17 +97,43 @@ func IsVSockPortFree(port int) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsWSLInstalled checks whether WSL is (probably) installed.
|
||||
func IsWSLInstalled() (bool, error) {
|
||||
key, err := registry.OpenKey(
|
||||
registry.LOCAL_MACHINE,
|
||||
wslInfoKeyPath,
|
||||
registry.QUERY_VALUE,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, registry.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to open WSL registry key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
keyInfo, err := key.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to stat WSL registry key: %w", err)
|
||||
}
|
||||
|
||||
// The key should exist even if WSL is not installed; however, installing
|
||||
// WSL creates subkeys ("MSI", and "Plugins"), so assume WSL is installed if
|
||||
// there are any subkeys.
|
||||
return keyInfo.SubKeyCount > 0, nil
|
||||
}
|
||||
|
||||
// GetDistroID returns a DistroId GUID corresponding to a Lima instance name.
|
||||
func GetDistroID(name string) (string, error) {
|
||||
rootKey, err := registry.OpenKey(
|
||||
registry.CURRENT_USER,
|
||||
wslDistroInfoPrefix,
|
||||
wslInfoKeyPath,
|
||||
registry.READ,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"failed to open Lxss key (%s): %w",
|
||||
wslDistroInfoPrefix,
|
||||
wslInfoKeyPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
@@ -114,22 +141,19 @@ func GetDistroID(name string) (string, error) {
|
||||
|
||||
keys, err := rootKey.ReadSubKeyNames(-1)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read subkey names for %s: %w", wslDistroInfoPrefix, err)
|
||||
return "", fmt.Errorf("failed to read subkey names for %s: %w", wslInfoKeyPath, err)
|
||||
}
|
||||
|
||||
var out string
|
||||
for _, k := range keys {
|
||||
subKey, err := registry.OpenKey(
|
||||
registry.CURRENT_USER,
|
||||
fmt.Sprintf(`%s\%s`, wslDistroInfoPrefix, k),
|
||||
registry.READ,
|
||||
)
|
||||
subKey, err := registry.OpenKey(rootKey, k, registry.READ)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read subkey %#q for key %#q: %w", k, wslDistroInfoPrefix, err)
|
||||
return "", fmt.Errorf("failed to read subkey %#q for key %#q: %w", k, wslInfoKeyPath, err)
|
||||
}
|
||||
dn, _, err := subKey.GetStringValue("DistributionName")
|
||||
subKey.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read 'DistributionName' value for subkey %#q of %#q: %w", k, wslDistroInfoPrefix, err)
|
||||
return "", fmt.Errorf("failed to read 'DistributionName' value for subkey %#q of %#q: %w", k, wslInfoKeyPath, err)
|
||||
}
|
||||
if dn == name {
|
||||
out = k
|
||||
|
||||
Reference in new issue
Block a user