mirror of
https://github.com/containers/podman.git
synced 2025-10-19 04:03:23 +08:00

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>
299 lines
7.9 KiB
Go
299 lines
7.9 KiB
Go
//go:build windows
|
|
|
|
package wsl
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/containers/podman/v5/pkg/machine/env"
|
|
|
|
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
|
|
"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/sirupsen/logrus"
|
|
)
|
|
|
|
type WSLStubber struct {
|
|
vmconfigs.WSLConfig
|
|
}
|
|
|
|
func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) error {
|
|
var (
|
|
err error
|
|
)
|
|
// cleanup half-baked files if init fails at any point
|
|
callbackFuncs := machine.CleanUp()
|
|
defer callbackFuncs.CleanIfErr(&err)
|
|
go callbackFuncs.CleanOnSignal()
|
|
mc.WSLHypervisor = new(vmconfigs.WSLConfig)
|
|
|
|
_ = setupWslProxyEnv()
|
|
|
|
if opts.UserModeNetworking {
|
|
if err = verifyWSLUserModeCompat(); err != nil {
|
|
return err
|
|
}
|
|
mc.WSLHypervisor.UserModeNetworking = true
|
|
}
|
|
|
|
const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..."
|
|
dist, err := provisionWSLDist(mc.Name, mc.ImagePath.GetPath(), prompt)
|
|
if err != nil {
|
|
if errors.Is(err, ErrWslNotSupported) {
|
|
// If error is Wsl/Service/RegisterDistro/CreateVm/HCS/ERROR_NOT_SUPPORTED
|
|
// or Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE
|
|
// it means WSL's VM creation failed, likely due to virtualization features not being enabled.
|
|
// Relaunching 'podman machine init' in elevated mode will attempt to reconfigure the WSL machine.
|
|
admin := HasAdminRights()
|
|
|
|
return attemptFeatureInstall(opts.ReExec, admin)
|
|
}
|
|
return err
|
|
}
|
|
|
|
unprovisionCallbackFunc := func() error {
|
|
return unprovisionWSL(mc)
|
|
}
|
|
callbackFuncs.Add(unprovisionCallbackFunc)
|
|
|
|
if mc.WSLHypervisor.UserModeNetworking {
|
|
if err = installUserModeDist(dist, mc.ImagePath.GetPath()); err != nil {
|
|
_ = unregisterDist(dist)
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Println("Configuring system...")
|
|
if err = configureSystem(mc, dist, mc.Ansible); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = installScripts(dist); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = createKeys(mc, dist); err != nil {
|
|
return err
|
|
}
|
|
|
|
// recycle vm
|
|
return terminateDist(dist)
|
|
}
|
|
|
|
func (w WSLStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) (*ignition.ReadyUnitOpts, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (w WSLStubber) Exists(name string) (bool, error) {
|
|
return isWSLExist(env.WithPodmanPrefix(name))
|
|
}
|
|
|
|
func (w WSLStubber) MountType() vmconfigs.VolumeMountType {
|
|
return vmconfigs.Unknown
|
|
}
|
|
|
|
func (w WSLStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool) error {
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error, error) {
|
|
// Note: we could consider swapping the two conditionals
|
|
// below if we wanted to hard error on the wsl unregister
|
|
// of the vm
|
|
wslRemoveFunc := func() error {
|
|
if err := runCmdPassThrough("wsl", "--unregister", env.WithPodmanPrefix(mc.Name)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return []string{}, wslRemoveFunc, nil
|
|
}
|
|
|
|
func (w WSLStubber) RemoveAndCleanMachines(_ *define.MachineDirs) error {
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) SetProviderAttrs(mc *vmconfigs.MachineConfig, opts define.SetOptions) error {
|
|
state, err := w.State(mc, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state != define.Stopped {
|
|
return errors.New("unable to change settings unless vm is stopped")
|
|
}
|
|
|
|
if opts.Rootful != nil && mc.HostUser.Rootful != *opts.Rootful {
|
|
if err := mc.SetRootful(*opts.Rootful); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if opts.CPUs != nil {
|
|
return errors.New("changing CPUs not supported for WSL machines")
|
|
}
|
|
|
|
if opts.Memory != nil {
|
|
return errors.New("changing memory not supported for WSL machines")
|
|
}
|
|
|
|
if opts.USBs != nil {
|
|
return errors.New("changing USBs not supported for WSL machines")
|
|
}
|
|
|
|
if opts.DiskSize != nil {
|
|
return errors.New("changing disk size not supported for WSL machines")
|
|
}
|
|
|
|
if opts.UserModeNetworking != nil && mc.WSLHypervisor.UserModeNetworking != *opts.UserModeNetworking {
|
|
if running, _ := isRunning(mc.Name); running {
|
|
return errors.New("user-mode networking can only be changed when the machine is not running")
|
|
}
|
|
|
|
dist := env.WithPodmanPrefix(mc.Name)
|
|
if err := changeDistUserModeNetworking(dist, mc.SSH.RemoteUsername, mc.ImagePath.GetPath(), *opts.UserModeNetworking); err != nil {
|
|
return fmt.Errorf("failure changing state of user-mode networking setting: %w", err)
|
|
}
|
|
|
|
mc.WSLHypervisor.UserModeNetworking = *opts.UserModeNetworking
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) StartNetworking(mc *vmconfigs.MachineConfig, cmd *gvproxy.GvproxyCommand) error {
|
|
// Startup user-mode networking if enabled
|
|
if mc.WSLHypervisor.UserModeNetworking {
|
|
return startUserModeNetworking(mc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool {
|
|
return mc.WSLHypervisor.UserModeNetworking
|
|
}
|
|
|
|
func (w WSLStubber) UseProviderNetworkSetup() bool {
|
|
return true
|
|
}
|
|
|
|
func (w WSLStubber) RequireExclusiveActive() bool {
|
|
return false
|
|
}
|
|
|
|
func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
|
|
socket, err := mc.APISocket()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
winProxyOpts := machine.WinProxyOpts{
|
|
Name: mc.Name,
|
|
IdentityPath: mc.SSH.IdentityPath,
|
|
Port: mc.SSH.Port,
|
|
RemoteUsername: mc.SSH.RemoteUsername,
|
|
Rootful: mc.HostUser.Rootful,
|
|
VMType: w.VMType(),
|
|
Socket: socket,
|
|
}
|
|
machine.LaunchWinProxy(winProxyOpts, noInfo)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) {
|
|
dist := env.WithPodmanPrefix(mc.Name)
|
|
|
|
err := wslInvoke(dist, "/root/bootstrap")
|
|
if err != nil {
|
|
err = fmt.Errorf("the WSL bootstrap script failed: %w", err)
|
|
}
|
|
|
|
readyFunc := func() error {
|
|
return nil
|
|
}
|
|
|
|
return nil, readyFunc, err
|
|
}
|
|
|
|
func (w WSLStubber) State(mc *vmconfigs.MachineConfig, bypass bool) (define.Status, error) {
|
|
running, err := isRunning(mc.Name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if running {
|
|
return define.Running, nil
|
|
}
|
|
return define.Stopped, nil
|
|
}
|
|
|
|
func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
|
var (
|
|
err error
|
|
)
|
|
|
|
if running, err := isRunning(mc.Name); !running {
|
|
return err
|
|
}
|
|
|
|
dist := env.WithPodmanPrefix(mc.Name)
|
|
|
|
// Stop user-mode networking if enabled
|
|
if err := stopUserModeNetworking(mc); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error())
|
|
}
|
|
|
|
if err := machine.StopWinProxy(mc.Name, vmtype); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
|
}
|
|
|
|
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
|
|
cmd.Stdin = strings.NewReader(waitTerm)
|
|
out := &bytes.Buffer{}
|
|
cmd.Stderr = out
|
|
cmd.Stdout = out
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
return fmt.Errorf("executing wait command: %w", err)
|
|
}
|
|
|
|
exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
|
|
if err = exitCmd.Run(); err != nil {
|
|
return fmt.Errorf("stopping systemd: %w", err)
|
|
}
|
|
|
|
if err = cmd.Wait(); err != nil {
|
|
logrus.Warnf("Failed to wait for systemd to exit: (%s)", strings.TrimSpace(out.String()))
|
|
}
|
|
|
|
return terminateDist(dist)
|
|
}
|
|
|
|
func (w WSLStubber) StopHostNetworking(mc *vmconfigs.MachineConfig, vmType define.VMType) error {
|
|
return stopUserModeNetworking(mc)
|
|
}
|
|
|
|
func (w WSLStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
|
|
dist := env.WithPodmanPrefix(mc.Name)
|
|
|
|
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(changePort, port)); err != nil {
|
|
return fmt.Errorf("could not change SSH port for guest OS: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w WSLStubber) VMType() define.VMType {
|
|
return define.WSLVirt
|
|
}
|
|
|
|
func (w WSLStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
|
|
return false, nil
|
|
}
|