diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index b061d7635a..84c68ef70c 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -215,8 +215,9 @@ func (p *QEMUVirtualization) CheckExclusiveActiveVM() (bool, string, error) { if err != nil { return false, "", fmt.Errorf("checking VM active: %w", err) } + // NOTE: Start() takes care of dealing with the "starting" state. for _, vm := range vms { - if vm.Running || vm.Starting { + if vm.Running { return true, vm.Name, nil } } diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index b85c19808f..6dbf57ba61 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -27,6 +27,7 @@ import ( "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/pkg/ioutils" + "github.com/containers/storage/pkg/lockfile" "github.com/digitalocean/go-qemu/qmp" "github.com/sirupsen/logrus" ) @@ -75,6 +76,32 @@ type MachineVM struct { Created time.Time // LastUp contains the last recorded uptime LastUp time.Time + + // User at runtime for serializing write operations. + lock *lockfile.LockFile +} + +func (v *MachineVM) setLock() error { + if v.lock != nil { + return nil + } + + // FIXME: there's a painful amount of `GetConfDir` calls scattered + // across the code base. This should be done once and stored + // somewhere instead. + vmConfigDir, err := machine.GetConfDir(vmtype) + if err != nil { + return err + } + + lockPath := filepath.Join(vmConfigDir, v.Name+".lock") + lock, err := lockfile.GetLockFile(lockPath) + if err != nil { + return fmt.Errorf("creating lockfile for VM: %w", err) + } + + v.lock = lock + return nil } type Monitor struct { @@ -606,6 +633,23 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error { defaultBackoff := 500 * time.Millisecond maxBackoffs := 6 + if err := v.setLock(); err != nil { + return err + } + v.lock.Lock() + defer v.lock.Unlock() + + state, err := v.State(false) + if err != nil { + return err + } + switch state { + case machine.Starting: + return fmt.Errorf("cannot start VM %q: starting state indicates that a previous start has failed: please stop and restart the VM", v.Name) + case machine.Running: + return fmt.Errorf("cannot start VM %q: %w", v.Name, machine.ErrVMAlreadyRunning) + } + v.Starting = true if err := v.writeConfig(); err != nil { return fmt.Errorf("writing JSON file: %w", err)