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
31 changes: 22 additions & 9 deletions cmd/podman/machine/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,19 @@ func initMachine(cmd *cobra.Command, args []string) error {

err = shim.Init(initOpts, machineProvider)
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) {
// ErrRelaunchSucceeded is not a real error: it signals that
// an elevated child process completed init successfully.
// Exit gracefully with a success message.
//
// This can happen with WSL when installing the WSL features
// or with HyperV when adding entries to the Registry
if errors.Is(err, define.ErrRelaunchSucceeded) {
fmt.Println("Machine init complete")
Comment thread
l0rd marked this conversation as resolved.
if now {
fmt.Printf("Machine %q started successfully\n", initOpts.Name)
return nil
}
printStartCommand(initOpts.Name)
return nil
}
return err
Expand All @@ -299,15 +306,21 @@ func initMachine(cmd *cobra.Command, args []string) error {
fmt.Println("Machine init complete")

if now {
// Pass reexec flag from init to start
startOpts.ReExec = initOpts.ReExec
return start(cmd, args)
}

printStartCommand(initOpts.Name)
return err
}

func printStartCommand(machineName string) {
extra := ""
if initOpts.Name != defaultMachineName {
extra = " " + initOpts.Name
if machineName != defaultMachineName {
extra = " " + machineName
}
fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra)
return err
}

// checkMaxMemory gets the total system memory and compares it to the variable. if the variable
Expand Down
16 changes: 16 additions & 0 deletions cmd/podman/machine/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
package machine

import (
"errors"

"github.com/containers/podman/v6/cmd/podman/registry"
"github.com/containers/podman/v6/libpod/events"
"github.com/containers/podman/v6/pkg/machine"
"github.com/containers/podman/v6/pkg/machine/define"
"github.com/containers/podman/v6/pkg/machine/shim"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -38,6 +41,13 @@ func init() {

imageFlagName := "save-image"
flags.BoolVar(&destroyOptions.SaveImage, imageFlagName, false, "Do not delete the image file")

flags.BoolVar(
&destroyOptions.ReExec,
"reexec", false,
"process was rexeced",
)
_ = flags.MarkHidden("reexec")
}

func rm(_ *cobra.Command, args []string) error {
Expand All @@ -53,6 +63,12 @@ func rm(_ *cobra.Command, args []string) error {
}

if err := shim.Remove(mc, vmProvider, destroyOptions); err != nil {
// ErrRelaunchSucceeded is not a real error: it signals that
// an elevated child process completed the removal successfully.
// Exit gracefully.
if errors.Is(err, define.ErrRelaunchSucceeded) {
return nil
}
return err
}
newMachineEvent(events.Remove, events.Event{Name: vmName})
Expand Down
2 changes: 2 additions & 0 deletions pkg/machine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type SSHOptions struct {
}

type StartOptions struct {
ReExec bool
NoInfo bool
Quiet bool
Rosetta bool
Expand All @@ -74,6 +75,7 @@ type StartOptions struct {
type StopOptions struct{}

type RemoveOptions struct {
ReExec bool
Force bool
SaveImage bool
SaveIgnition bool
Expand Down
8 changes: 4 additions & 4 deletions pkg/machine/define/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
)

var (
ErrWrongState = errors.New("VM in wrong state to perform action")
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")
ErrWrongState = errors.New("VM in wrong state to perform action")
ErrNotImplemented = errors.New("functionality not implemented")
ErrRelaunchSucceeded = errors.New("stopping execution: command relaunched with --reexec flag for elevated privileges succeeded")
ErrRebootInitiated = errors.New("system reboot initiated")
)

type ErrVMAlreadyExists struct {
Expand Down
50 changes: 50 additions & 0 deletions pkg/machine/hyperv/stubber.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (h HyperVStubber) CreateVM(_ define.CreateVMOpts, mc *vmconfigs.MachineConf
var err error
callbackFuncs := machine.CleanUp()
defer callbackFuncs.CleanIfErr(&err)
callbackFuncs.Add(createErrorLogCallback(&err))
go callbackFuncs.CleanOnSignal()

hwConfig := hypervctl.HardwareConfig{
Expand All @@ -63,6 +64,13 @@ func (h HyperVStubber) CreateVM(_ define.CreateVMOpts, mc *vmconfigs.MachineConf
// This is to prevent a non-admin user from creating the first machine
// which would require adding vsock entries into the Windows Registry.
if err := h.canCreate(); err != nil {
// If it returns ErrHypervRegistryInitRequiresElevation and we're not already re-executing,
// offer to elevate automatically if user is in admin group
if err == ErrHypervRegistryInitRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
message := "To initialize the first Hyper-V machine, Podman requires admin rights to set up the Windows Registry.\n\n" +
windows.UACConfirmationPrompt
return launchElevate(message)
}
return err
}

Expand Down Expand Up @@ -179,6 +187,13 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err
// This is to prevent a non-admin user from deleting the last machine
// which would require removal of vsock entries from the Windows Registry.
if err := h.canRemove(mc); err != nil {
// If we get ErrHypervRegistryRemoveRequiresElevation and we're not already re-executing,
// and the user has admin rights (is in admin group), offer to elevate automatically
if err == ErrHypervRegistryRemoveRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
message := "Removing this Hyper-V machine requires admin rights to clean up the Windows Registry.\n\n" +
windows.UACConfirmationPrompt
return nil, nil, launchElevate(message)
}
return nil, nil, err
}

Expand Down Expand Up @@ -237,6 +252,35 @@ func (h HyperVStubber) canCreate() error {
return ErrHypervUserNotInAdminGroup
}

// launchElevate attempts to automatically re-run the command as administrator
// This is similar to how WSL handles elevation.
func launchElevate(message string) error {
if windows.MessageBox(message, "Podman Machine", false) != 1 {
return errors.New("elevation process cancelled by user. Please rerun the command as administrator")
}
err := windows.CreateOrTruncateElevatedOutputFile()
if err != nil {
return err
}

err = windows.RelaunchElevatedWait()
if err != nil {
windows.DumpOutputFile()
return fmt.Errorf("elevated process failed with error: %w", err)
}
return define.ErrRelaunchSucceeded
}

// createErrorLogCallback creates a callback function that logs errors to file when --reexec is detected
func createErrorLogCallback(err *error) func() error {
return func() error {
if *err != nil && windows.IsReExecuting() {
windows.LogErrorToFile(*err)
}
return nil
}
}

func isLegacyMachine(mc *vmconfigs.MachineConfig) bool {
return mc.HyperVHypervisor != nil && mc.HyperVHypervisor.ReadyVsock.MachineName != ""
}
Expand Down Expand Up @@ -372,6 +416,7 @@ func (h HyperVStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func(

callbackFuncs := machine.CleanUp()
defer callbackFuncs.CleanIfErr(&err)
callbackFuncs.Add(createErrorLogCallback(&err))
go callbackFuncs.CleanOnSignal()

if mc.IsFirstBoot() {
Expand Down Expand Up @@ -547,6 +592,11 @@ func (h HyperVStubber) PrepareIgnition(mc *vmconfigs.MachineConfig, _ *ignition.
readySock, err := vsock.LoadHVSockRegistryEntryByPurpose(vsock.Events)
if err != nil {
if !windows.HasAdminRights() {
if !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
message := "Initializing the first Hyper-V machine requires admin rights to set up the Windows Registry.\n\n" +
windows.UACConfirmationPrompt
return nil, launchElevate(message)
}
return nil, ErrHypervRegistryInitRequiresElevation
}
readySock, err = vsock.NewHVSockRegistryEntry(vsock.Events)
Expand Down
50 changes: 32 additions & 18 deletions pkg/machine/shim/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,15 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
)

callbackFuncs := machine.CleanUp()
defer callbackFuncs.CleanIfErr(&err)
defer func() {
// ErrRelaunchSucceeded is not a real error: it signals that
// an elevated child process completed the operation successfully.
// Skip cleanup so we don't remove resources (e.g. the disk image)
// that the child process created and that are now in use.
if !errors.Is(err, machineDefine.ErrRelaunchSucceeded) {
callbackFuncs.CleanIfErr(&err)
}
}()
go callbackFuncs.CleanOnSignal()

dirs, err := env.GetMachineDirs(mp.VMType())
Expand Down Expand Up @@ -159,21 +167,23 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
}
mc.ImagePath = imagePath

// TODO The following stanzas should be re-written in a differeent place. It should have a custom
// parser for our image pulling. It would be nice if init just got an error and mydisk back.
//
// Eventual valid input:
// "" <- means take the default
// "http|https://path"
// "/path
// "docker://quay.io/something/someManifest

if err := diskpull.GetDisk(opts.Image, dirs, mc.ImagePath, mp.VMType(), mc.Name, opts.SkipTlsVerify); err != nil {
return err
// If the process was re-executed with elevation, the image has already been pulled
// in the parent process, so skip disk pulling here.
if !opts.ReExec {
// TODO The following stanzas should be re-written in a differeent place. It should have a custom
// parser for our image pulling. It would be nice if init just got an error and mydisk back.
//
// Eventual valid input:
// "" <- means take the default
// "http|https://path"
// "/path
// "docker://quay.io/something/someManifest
if err := diskpull.GetDisk(opts.Image, dirs, mc.ImagePath, mp.VMType(), mc.Name, opts.SkipTlsVerify); err != nil {
return err
}
callbackFuncs.Add(mc.ImagePath.Delete)
}

callbackFuncs.Add(mc.ImagePath.Delete)

logrus.Debugf("imagePath is %q", imagePath.GetPath())

ignitionFile, err := mc.IgnitionFile()
Expand Down Expand Up @@ -460,8 +470,10 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.St
if err != nil {
return err
}
mc.Lock()
defer mc.Unlock()
if !opts.ReExec {
mc.Lock()
defer mc.Unlock()
}
if err := mc.Refresh(); err != nil {
return fmt.Errorf("reload config: %w", err)
}
Expand Down Expand Up @@ -732,8 +744,10 @@ func Remove(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.R
if err != nil {
return err
}
mc.Lock()
defer mc.Unlock()
if !opts.ReExec {
mc.Lock()
defer mc.Unlock()
}
if err := mc.Refresh(); err != nil {
return fmt.Errorf("reload config: %w", err)
}
Expand Down
Loading