mirror of https://github.com/containers/podman.git
768 lines
22 KiB
Go
768 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)
|
|
}
|
|
}
|
|
|
|
if err := enableUserLinger(mc, dist); err != nil {
|
|
return 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)
|
|
}
|
|
|
|
if err := wslPipe(getConfigBindServicesScript(user), dist, "sh"); err != nil {
|
|
return fmt.Errorf("could not configure podman binding services 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)
|
|
}
|
|
|
|
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
|
|
}
|