Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/podman/machine/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package machine

import (
"errors"
"fmt"
"os"

Expand Down Expand Up @@ -232,6 +233,14 @@ func initMachine(cmd *cobra.Command, args []string) error {

err = shim.Init(initOpts, provider)
if err != nil {
// The installation is partially complete and podman should
// exit gracefully with no error and no success message.
// Examples:
// - a user has chosen to perform their own reboot
// - reexec for limited admin operations, returning to parent
if errors.Is(err, define.ErrInitRelaunchAttempt) {
return nil
}
return err
}

Expand Down
8 changes: 5 additions & 3 deletions pkg/machine/define/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
)

var (
ErrWrongState = errors.New("VM in wrong state to perform action")
ErrVMAlreadyExists = errors.New("VM already exists")
ErrNotImplemented = errors.New("functionality not implemented")
ErrWrongState = errors.New("VM in wrong state to perform action")
ErrVMAlreadyExists = errors.New("VM already exists")
ErrNotImplemented = errors.New("functionality not implemented")
ErrInitRelaunchAttempt = errors.New("stopping execution: 'init' relaunched with --reexec flag to reinitialize the VM")
ErrRebootInitiated = errors.New("system reboot initiated")
)

type ErrVMRunningCannotDestroyed struct {
Expand Down
26 changes: 17 additions & 9 deletions pkg/machine/shim/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
if err != nil {
return err
}
machineLock.Lock()
defer machineLock.Unlock()

// If the machine is being re-launched, the lock is already held
if !opts.ReExec {
machineLock.Lock()
defer machineLock.Unlock()
}

mc, err := vmconfigs.NewMachineConfig(opts, dirs, sshIdentityPath, mp.VMType(), machineLock)
if err != nil {
Expand All @@ -111,8 +115,9 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
mc.Version = vmconfigs.MachineConfigVersion

createOpts := machineDefine.CreateVMOpts{
Name: opts.Name,
Dirs: dirs,
Name: opts.Name,
Dirs: dirs,
ReExec: opts.ReExec,
}

if umn := opts.UserModeNetworking; umn != nil {
Expand Down Expand Up @@ -264,6 +269,14 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
}
ignBuilder.WithUnit(readyUnit)

// CreateVM could cause the init command to be re-launched in some cases (e.g. wsl)
// so we need to avoid creating the machine config or connections before this check happens.
// when relaunching, the invoked 'init' command will be responsible to set up the machine
err = mp.CreateVM(createOpts, mc, &ignBuilder)
if err != nil {
return err
}
Comment on lines +275 to +278
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any reason of why this was moved. IF the order is important then it needs a big comment explaining why.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment. Basically i want to avoid creating any resources before the CreateVM that could be responsible to invoke a new init that will perform the setup. So i switched the order with the AddSSHConnectionsToPodmanSocket


// TODO AddSSHConnectionToPodmanSocket could take an machineconfig instead
if err := connection.AddSSHConnectionsToPodmanSocket(mc.HostUser.UID, mc.SSH.Port, mc.SSH.IdentityPath, mc.Name, mc.SSH.RemoteUsername, opts); err != nil {
return err
Expand All @@ -278,11 +291,6 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
}
callbackFuncs.Add(cleanup)

err = mp.CreateVM(createOpts, mc, &ignBuilder)
if err != nil {
return err
}

if len(opts.IgnitionPath) == 0 {
if err := ignBuilder.Build(); err != nil {
return err
Expand Down
11 changes: 0 additions & 11 deletions pkg/machine/wsl/declares.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,6 @@ http://docs.microsoft.com/en-us/windows/wsl/install

`

const wslKernelError = `Could not %s. See previous output for any potential failure details.
If you can not resolve the issue, try rerunning the "podman machine init command". If that fails
try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails,
try following the steps outlined in the following article:

http://docs.microsoft.com/en-us/windows/wsl/install

`

const wslInstallKernel = "install the WSL Kernel"

const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows
Either update to Build 19041 (or later), or perform the manual installation steps
outlined in the following article:
Expand Down
123 changes: 31 additions & 92 deletions pkg/machine/wsl/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"path/filepath"
"strconv"
"strings"
"time"

"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/strongunits"
Expand All @@ -22,7 +21,6 @@ import (
"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/pkg/machine/wsl/wutil"
"github.com/containers/podman/v5/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/sirupsen/logrus"
Expand All @@ -32,7 +30,8 @@ import (

var (
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = define.WSLVirt
vmtype = define.WSLVirt
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
)

type ExitCodeError struct {
Expand Down Expand Up @@ -95,7 +94,26 @@ func provisionWSLDist(name string, imagePath string, prompt string) (string, err

dist := env.WithPodmanPrefix(name)
fmt.Println(prompt)
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil {

// 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)
}

Expand Down Expand Up @@ -310,41 +328,6 @@ func writeWslConf(dist string, user string) error {
return nil
}

func checkAndInstallWSL(reExec bool) (bool, error) {
if wutil.IsWSLInstalled() {
return true, nil
}

admin := HasAdminRights()

if !wutil.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")
Expand All @@ -364,34 +347,34 @@ func attemptFeatureInstall(reExec, admin bool) error {
"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")
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 := truncateElevatedOutputFile(); err != nil {
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 nil
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 err
return define.ErrInitRelaunchAttempt
}

func installWsl() error {
Expand All @@ -409,44 +392,10 @@ func installWsl() error {
"/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, "wsl", "--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 {
Expand All @@ -473,24 +422,14 @@ 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 {
func createOrTruncateElevatedOutputFile() error {
name, err := getElevatedOutputFileName()
if err != nil {
return err
}

return os.Truncate(name, 0)
_, err = os.Create(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function is called truncate but now it no longer truncates which is confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed 👍

return err
}

func getElevatedOutputFile(mode int) (*os.File, error) {
Expand Down Expand Up @@ -572,7 +511,7 @@ func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
cmd.Stdin = os.Stdin
cmd.Stdout = io.MultiWriter(os.Stdout, out)
cmd.Stderr = io.MultiWriter(os.Stderr, out)
if err := cmd.Run(); err != nil {
if err := cmd.Run(); isMsiError(err) {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
Expand Down
14 changes: 9 additions & 5 deletions pkg/machine/wsl/stubber.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf
go callbackFuncs.CleanOnSignal()
mc.WSLHypervisor = new(vmconfigs.WSLConfig)

if cont, err := checkAndInstallWSL(opts.ReExec); !cont {
appendOutputIfError(opts.ReExec, err)
return err
}

_ = setupWslProxyEnv()

if opts.UserModeNetworking {
Expand All @@ -51,6 +46,15 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf
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
}

Expand Down
11 changes: 6 additions & 5 deletions pkg/machine/wsl/util_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"unsafe"

"github.com/Microsoft/go-winio"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/homedir"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -216,6 +217,10 @@ func reboot() error {
}
}

if err := addRunOnceRegistryEntry(command); err != nil {
return err
}

message := "To continue the process of enabling WSL, the system needs to reboot. " +
"Alternatively, you can cancel and reboot manually\n\n" +
"After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
Expand All @@ -226,10 +231,6 @@ func reboot() error {
return nil
}

if err := addRunOnceRegistryEntry(command); err != nil {
return err
}

if err := winio.RunWithPrivilege(rebootPrivilege, func() error {
if err := windows.ExitWindowsEx(rebootFlags, rebootReason); err != nil {
return fmt.Errorf("execute ExitWindowsEx to reboot system failed: %w", err)
Expand All @@ -239,7 +240,7 @@ func reboot() error {
return fmt.Errorf("cannot reboot system: %w", err)
}

return nil
return define.ErrRebootInitiated
}

func addRunOnceRegistryEntry(command string) error {
Expand Down
Loading