mirror of
https://github.com/containers/podman.git
synced 2025-11-01 19:21:04 +08:00
Copy all proxy envs into the VM on each start, this allows for updates without having to recrate the VM. This is implemented via shell script that is passed via ssh to the VM after it is started. With that we now use the same logic for all providers the old fw_cfg logic for qemu has been removed and the WSL code as well which keeps the behavior the same. There is a small risk now because we only update the env via ssh that processes started before will have the old incorrect env but it should really only effect core system processes which likely do not need them anyway. The podman system service should not be started at this point so it should be good enough. It also fixes the broken behavior with SSL_CERT_FILE/SSL_CERT_DIR which were updated on each start which is not correct as the files are only copied with ignition so these should not be updated and just set statically when the VM was created. e2e test has been added to ensure the behavior works as expected. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
787 lines
21 KiB
Go
787 lines
21 KiB
Go
//go:build windows
|
|
|
|
package wsl
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/common/pkg/config"
|
|
"github.com/containers/podman/v5/pkg/machine"
|
|
"github.com/containers/podman/v5/pkg/machine/define"
|
|
"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"
|
|
"golang.org/x/text/encoding/unicode"
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
var (
|
|
// vmtype refers to qemu (vs libvirt, krun, etc)
|
|
vmtype = define.WSLVirt
|
|
)
|
|
|
|
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 := machine.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 := machine.ToDist(mc.Name)
|
|
if err := terminateDist(dist); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
if err := unregisterDist(dist); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
|
|
vmDataDir, err := machine.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 := machine.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 := machine.ToDist(name)
|
|
fmt.Println(prompt)
|
|
if err = runCmdPassThrough(wutil.FindWSL(), "--import", dist, distTarget, imagePath, "--version", "2"); 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)
|
|
}
|
|
|
|
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) 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)
|
|
}
|
|
|
|
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 checkAndInstallWSL(reExec bool) (bool, error) {
|
|
if wutil.IsWSLInstalled() {
|
|
return true, nil
|
|
}
|
|
|
|
admin := hasAdminRights()
|
|
|
|
if !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")
|
|
} 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 errors.New("the WSL installation aborted")
|
|
}
|
|
|
|
if reExec && !admin {
|
|
return launchElevate("install the Windows WSL Features")
|
|
}
|
|
|
|
return installWsl()
|
|
}
|
|
|
|
func launchElevate(operation string) error {
|
|
if err := truncateElevatedOutputFile(); 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 nil
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
|
|
dumpOutputFile()
|
|
fmt.Fprintf(os.Stderr, wslInstallError, operation)
|
|
}
|
|
return err
|
|
}
|
|
|
|
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)
|
|
}
|
|
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, wutil.FindWSL(), "--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 {
|
|
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 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 {
|
|
name, err := getElevatedOutputFileName()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Truncate(name, 0)
|
|
}
|
|
|
|
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(wutil.FindWSL(), newArgs...)
|
|
}
|
|
|
|
func wslPipe(input string, dist string, arg ...string) error {
|
|
newArgs := []string{"-u", "root", "-d", dist}
|
|
newArgs = append(newArgs, arg...)
|
|
return pipeCmdPassThrough(wutil.FindWSL(), input, newArgs...)
|
|
}
|
|
|
|
//nolint:unused
|
|
func wslCreateKeys(identityPath string, dist string) (string, error) {
|
|
return machine.CreateSSHKeysPrefix(identityPath, true, true, wutil.FindWSL(), "-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
|
|
return cmd.Run()
|
|
}
|
|
|
|
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)
|
|
return cmd.Run()
|
|
}
|
|
|
|
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
|
|
return cmd.Run()
|
|
}
|
|
|
|
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 := machine.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 IsWSLFeatureEnabled() bool {
|
|
return wutil.SilentExec(wutil.FindWSL(), "--set-default-version", "2") == nil
|
|
}
|
|
|
|
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(wutil.FindWSL(), args...)
|
|
out, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
return nil, 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{}{}
|
|
}
|
|
}
|
|
|
|
_ = cmd.Wait()
|
|
|
|
return all, nil
|
|
}
|
|
|
|
func isSystemdRunning(dist string) (bool, error) {
|
|
cmd := exec.Command(wutil.FindWSL(), "-u", "root", "-d", dist, "sh")
|
|
cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
|
|
out, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
_ = cmd.Wait()
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func terminateDist(dist string) error {
|
|
cmd := exec.Command(wutil.FindWSL(), "--terminate", dist)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func unregisterDist(dist string) error {
|
|
cmd := exec.Command(wutil.FindWSL(), "--unregister", dist)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func isRunning(name string) (bool, error) {
|
|
dist := machine.ToDist(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) uint64 {
|
|
vmDataDir, err := machine.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 uint64(info.Size())
|
|
}
|
|
|
|
//nolint:unused
|
|
func getCPUs(name string) (uint64, error) {
|
|
dist := machine.ToDist(name)
|
|
if run, _ := isWSLRunning(dist); !run {
|
|
return 0, nil
|
|
}
|
|
cmd := exec.Command(wutil.FindWSL(), "-u", "root", "-d", dist, "nproc")
|
|
out, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
return 0, err
|
|
}
|
|
scanner := bufio.NewScanner(out)
|
|
var result string
|
|
for scanner.Scan() {
|
|
result = scanner.Text()
|
|
}
|
|
_ = cmd.Wait()
|
|
|
|
ret, err := strconv.Atoi(result)
|
|
return uint64(ret), err
|
|
}
|
|
|
|
//nolint:unused
|
|
func getMem(name string) (uint64, error) {
|
|
dist := machine.ToDist(name)
|
|
if run, _ := isWSLRunning(dist); !run {
|
|
return 0, nil
|
|
}
|
|
cmd := exec.Command(wutil.FindWSL(), "-u", "root", "-d", dist, "cat", "/proc/meminfo")
|
|
out, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
return 0, err
|
|
}
|
|
scanner := bufio.NewScanner(out)
|
|
var (
|
|
total, available uint64
|
|
t, a int
|
|
)
|
|
for scanner.Scan() {
|
|
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
|
|
}
|
|
}
|
|
_ = cmd.Wait()
|
|
|
|
return 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
|
|
}
|