Files
podman/pkg/machine/wsl/machine.go
lstocchi 8532ecb710 fix wsl install workflow on machine init command
this patch changes how the detection of wsl works.
The old way of using wsl --status command output to detect some missing features required by WSL is not fully reliable.
WSL checks if the wsl feature is enabled and if the vmcompute service do exist. However, this is not enough to identify if the virtual machine platform feature is enabled. The vmcompute service could exist because it has been installed by other tools or it could exist but being stopped.

The way proposed by this patch is to try execute the import command and,
if it fails, check the error and if it is related to the Host Compute
Service try to install all features required by WSL.

The flow is the same as before, the user is asked to execute the podman
machine init command with elevated privileges. Eventually, after
enabling WSL and VMP features, the user is asked to reboot the machine.

When the machine restarts, the powershell gets invoked again and execute
the command init.

The code also fixes some issues that could cause misbehaviors when
invoking recursively the elevated shell, like an unreleased lock, or a
missing file.

Signed-off-by: lstocchi <lstocchi@redhat.com>
2025-06-04 14:26:48 +02:00

777 lines
22 KiB
Go

//go:build windows
package wsl
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/strongunits"
"github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/define"
"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/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/sirupsen/logrus"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
var (
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = define.WSLVirt
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
)
type ExitCodeError struct {
code uint
}
func (e *ExitCodeError) Error() string {
return fmt.Sprintf("Process failed with exit code: %d", e.code)
}
//nolint:unused
func getConfigPath(name string) (string, error) {
return getConfigPathExt(name, "json")
}
//nolint:unused
func getConfigPathExt(name string, extension string) (string, error) {
vmConfigDir, err := env.GetConfDir(vmtype)
if err != nil {
return "", err
}
return filepath.Join(vmConfigDir, fmt.Sprintf("%s.%s", name, extension)), nil
}
// TODO like provisionWSL, i think this needs to be pushed to use common
// paths and types where possible
func unprovisionWSL(mc *vmconfigs.MachineConfig) error {
dist := env.WithPodmanPrefix(mc.Name)
if err := terminateDist(dist); err != nil {
logrus.Error(err)
}
if err := unregisterDist(dist); err != nil {
logrus.Error(err)
}
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, mc.Name)
return utils.GuardedRemoveAll(distTarget)
}
// TODO there are some differences here that I dont fully groak but I think
// we should push this stuff be more common (dir names, etc) and also use
// typed things where possible like vmfiles
func provisionWSLDist(name string, imagePath string, prompt string) (string, error) {
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return "", err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, name)
if err := os.MkdirAll(distDir, 0755); err != nil {
return "", fmt.Errorf("could not create wsldist directory: %w", err)
}
dist := env.WithPodmanPrefix(name)
fmt.Println(prompt)
// 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)
}
// Fixes newuidmap
if err = wslInvoke(dist, "rpm", "--restore", "shadow-utils"); err != nil {
return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err)
}
if err = wslInvoke(dist, "mkdir", "-p", "/usr/local/bin"); err != nil {
return "", fmt.Errorf("could not create /usr/local/bin: %w", err)
}
if err = wslInvoke(dist, "ln", "-f", "-s", gvForwarderPath, "/usr/local/bin/vm"); err != nil {
return "", fmt.Errorf("could not setup compatibility link: %w", err)
}
return dist, nil
}
func createKeys(mc *vmconfigs.MachineConfig, dist string) error {
user := mc.SSH.RemoteUsername
if err := terminateDist(dist); err != nil {
return fmt.Errorf("could not cycle WSL dist: %w", err)
}
identityPath := mc.SSH.IdentityPath + ".pub"
// TODO We could audit vmfile reads and see if a 'ReadToString'
// method makes sense.
pubKey, err := os.ReadFile(identityPath)
if err != nil {
return fmt.Errorf("could not create ssh keys: %w", err)
}
key := string(pubKey)
if err := wslPipe(key+"\n", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
"cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
return fmt.Errorf("could not create root authorized keys on guest OS: %w", err)
}
userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
"cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
"chmod 600 /home/[USER]/.ssh/authorized_keys", user)
if err := wslPipe(key+"\n", dist, "sh", "-c", userAuthCmd); err != nil {
return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", user, err)
}
return nil
}
func configureSystem(mc *vmconfigs.MachineConfig, dist string, ansibleConfig *vmconfigs.AnsibleConfig) error {
user := mc.SSH.RemoteUsername
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, mc.SSH.Port, mc.SSH.Port)); err != nil {
return fmt.Errorf("could not configure SSH port for guest OS: %w", err)
}
if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil {
return fmt.Errorf("could not configure systemd settings for guest OS: %w", err)
}
if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
return fmt.Errorf("could not add wheel to sudoers: %w", err)
}
if err := wslPipe(overrideSysusers, dist, "sh", "-c",
"cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err)
}
if ansibleConfig != nil {
if err := wslPipe(ansibleConfig.Contents, dist, "sh", "-c", fmt.Sprintf("cat > %s", ansibleConfig.PlaybookPath)); err != nil {
return fmt.Errorf("could not generate playbook file for guest os: %w", err)
}
}
lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil {
return fmt.Errorf("could not generate linger service for guest OS: %w", err)
}
if err := enableUserLinger(mc, dist); err != nil {
return err
}
if err := wslPipe(withUser(lingerSetup, user), dist, "sh"); err != nil {
return fmt.Errorf("could not configure systemd settings for guest OS: %w", err)
}
if err := wslPipe(containersConf, dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
return fmt.Errorf("could not create containers.conf for guest OS: %w", err)
}
if err := configureRegistries(dist); err != nil {
return err
}
if err := setupPodmanDockerSock(dist, mc.HostUser.Rootful); err != nil {
return err
}
if err := wslInvoke(dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
return fmt.Errorf("could not create podman-machine file for guest OS: %w", err)
}
if err := configureBindMounts(dist, user); err != nil {
return err
}
return changeDistUserModeNetworking(dist, user, mc.ImagePath.GetPath(), mc.WSLHypervisor.UserModeNetworking)
}
func configureBindMounts(dist string, user string) error {
if err := wslPipe(fmt.Sprintf(bindMountSystemService, dist), dist, "sh", "-c", "cat > /etc/systemd/system/podman-mnt-bindings.service"); err != nil {
return fmt.Errorf("could not create podman binding service file for guest OS: %w", err)
}
catUserService := "cat > " + getUserUnitPath(user)
if err := wslPipe(getBindMountUserService(dist), dist, "sh", "-c", catUserService); err != nil {
return fmt.Errorf("could not create podman binding user service file for guest OS: %w", err)
}
if err := wslPipe(getBindMountFsTab(dist), dist, "sh", "-c", "cat >> /etc/fstab"); err != nil {
return fmt.Errorf("could not create podman binding fstab entry for guest OS: %w", err)
}
if err := wslPipe(getConfigBindServicesScript(user), dist, "sh"); err != nil {
return fmt.Errorf("could not configure podman binding services for guest OS: %w", err)
}
catGroupDropin := fmt.Sprintf("cat > %s/%s", podmanSocketDropinPath, "10-group.conf")
if err := wslPipe(overrideSocketGroup, dist, "sh", "-c", catGroupDropin); err != nil {
return fmt.Errorf("could not configure podman socket group override: %w", err)
}
return nil
}
func getConfigBindServicesScript(user string) string {
return fmt.Sprintf(configBindServices, user)
}
func getBindMountUserService(dist string) string {
return fmt.Sprintf(bindMountUserService, dist)
}
func getUserUnitPath(user string) string {
return fmt.Sprintf(bindUserUnitPath, user)
}
func getBindMountFsTab(dist string) string {
return fmt.Sprintf(bindMountFsTab, dist)
}
func setupPodmanDockerSock(dist string, rootful bool) error {
content := ignition.GetPodmanDockerTmpConfig(1000, rootful, true)
if err := wslPipe(content, dist, "sh", "-c", "cat > "+ignition.PodmanDockerTmpConfPath); err != nil {
return fmt.Errorf("could not create internal docker sock conf: %w", err)
}
return nil
}
func enableUserLinger(mc *vmconfigs.MachineConfig, dist string) error {
lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + mc.SSH.RemoteUsername
if err := wslInvoke(dist, "sh", "-c", lingerCmd); err != nil {
return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err)
}
return nil
}
func configureRegistries(dist string) error {
cmd := "cat > /etc/containers/registries.conf.d/999-podman-machine.conf"
if err := wslPipe(registriesConf, dist, "sh", "-c", cmd); err != nil {
return fmt.Errorf("could not configure registries on guest OS: %w", err)
}
return nil
}
func installScripts(dist string) error {
if err := wslPipe(enterns, dist, "sh", "-c",
"cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
return fmt.Errorf("could not create enterns script for guest OS: %w", err)
}
if err := wslPipe(profile, dist, "sh", "-c",
"cat > /etc/profile.d/enterns.sh"); err != nil {
return fmt.Errorf("could not create motd profile script for guest OS: %w", err)
}
if err := wslPipe(wslmotd, dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
return fmt.Errorf("could not create a WSL MOTD for guest OS: %w", err)
}
if err := wslPipe(bootstrap, dist, "sh", "-c",
"cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
return fmt.Errorf("could not create bootstrap script for guest OS: %w", err)
}
return nil
}
func writeWslConf(dist string, user string) error {
if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil {
return fmt.Errorf("could not configure wsl config for guest OS: %w", err)
}
return 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")
} else if !winVersionAtLeast(10, 0, 19041) {
fmt.Fprint(os.Stderr, wslOldVersion)
return errors.New("the WSL can not be automatically installed")
}
message := "WSL is not installed on this system, installing it.\n\n"
if !admin {
message += "Since you are not running as admin, a new window will open and " +
"require you to approve administrator privileges.\n\n"
}
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 {
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 := createOrTruncateElevatedOutputFile(); err != nil {
return err
}
err := relaunchElevatedWait()
if err != nil {
if eerr, ok := err.(*ExitCodeError); ok {
if eerr.code == ErrorSuccessRebootRequired {
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
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 define.ErrInitRelaunchAttempt
}
func installWsl() error {
log, err := getElevatedOutputFileWrite()
if err != nil {
return err
}
defer log.Close()
if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
"/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
return fmt.Errorf("could not enable WSL Feature: %w", err)
}
if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
"/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
return fmt.Errorf("could not enable Virtual Machine Feature: %w", err)
}
return reboot()
}
func getElevatedOutputFileName() (string, error) {
dir, err := homedir.GetDataHome()
if err != nil {
return "", err
}
return filepath.Join(dir, "podman-elevated-output.log"), nil
}
func dumpOutputFile() {
file, err := getElevatedOutputFileRead()
if err != nil {
logrus.Debug("could not find elevated child output file")
return
}
defer file.Close()
_, _ = io.Copy(os.Stdout, file)
}
func getElevatedOutputFileRead() (*os.File, error) {
return getElevatedOutputFile(os.O_RDONLY)
}
func getElevatedOutputFileWrite() (*os.File, error) {
return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
}
func createOrTruncateElevatedOutputFile() error {
name, err := getElevatedOutputFileName()
if err != nil {
return err
}
_, err = os.Create(name)
return err
}
func getElevatedOutputFile(mode int) (*os.File, error) {
name, err := getElevatedOutputFileName()
if err != nil {
return nil, err
}
dir, err := homedir.GetDataHome()
if err != nil {
return nil, err
}
if err = os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
return os.OpenFile(name, mode, 0644)
}
func isMsiError(err error) bool {
if err == nil {
return false
}
if eerr, ok := err.(*exec.ExitError); ok {
switch eerr.ExitCode() {
case 0:
fallthrough
case ErrorSuccessRebootInitiated:
fallthrough
case ErrorSuccessRebootRequired:
return false
}
}
return true
}
func withUser(s string, user string) string {
return strings.ReplaceAll(s, "[USER]", user)
}
func wslInvoke(dist string, arg ...string) error {
newArgs := []string{"-u", "root", "-d", dist}
newArgs = append(newArgs, arg...)
return runCmdPassThrough("wsl", newArgs...)
}
func wslPipe(input string, dist string, arg ...string) error {
newArgs := []string{"-u", "root", "-d", dist}
newArgs = append(newArgs, arg...)
return pipeCmdPassThrough("wsl", input, newArgs...)
}
//nolint:unused
func wslCreateKeys(identityPath string, dist string) (string, error) {
return machine.CreateSSHKeysPrefix(identityPath, true, true, "wsl", "-u", "root", "-d", dist)
}
func runCmdPassThrough(name string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
// TODO - Perhaps improve this with a conpty pseudo console so that
// dism installer text bars mirror console behavior (redraw)
cmd := exec.Command(name, arg...)
cmd.Stdin = os.Stdin
cmd.Stdout = io.MultiWriter(os.Stdout, out)
cmd.Stderr = io.MultiWriter(os.Stderr, out)
if err := cmd.Run(); isMsiError(err) {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func pipeCmdPassThrough(name string, input string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin = strings.NewReader(input)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func setupWslProxyEnv() (hasProxy bool) {
current, _ := os.LookupEnv("WSLENV")
for _, key := range config.ProxyEnv {
if value, _ := os.LookupEnv(key); len(value) < 1 {
continue
}
hasProxy = true
delim := ""
if len(current) > 0 {
delim = ":"
}
current = fmt.Sprintf("%s%s%s/u", current, delim, key)
}
if hasProxy {
os.Setenv("WSLENV", current)
}
return
}
//nolint:unused
func obtainGlobalConfigLock() (*fileLock, error) {
lockDir, err := env.GetGlobalDataDir()
if err != nil {
return nil, err
}
// Lock file needs to be above all backends
// TODO: This should be changed to a common.Config lock mechanism when available
return lockFile(filepath.Join(lockDir, "podman-config.lck"))
}
func isWSLRunning(dist string) (bool, error) {
return wslCheckExists(dist, true)
}
func isWSLExist(dist string) (bool, error) {
return wslCheckExists(dist, false)
}
func wslCheckExists(dist string, running bool) (bool, error) {
all, err := getAllWSLDistros(running)
if err != nil {
return false, err
}
_, exists := all[dist]
return exists, nil
}
func getAllWSLDistros(running bool) (map[string]struct{}, error) {
args := []string{"-l", "--quiet"}
if running {
args = append(args, "--running")
}
cmd := exec.Command("wsl", args...)
out, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start command %s %v: %w", cmd.Path, args, err)
}
all := make(map[string]struct{})
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) > 0 {
all[fields[0]] = struct{}{}
}
}
err = cmd.Wait()
if err != nil {
return nil, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, args, err, strings.TrimSpace(stderr.String()))
}
return all, nil
}
func isSystemdRunning(dist string) (bool, error) {
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
out, err := cmd.StdoutPipe()
if err != nil {
return false, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return false, err
}
scanner := bufio.NewScanner(out)
result := false
if scanner.Scan() {
text := scanner.Text()
i, err := strconv.Atoi(text)
if err == nil && i > 0 {
result = true
}
}
err = cmd.Wait()
if err != nil {
return false, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
}
return result, nil
}
func terminateDist(dist string) error {
cmd := exec.Command("wsl", "--terminate", dist)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
}
return nil
}
func unregisterDist(dist string) error {
cmd := exec.Command("wsl", "--unregister", dist)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
}
return nil
}
func isRunning(name string) (bool, error) {
dist := env.WithPodmanPrefix(name)
wsl, err := isWSLRunning(dist)
if err != nil {
return false, err
}
sysd := false
if wsl {
sysd, err = isSystemdRunning(dist)
if err != nil {
return false, err
}
}
return sysd, err
}
//nolint:unused
func getDiskSize(name string) strongunits.GiB {
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return 0
}
distDir := filepath.Join(vmDataDir, "wsldist")
disk := filepath.Join(distDir, name, "ext4.vhdx")
info, err := os.Stat(disk)
if err != nil {
return 0
}
return strongunits.ToGiB(strongunits.B(info.Size()))
}
//nolint:unused
func getCPUs(name string) (uint64, error) {
dist := env.WithPodmanPrefix(name)
if run, _ := isWSLRunning(dist); !run {
return 0, nil
}
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc")
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return 0, err
}
scanner := bufio.NewScanner(out)
var result string
for scanner.Scan() {
result = scanner.Text()
}
err = cmd.Wait()
if err != nil {
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(strings.TrimSpace(stderr.String())))
}
ret, err := strconv.Atoi(result)
return uint64(ret), err
}
//nolint:unused
func getMem(name string) (strongunits.MiB, error) {
dist := env.WithPodmanPrefix(name)
if run, _ := isWSLRunning(dist); !run {
return 0, nil
}
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo")
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return 0, err
}
scanner := bufio.NewScanner(out)
var (
total, available uint64
t, a int
)
for scanner.Scan() {
// fields are in kB so div to mb
fields := strings.Fields(scanner.Text())
if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
t, err = strconv.Atoi(fields[1])
total = uint64(t) / 1024
} else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
a, err = strconv.Atoi(fields[1])
available = uint64(a) / 1024
}
if err != nil {
break
}
}
err = cmd.Wait()
if err != nil {
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
}
return strongunits.MiB(total - available), err
}
//nolint:unused
func getResources(mc *vmconfigs.MachineConfig) (resources vmconfigs.ResourceConfig) {
resources.CPUs, _ = getCPUs(mc.Name)
resources.Memory, _ = getMem(mc.Name)
resources.DiskSize = getDiskSize(mc.Name)
return
}