package drivers

import (
	"bufio"
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"embed"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"maps"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"slices"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"unsafe"

	"github.com/flosch/pongo2/v6"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/kballard/go-shellquote"
	"github.com/klauspost/pgzip"
	"github.com/mdlayher/vsock"
	"github.com/pkg/sftp"
	"go.yaml.in/yaml/v4"
	"golang.org/x/sync/errgroup"
	"golang.org/x/sys/unix"
	"google.golang.org/protobuf/proto"

	incus "github.com/lxc/incus/v7/client"
	internalInstance "github.com/lxc/incus/v7/internal/instance"
	"github.com/lxc/incus/v7/internal/instancewriter"
	"github.com/lxc/incus/v7/internal/jmap"
	"github.com/lxc/incus/v7/internal/linux"
	"github.com/lxc/incus/v7/internal/migration"
	"github.com/lxc/incus/v7/internal/ports"
	"github.com/lxc/incus/v7/internal/server/apparmor"
	"github.com/lxc/incus/v7/internal/server/cgroup"
	"github.com/lxc/incus/v7/internal/server/db"
	dbCluster "github.com/lxc/incus/v7/internal/server/db/cluster"
	"github.com/lxc/incus/v7/internal/server/device"
	deviceConfig "github.com/lxc/incus/v7/internal/server/device/config"
	"github.com/lxc/incus/v7/internal/server/device/nictype"
	"github.com/lxc/incus/v7/internal/server/instance"
	"github.com/lxc/incus/v7/internal/server/instance/drivers/cfg"
	"github.com/lxc/incus/v7/internal/server/instance/drivers/edk2"
	"github.com/lxc/incus/v7/internal/server/instance/drivers/qemudefault"
	"github.com/lxc/incus/v7/internal/server/instance/drivers/qmp"
	"github.com/lxc/incus/v7/internal/server/instance/instancetype"
	"github.com/lxc/incus/v7/internal/server/instance/operationlock"
	"github.com/lxc/incus/v7/internal/server/lifecycle"
	"github.com/lxc/incus/v7/internal/server/metrics"
	localMigration "github.com/lxc/incus/v7/internal/server/migration"
	"github.com/lxc/incus/v7/internal/server/network"
	"github.com/lxc/incus/v7/internal/server/operations"
	"github.com/lxc/incus/v7/internal/server/project"
	"github.com/lxc/incus/v7/internal/server/response"
	"github.com/lxc/incus/v7/internal/server/scriptlet"
	scriptletLoad "github.com/lxc/incus/v7/internal/server/scriptlet/load"
	"github.com/lxc/incus/v7/internal/server/selinux"
	"github.com/lxc/incus/v7/internal/server/state"
	storagePools "github.com/lxc/incus/v7/internal/server/storage"
	storageDrivers "github.com/lxc/incus/v7/internal/server/storage/drivers"
	localUtil "github.com/lxc/incus/v7/internal/server/util"
	localvsock "github.com/lxc/incus/v7/internal/server/vsock"
	internalUtil "github.com/lxc/incus/v7/internal/util"
	"github.com/lxc/incus/v7/internal/version"
	"github.com/lxc/incus/v7/shared/api"
	agentAPI "github.com/lxc/incus/v7/shared/api/agent"
	"github.com/lxc/incus/v7/shared/archive"
	"github.com/lxc/incus/v7/shared/ioprogress"
	"github.com/lxc/incus/v7/shared/logger"
	"github.com/lxc/incus/v7/shared/osarch"
	"github.com/lxc/incus/v7/shared/osinfo"
	"github.com/lxc/incus/v7/shared/resources"
	"github.com/lxc/incus/v7/shared/revert"
	"github.com/lxc/incus/v7/shared/subprocess"
	localtls "github.com/lxc/incus/v7/shared/tls"
	"github.com/lxc/incus/v7/shared/uefi"
	"github.com/lxc/incus/v7/shared/units"
	"github.com/lxc/incus/v7/shared/util"
)

// incus-agent files
//
//go:embed agent-loader/*
var incusAgentLoader embed.FS

// qemuSerialChardevName is used to communicate state with QEMU via QMP.
const qemuSerialChardevName = "qemu_serial-chardev"

// qemuDeviceIDPrefix used as part of the name given QEMU devices generated from user added devices.
const qemuDeviceIDPrefix = "dev-incus_"

// qemuNetDevIDPrefix used as part of the name given QEMU netdevs generated from user added devices.
const qemuNetDevIDPrefix = "incus_"

// qemuBlockDevIDPrefix used as part of the name given QEMU blockdevs generated from user added devices.
const qemuBlockDevIDPrefix = "incus_"

// qemuMountTagMaxLength defines the maximum number of characters allowed for the mount tag added to the QEMU configuration.
const qemuMountTagMaxLength = 30

// qemuMountTag9pMaxLength defines the maximum number of characters allowed for the mount tag added to the QEMU configuration when using 9p.
const qemuMountTag9pMaxLength = 25

// qemuMountTagPrefix is the prefix used for QEMU mount tags for directory shares.
const qemuMountTagPrefix = "incus_"

// qemuSparseUSBPorts is the amount of sparse USB ports for VMs.
// 4 are reserved, and the other 4 can be used for any USB device.
const qemuSparseUSBPorts = 8

var errQemuAgentOffline = errors.New("VM agent isn't currently running")

type monitorHook func(m *qmp.Monitor) error

// qemuLoad creates a Qemu instance from the supplied InstanceArgs.
func qemuLoad(s *state.State, args db.InstanceArgs, p api.Project) (instance.Instance, error) {
	// Create the instance struct.
	d := qemuInstantiate(s, args, nil, p)

	// Expand config and devices.
	err := d.expandConfig()
	if err != nil {
		return nil, err
	}

	return d, nil
}

// qemuInstantiate creates a Qemu struct without expanding config. The expandedDevices argument is
// used during device config validation when the devices have already been expanded and we do not
// have access to the profiles used to do it. This can be safely passed as nil if not required.
func qemuInstantiate(s *state.State, args db.InstanceArgs, expandedDevices deviceConfig.Devices, p api.Project) *qemu {
	d := &qemu{
		common: common{
			state: s,

			architecture: args.Architecture,
			creationDate: args.CreationDate,
			dbType:       args.Type,
			description:  args.Description,
			ephemeral:    args.Ephemeral,
			expiryDate:   args.ExpiryDate,
			id:           args.ID,
			lastUsedDate: args.LastUsedDate,
			localConfig:  args.Config,
			localDevices: args.Devices,
			logger:       logger.AddContext(logger.Ctx{"instanceType": args.Type, "instance": args.Name, "project": args.Project}),
			name:         args.Name,
			node:         args.Node,
			profiles:     args.Profiles,
			project:      p,
			isSnapshot:   args.Snapshot,
			stateful:     args.Stateful,
		},
	}

	// Get the architecture name.
	archName, err := osarch.ArchitectureName(d.architecture)
	if err == nil {
		d.architectureName = archName
	}

	// Cleanup the zero values.
	if d.expiryDate.IsZero() {
		d.expiryDate = time.Time{}
	}

	if d.creationDate.IsZero() {
		d.creationDate = time.Time{}
	}

	if d.lastUsedDate.IsZero() {
		d.lastUsedDate = time.Time{}
	}

	// This is passed during expanded config validation.
	if expandedDevices != nil {
		d.expandedDevices = expandedDevices
	}

	return d
}

// qemuCreate creates a new storage volume record and returns an initialized Instance.
// Returns a revert fail function that can be used to undo this function if a subsequent step fails.
func qemuCreate(s *state.State, args db.InstanceArgs, p api.Project, partialDeviceValidation bool, op *operations.Operation) (instance.Instance, revert.Hook, error) {
	reverter := revert.New()
	defer reverter.Fail()

	// Create the instance struct.
	d := &qemu{
		common: common{
			state: s,
			op:    op,

			architecture: args.Architecture,
			creationDate: args.CreationDate,
			dbType:       args.Type,
			description:  args.Description,
			ephemeral:    args.Ephemeral,
			expiryDate:   args.ExpiryDate,
			id:           args.ID,
			lastUsedDate: args.LastUsedDate,
			localConfig:  args.Config,
			localDevices: args.Devices,
			logger:       logger.AddContext(logger.Ctx{"instanceType": args.Type, "instance": args.Name, "project": args.Project}),
			name:         args.Name,
			node:         args.Node,
			profiles:     args.Profiles,
			project:      p,
			isSnapshot:   args.Snapshot,
			stateful:     args.Stateful,
		},
	}

	// Get the architecture name.
	archName, err := osarch.ArchitectureName(d.architecture)
	if err == nil {
		d.architectureName = archName
	}

	// Cleanup the zero values.
	if d.expiryDate.IsZero() {
		d.expiryDate = time.Time{}
	}

	if d.creationDate.IsZero() {
		d.creationDate = time.Time{}
	}

	if d.lastUsedDate.IsZero() {
		d.lastUsedDate = time.Time{}
	}

	if args.Snapshot {
		d.logger.Info("Creating instance snapshot", logger.Ctx{"ephemeral": d.ephemeral})
	} else {
		d.logger.Info("Creating instance", logger.Ctx{"ephemeral": d.ephemeral})
	}

	// Load the config.
	err = d.init()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to expand config: %w", err)
	}

	// When not a snapshot, perform full validation.
	if !args.Snapshot {
		// Validate expanded config (allows mixed instance types for profiles).
		err = instance.ValidConfig(s.OS, d.expandedConfig, true, instancetype.Any)
		if err != nil {
			return nil, nil, fmt.Errorf("Invalid config: %w", err)
		}

		err = instance.ValidDevices(s, d.project, d.Type(), d.localDevices, d.expandedDevices)
		if err != nil {
			return nil, nil, fmt.Errorf("Invalid devices: %w", err)
		}
	}

	// Retrieve the instance's storage pool.
	_, rootDiskDevice, err := d.getRootDiskDevice()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed getting root disk: %w", err)
	}

	if rootDiskDevice["pool"] == "" {
		return nil, nil, errors.New("The instance's root device is missing the pool property")
	}

	// Initialize the storage pool.
	d.storagePool, err = storagePools.LoadByName(d.state, rootDiskDevice["pool"])
	if err != nil {
		return nil, nil, fmt.Errorf("Failed loading storage pool: %w", err)
	}

	volType, err := storagePools.InstanceTypeToVolumeType(d.Type())
	if err != nil {
		return nil, nil, err
	}

	storagePoolSupported := slices.Contains(d.storagePool.Driver().Info().VolumeTypes, volType)

	if !storagePoolSupported {
		return nil, nil, errors.New("Storage pool does not support instance type")
	}

	if !d.IsSnapshot() {
		// Add devices to instance.
		cleanup, err := d.devicesAdd(d, false, partialDeviceValidation)
		if err != nil {
			return nil, nil, err
		}

		reverter.Add(cleanup)
	}

	if d.isSnapshot {
		d.logger.Info("Created instance snapshot", logger.Ctx{"ephemeral": d.ephemeral})
	} else {
		d.logger.Info("Created instance", logger.Ctx{"ephemeral": d.ephemeral})
	}

	if d.isSnapshot {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceSnapshotCreated.Event(d, nil))
	} else {
		err = d.state.Authorizer.AddInstance(d.state.ShutdownCtx, d.project.Name, d.Name())
		if err != nil {
			logger.Error("Failed to add instance to authorizer", logger.Ctx{"name": d.Name(), "project": d.project.Name, "error": err})
		}

		reverter.Add(func() { _ = d.state.Authorizer.DeleteInstance(d.state.ShutdownCtx, d.project.Name, d.Name()) })

		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceCreated.Event(d, map[string]any{
			"type":         api.InstanceTypeVM,
			"storage-pool": d.storagePool.Name(),
			"location":     d.Location(),
		}))
	}

	cleanup := reverter.Clone().Fail
	reverter.Success()

	return d, cleanup, err
}

// qemu is the QEMU virtual machine driver.
type qemu struct {
	common

	// Cached handles.
	// Do not use these variables directly, instead use their associated get functions so they
	// will be initialized on demand.
	architectureName string

	// Stateful migration streams.
	migrationReceiveStateful map[string]io.ReadWriteCloser

	// Indicate whether the root disk will be live-migrated.
	migrationRootDisk bool
	disksToMigrate    []localMigration.DependentVolumeArgs

	// Indicates whether this is an inner-cluster or cross-cluster move.
	migrationClusterMove bool

	// Keep a reference to the console socket when switching backends, so we can properly cleanup when switching back to a ring buffer.
	consoleSocket     *net.UnixListener
	consoleSocketFile *os.File

	// Keep a record of QEMU configuration.
	cmdArgs []string
	conf    []cfg.Section
}

// qmpConnect connects to the QMP monitor.
func (d *qemu) qmpConnect() (*qmp.Monitor, error) {
	return qmp.Connect(d.monitorPath(), qemuSerialChardevName, d.getMonitorEventHandler(), d.QMPLogFilePath(), qemuDetachDisk(d.state, d.id))
}

// getAgentClient returns the current agent client handle.
// Callers should check that the instance is running (and therefore mounted) before calling this function,
// otherwise the qmp.Connect call will fail to use the monitor socket file.
func (d *qemu) getAgentClient() (*http.Client, error) {
	// Check that the VM is in a state where the agent may be reachable.
	status := d.statusCode()
	if !d.isRunningStatusCode(status) || status == api.Frozen {
		return nil, errQemuAgentOffline
	}

	// Only Linux and Windows support VirtIO vsock.
	if slices.Contains([]osinfo.OSType{osinfo.FreeBSD, osinfo.MacOS}, d.GuestOS()) {
		// Get known network details.
		networks, err := d.getNetworkState()
		if err != nil {
			return nil, errQemuAgentOffline
		}

		// The connection uses mutual authentication, so use the server's key & cert for client.
		agentCert, _, clientCert, clientKey, err := d.generateAgentCert()
		if err != nil {
			return nil, err
		}

		// Get the TLS configuration.
		tlsConfig, err := localtls.GetTLSConfigMem(clientCert, clientKey, "", agentCert, false)
		if err != nil {
			return nil, err
		}

		// Setup an HTTPS client.
		client := &http.Client{}
		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
			// Replicate the headers.
			req.Header = via[len(via)-1].Header

			return nil
		}

		for _, netInterface := range networks {
			for _, address := range netInterface.Addresses {
				if address.Scope != "global" {
					continue
				}

				networkAddress := net.JoinHostPort(address.Address, strconv.Itoa(ports.HTTPSDefaultPort))

				client.Transport = &http.Transport{
					TLSClientConfig: tlsConfig,
					DialContext: func(_ context.Context, network, addr string) (net.Conn, error) {
						return net.DialTimeout("tcp", networkAddress, 100*time.Millisecond)
					},
					DisableKeepAlives:     true,
					ExpectContinueTimeout: time.Second * 3,
					ResponseHeaderTimeout: time.Second * 3600,
					TLSHandshakeTimeout:   time.Second * 3,
				}

				_, err := client.Get("https://agent/")
				if err == nil {
					return client, nil
				}
			}
		}

		return nil, errQemuAgentOffline
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, err
	}

	if !monitor.AgenStarted() || monitor.GetInstanceState() != nil {
		return nil, errQemuAgentOffline
	}

	// The connection uses mutual authentication, so use the server's key & cert for client.
	agentCert, _, clientCert, clientKey, err := d.generateAgentCert()
	if err != nil {
		return nil, err
	}

	// Existing vsock ID from volatile.
	vsockID, err := d.getVsockID()
	if err != nil {
		return nil, err
	}

	agent, err := localvsock.HTTPClient(vsockID, ports.HTTPSDefaultPort, clientCert, clientKey, agentCert)
	if err != nil {
		return nil, err
	}

	return agent, nil
}

func (d *qemu) getMonitorEventHandler() func(event string, data map[string]any) {
	// Create local variables from instance properties we need so as not to keep references to instance around
	// after we have returned the callback function.
	instProject := d.Project()
	instanceName := d.Name()
	s := d.state

	return func(event string, data map[string]any) {
		if !slices.Contains([]string{qmp.EventVMShutdown, qmp.EventVMReset, qmp.EventAgentStarted, qmp.EventAgentStopped, qmp.EventRTCChange}, event) {
			return // Don't bother loading the instance from DB if we aren't going to handle the event.
		}

		var err error
		var d *qemu // Redefine d as local variable inside callback to avoid keeping references around.

		inst := instanceRefGet(instProject.Name, instanceName)
		if inst == nil {
			inst, err = instance.LoadByProjectAndName(s, instProject.Name, instanceName)
			if err != nil {
				l := logger.AddContext(logger.Ctx{"project": instProject.Name, "instance": instanceName})
				// If DB not available, try loading from backup file.
				l.Warn("Failed loading instance from database to handle monitor event, trying backup file", logger.Ctx{"err": err})

				instancePath := filepath.Join(internalUtil.VarPath("virtual-machines"), project.Instance(instProject.Name, instanceName))
				inst, err = instance.LoadFromBackup(s, instProject.Name, instancePath, false)
				if err != nil {
					l.Error("Failed loading instance to handle monitor event", logger.Ctx{"err": err})
					return
				}
			}
		}

		d, ok := inst.(*qemu)
		if !ok {
			return
		}

		switch event {
		case qmp.EventAgentStarted:
			d.logger.Debug("Instance agent started")
			err := d.advertiseVsockAddress()
			if err != nil {
				d.logger.Warn("Failed to advertise vsock address to instance agent", logger.Ctx{"err": err})
				return
			}

			err = d.VolatileSet(map[string]string{"volatile.last_state.agent": instance.AgentStateStarted})
			if err != nil {
				d.logger.Error("Failed recording last agent state", logger.Ctx{"err": err})
			}

			s.Events.SendLifecycle(instProject.Name, lifecycle.InstanceAgentStarted.Event(d, nil))

		case qmp.EventAgentStopped:
			d.logger.Debug("Instance agent stopped")

			err = d.VolatileSet(map[string]string{"volatile.last_state.agent": instance.AgentStateStopped})
			if err != nil {
				d.logger.Error("Failed recording last agent state", logger.Ctx{"err": err})
			}

			s.Events.SendLifecycle(instProject.Name, lifecycle.InstanceAgentStopped.Event(d, nil))

		case qmp.EventVMReset:
			monitor, err := d.qmpConnect()
			if err == nil {
				if monitor.HandleReset() {
					// This RESET corresponds to a deliberate system_reset we triggered
					// (e.g. the boot-config rebuild during startup), so let QEMU handle
					// it internally rather than tearing the VM down.
					break
				}

				if !d.needsFullRestart() {
					// If a quick restart is possible, let QEMU handle it.
					d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceRestarted.Event(d, nil))

					break
				}
			}

			fallthrough
		case qmp.EventVMShutdown:
			var reason string

			target := "stop"
			entry, ok := data["reason"]
			if ok {
				entryStr, ok := entry.(string)
				if ok {
					reason = entryStr
				}
			}

			if reason == "guest-reset" {
				target = "reboot"
			}

			if reason == qmp.EventVMShutdownReasonDisconnect {
				d.logger.Warn("Instance stopped", logger.Ctx{"target": target, "reason": data["reason"]})
			} else {
				d.logger.Debug("Instance stopped", logger.Ctx{"target": target, "reason": data["reason"]})
			}

			err = d.onStop(target, reason)
			if err != nil {
				d.logger.Error("Failed to cleanly stop instance", logger.Ctx{"err": err})
				return
			}

		case qmp.EventRTCChange:
			val, ok := data["offset"].(float64)
			if !ok {
				d.logger.Debug("No offset in data", logger.Ctx{"data": data})
				return
			}

			err = d.onRTCChange(int(val))
			if err != nil {
				d.logger.Error("Failed to apply rtc change", logger.Ctx{"offset": val, "err": err})
			}
		}
	}
}

// mount the instance's config volume if needed.
func (d *qemu) mount() (*storagePools.MountInfo, error) {
	var pool storagePools.Pool
	pool, err := d.getStoragePool()
	if err != nil {
		return nil, err
	}

	if d.IsSnapshot() {
		mountInfo, err := pool.MountInstanceSnapshot(d, nil)
		if err != nil {
			return nil, err
		}

		return mountInfo, nil
	}

	mountInfo, err := pool.MountInstance(d, nil)
	if err != nil {
		return nil, err
	}

	return mountInfo, nil
}

// unmount the instance's config volume if needed.
func (d *qemu) unmount() error {
	pool, err := d.getStoragePool()
	if err != nil {
		return err
	}

	err = pool.UnmountInstance(d, nil)
	if err != nil {
		return err
	}

	return nil
}

// generateAgentCert creates the necessary server key and certificate if needed.
func (d *qemu) generateAgentCert() (string, string, string, string, error) {
	agentCertFile := filepath.Join(d.Path(), "agent.crt")
	agentKeyFile := filepath.Join(d.Path(), "agent.key")
	clientCertFile := filepath.Join(d.Path(), "agent-client.crt")
	clientKeyFile := filepath.Join(d.Path(), "agent-client.key")

	// Create server certificate.
	err := localtls.FindOrGenCert(agentCertFile, agentKeyFile, false, false)
	if err != nil {
		return "", "", "", "", err
	}

	// Create client certificate.
	err = localtls.FindOrGenCert(clientCertFile, clientKeyFile, true, false)
	if err != nil {
		return "", "", "", "", err
	}

	// Read all the files
	agentCert, err := os.ReadFile(agentCertFile)
	if err != nil {
		return "", "", "", "", err
	}

	agentKey, err := os.ReadFile(agentKeyFile)
	if err != nil {
		return "", "", "", "", err
	}

	clientCert, err := os.ReadFile(clientCertFile)
	if err != nil {
		return "", "", "", "", err
	}

	clientKey, err := os.ReadFile(clientKeyFile)
	if err != nil {
		return "", "", "", "", err
	}

	return string(agentCert), string(agentKey), string(clientCert), string(clientKey), nil
}

// Freeze freezes the instance.
func (d *qemu) Freeze() error {
	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	// Send the stop command.
	err = monitor.Pause()
	if err != nil {
		return err
	}

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstancePaused.Event(d, nil))
	return nil
}

// configDriveMountPath returns the path for the config drive bind mount.
func (d *qemu) configDriveMountPath() string {
	return filepath.Join(d.DevicesPath(), "config.mount")
}

// configDriveMountPathClear attempts to unmount the config drive bind mount and remove the directory.
func (d *qemu) configDriveMountPathClear() error {
	return device.DiskMountClear(d.configDriveMountPath())
}

// pidWait waits for the QEMU process to exit. Does this in a way that doesn't require the process to be a
// parent of the QEMU process (in order to allow for the daemon to be restarted after the VM was started).
// Returns true if process stopped, false if timeout was exceeded.
func (d *qemu) pidWait(timeout time.Duration) bool {
	waitUntil := time.Now().Add(timeout)
	for {
		pid, _ := d.pid()
		if pid <= 0 {
			break
		}

		if time.Now().After(waitUntil) {
			return false
		}

		time.Sleep(time.Millisecond * time.Duration(250))
	}

	return true
}

// onStop is run when the instance stops.
func (d *qemu) onStop(target string, reason string) error {
	d.logger.Debug("onStop hook started", logger.Ctx{"target": target, "reason": reason})
	defer d.logger.Debug("onStop hook finished", logger.Ctx{"target": target, "reason": reason})

	// Create/pick up operation.
	op, err := d.onStopOperationSetup(target)
	if err != nil {
		return err
	}

	// Unlock on return
	defer op.Done(nil)

	// Set operation if missing.
	if d.op == nil {
		d.op = op.GetOperation()
	}

	// If QEMU is still running, stop it (handles reboot).
	monitor, err := qmp.Connect(d.monitorPath(), qemuSerialChardevName, nil, d.QMPLogFilePath(), qemuDetachDisk(d.state, d.id))
	if err == nil {
		_ = monitor.Quit()
	}

	// Wait for QEMU process to end (to avoiding racing start when restarting).
	// Wait up to 5 minutes to allow for flushing any pending data to disk.
	d.logger.Debug("Waiting for VM process to finish")
	waitTimeout := time.Minute * 5
	if d.pidWait(waitTimeout) {
		d.logger.Debug("VM process finished")
	} else {
		// Log a warning, but continue clean up as best we can.
		d.logger.Error("VM process failed to stop", logger.Ctx{"timeout": waitTimeout})
	}

	// Fully cleanup the existing QEMU monitor.
	if monitor != nil {
		monitor.Disconnect()
	}

	// Record power state.
	err = d.VolatileSet(map[string]string{
		"volatile.last_state.power": instance.PowerStateStopped,
		"volatile.last_state.ready": "false",
	})
	if err != nil {
		// Don't return an error here as we still want to cleanup the instance even if DB not available.
		d.logger.Error("Failed recording last power state", logger.Ctx{"err": err})
	}

	// Cleanup.
	d.cleanupDevices() // Must be called before unmount.
	_ = os.Remove(d.pidFilePath())
	_ = os.Remove(d.monitorPath())
	_ = os.Remove(d.spicePath())

	// Stop the storage for the instance.
	err = d.unmount()
	if err != nil && !errors.Is(err, storageDrivers.ErrInUse) {
		err = fmt.Errorf("Failed unmounting instance: %w", err)
		op.Done(err)
		return err
	}

	// Unload the apparmor profile
	err = apparmor.InstanceUnload(d.state.OS, d)
	if err != nil {
		op.Done(err)
		return err
	}

	// Determine if instance should be auto-restarted.
	cleanShutdown := reason == qmp.EventVMShutdownReasonGuestShutdown || reason == qmp.EventVMShutdownReasonQuit

	var autoRestart bool
	if target != "reboot" && !cleanShutdown && d.shouldAutoRestart() {
		autoRestart = true

		// Mark current shutdown as complete.
		op.Done(nil)

		// Create a new restart operation.
		op, err = operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionRestart, nil, true, false)
		if err == nil {
			defer op.Done(nil)
		} else {
			d.logger.Error("Failed to setup new restart operation", logger.Ctx{"err": err})
		}
	}

	// Log and emit lifecycle if not user triggered.
	if target != "reboot" && !autoRestart && op.Action() != operationlock.ActionMigrate && op.Action() != operationlock.ActionRestart {
		if op.GetInstanceInitiated() {
			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceShutdown.Event(d, nil))
		} else {
			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStopped.Event(d, nil))
		}

		// Only trigger if agent state not stopped and update accordingly
		if d.LocalConfig()["volatile.last_state.agent"] == instance.AgentStateStarted {
			err = d.VolatileSet(map[string]string{"volatile.last_state.agent": instance.AgentStateStopped})
			if err != nil {
				d.logger.Error("Failed recording last ready state", logger.Ctx{"err": err})
			}

			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceAgentStopped.Event(d, nil))
		}
	}

	// Reboot the instance.
	if target == "reboot" || autoRestart {
		err = d.Start(false)
		if err != nil {
			op.Done(err)
			return err
		}

		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceRestarted.Event(d, nil))
	} else if d.ephemeral {
		// Destroy ephemeral virtual machines.
		err = d.delete(true, true)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	return nil
}

// Shutdown shuts the instance down.
func (d *qemu) Shutdown(timeout time.Duration) error {
	d.logger.Debug("Shutdown started", logger.Ctx{"timeout": timeout})
	defer d.logger.Debug("Shutdown finished", logger.Ctx{"timeout": timeout})

	// Must be run prior to creating the operation lock.
	statusCode := d.statusCode()
	if !d.isRunningStatusCode(statusCode) {
		if statusCode == api.Error {
			return fmt.Errorf("The instance cannot be cleanly shutdown as in %s status", statusCode)
		}

		return ErrInstanceIsStopped
	}

	// Save the console log from ring buffer before the instance is shutdown. Must be run prior to creating the operation lock.
	_, err := d.ConsoleLog()
	if err != nil {
		return err
	}

	// Setup a new operation.
	// Allow inheriting of ongoing restart operation (we are called from restartCommon).
	// Allow reuse when creating a new stop operation. This allows the Stop() function to inherit operation.
	// Allow reuse of a reusable ongoing stop operation as Shutdown() may be called earlier, which allows reuse
	// of its operations. This allow for multiple Shutdown() attempts.
	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionStop, []operationlock.Action{operationlock.ActionRestart}, true, true)
	if err != nil {
		if errors.Is(err, operationlock.ErrNonReusuableSucceeded) {
			// An existing matching operation has now succeeded, return.
			return nil
		}

		return err
	}

	// If frozen, resume so the signal can be handled.
	if d.IsFrozen() {
		err := d.Unfreeze()
		if err != nil {
			return err
		}
	}

	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		op.Done(err)
		return err
	}

	// Indicate to the onStop hook that if the VM stops it was due to a clean shutdown because the VM responded
	// to the powerdown request.
	op.SetInstanceInitiated(true)

	// Send the system_powerdown command.
	err = monitor.Powerdown()
	if err != nil {
		if errors.Is(err, qmp.ErrMonitorDisconnect) {
			op.Done(nil)
			return nil
		}

		op.Done(err)
		return err
	}

	// Wait 500ms for the first event to be received by the guest.
	time.Sleep(500 * time.Millisecond)

	// Attempt to send a second system_powerdown command (required to get Windows to shutdown).
	_ = monitor.Powerdown()

	d.logger.Debug("Shutdown request sent to instance")

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	// Wait for operation lock to be Done or context to timeout. The operation lock is normally completed by
	// onStop which picks up the same lock and then marks it as Done after the instance stops and the devices
	// have been cleaned up. However if the operation has failed for another reason we collect the error here.
	err = op.Wait(ctx)
	status := d.statusCode()
	if status != api.Stopped {
		errPrefix := fmt.Errorf("Failed shutting down instance, status is %q", status)

		if err != nil {
			return fmt.Errorf("%s: %w", errPrefix.Error(), err)
		}

		return errPrefix
	}

	// Now handle errors from shutdown sequence and return to caller if wasn't completed cleanly.
	if err != nil {
		return err
	}

	return nil
}

// Restart restart the instance.
func (d *qemu) Restart(timeout time.Duration) error {
	return d.restartCommon(d, timeout)
}

// Rebuild rebuilds the instance using the supplied image fingerprint as source.
func (d *qemu) Rebuild(img *api.Image, op *operations.Operation) error {
	return d.rebuildCommon(d, img, op)
}

// killQemuProcess kills specified process. Optimistically attempts to wait for the process to fully exit, but does
// not return an error if the Wait call fails. This is because this function is used in scenarios where the daemon has
// been restarted after the VM has been started and is no longer the parent of the QEMU process.
// The caller should use another method to ensure that the QEMU process has fully exited instead.
// Returns an error if the Kill signal couldn't be sent to the process (for any other reason apart from the process
// not existing).
func (d *qemu) killQemuProcess(pid int) error {
	proc, err := os.FindProcess(pid)
	if err != nil {
		if errors.Is(err, os.ErrProcessDone) {
			return nil
		}

		return err
	}

	err = proc.Kill()
	if err != nil {
		if errors.Is(err, os.ErrProcessDone) {
			return nil
		}

		return err
	}

	// Wait for process to exit, but don't return an error if this fails as it may be called when the daemon isn't
	// the parent of the process, and we have still sent the kill signal as per the function's description.
	_, err = proc.Wait()
	if err != nil {
		if strings.Contains(err.Error(), "no child processes") {
			return nil
		}

		d.logger.Warn("Failed to collect VM process exit status", logger.Ctx{"pid": pid, "err": err})
	}

	return nil
}

// restoreState restores the VM state from a file handle.
func (d *qemu) restoreStateHandle(ctx context.Context, monitor *qmp.Monitor, f *os.File) error {
	err := monitor.SendFile("migration", f)
	if err != nil {
		return err
	}

	err = monitor.MigrateIncoming(ctx, "migration")
	if err != nil {
		if errors.Is(err, qmp.ErrMonitorDisconnect) && util.PathExists(d.LogFilePath()) {
			qemuError, err := os.ReadFile(d.LogFilePath())
			if err != nil {
				return err
			}

			return fmt.Errorf("QEMU crashed on VM restore: %s", string(qemuError))
		}

		return err
	}

	return nil
}

// receiveMigrationSnapshot handles an incoming disk snapshot during migration.
func (d *qemu) receiveMigrationSnapshot(monitor *qmp.Monitor, blockExport string, filesystemConn io.ReadWriteCloser) error {
	nbdConn, err := monitor.NBDServerStart("", 1)
	if err != nil {
		return fmt.Errorf("Failed starting NBD server: %w", err)
	}

	d.logger.Debug("Migration NBD server started")

	defer func() {
		_ = nbdConn.Close()
		_ = monitor.NBDServerStop()
	}()

	err = monitor.NBDBlockExportAdd(blockExport, blockExport, true, "", nil)
	if err != nil {
		return fmt.Errorf("Failed adding root disk to NBD server: %w", err)
	}

	d.logger.Debug("Migration storage NBD export starting")

	go func() { _, _ = util.SafeCopy(filesystemConn, nbdConn) }()

	_, _ = util.SafeCopy(nbdConn, filesystemConn)

	filesystemConn.Close()
	d.logger.Debug("Migration storage NBD export finished")

	return nil
}

// restoreState restores VM state from state file or from migration source if d.migrationReceiveStateful set.
func (d *qemu) restoreState(monitor *qmp.Monitor) error {
	if d.migrationReceiveStateful != nil {
		stateConn := d.migrationReceiveStateful[api.SecretNameState]
		if stateConn == nil {
			return errors.New("Migration state connection is not initialized")
		}

		// Perform non-shared storage transfer if requested.
		filesystemConn := d.migrationReceiveStateful[api.SecretNameFilesystem]
		if filesystemConn != nil {
			go func() {
				if d.migrationRootDisk {
					rootDiskName, _, err := internalInstance.GetRootDiskDevice(d.expandedDevices.CloneNative())
					if err != nil {
						d.logger.Error("Failed getting instance root disk", logger.Ctx{"err": err})
						return
					}

					escapedDeviceName := linux.PathNameEncode(rootDiskName)
					rootDiskName = d.blockNodeName(escapedDeviceName)

					err = d.receiveMigrationSnapshot(monitor, rootDiskName, filesystemConn)
					if err != nil {
						d.logger.Error("Failed receiving migration snapshot", logger.Ctx{"err": err})
						return
					}
				}

				pool, err := d.getStoragePool()
				if err != nil {
					d.logger.Error("Failed fetching instance storage pool", logger.Ctx{"err": err})
					return
				}

				config, err := pool.GenerateInstanceBackupConfig(d, false, true, nil)
				if err != nil {
					d.logger.Error("Failed generating instance backup config", logger.Ctx{"err": err})
					return
				}

				devicesMap := storagePools.DevicesMapFromBackupConfig(config)

				for _, vol := range d.disksToMigrate {
					d.logger.Debug("Receiving dependent volume", logger.Ctx{"name": vol.Name, "pool": vol.Pool})
					deviceName := storagePools.DeviceByPoolAndVolume(devicesMap, vol.Pool, vol.Name)
					if deviceName == "" {
						d.logger.Error("Failed to find requested device", logger.Ctx{"pool": vol.Pool, "volName": vol.Name})
						return
					}

					diskName := d.blockNodeName(linux.PathNameEncode(deviceName))

					err = d.receiveMigrationSnapshot(monitor, diskName, filesystemConn)
					if err != nil {
						d.logger.Error("Failed receiving migration snapshot", logger.Ctx{"err": err})
					}
				}
			}()
		}

		// Receive checkpoint from QEMU process on source.
		d.logger.Debug("Stateful migration checkpoint receive starting")
		pipeRead, pipeWrite, err := os.Pipe()
		if err != nil {
			return err
		}

		go func() {
			_, _ = util.SafeCopy(pipeWrite, stateConn)

			_ = pipeRead.Close()
			_ = pipeWrite.Close()
		}()

		err = d.restoreStateHandle(context.Background(), monitor, pipeRead)
		if err != nil {
			return fmt.Errorf("Failed restoring checkpoint from source: %w", err)
		}

		d.logger.Debug("Stateful migration checkpoint receive finished")
	} else {
		statePath := d.StatePath()
		d.logger.Debug("Stateful checkpoint restore starting", logger.Ctx{"source": statePath})
		defer d.logger.Debug("Stateful checkpoint restore finished", logger.Ctx{"source": statePath})

		stateFile, err := os.Open(statePath)
		if err != nil {
			return fmt.Errorf("Failed opening state file %q: %w", statePath, err)
		}

		defer logger.WarnOnError(stateFile.Close, "Failed to close state file")

		uncompressedState, err := pgzip.NewReaderN(stateFile, 1<<20, archive.CompressionThreads())
		if err != nil {
			return fmt.Errorf("Failed opening state gzip reader: %w", err)
		}

		defer logger.WarnOnError(uncompressedState.Close, "Failed to close state reader")

		pipeRead, pipeWrite, err := os.Pipe()
		if err != nil {
			return err
		}

		go func() {
			_, err := util.SafeCopy(pipeWrite, uncompressedState)
			if err != nil {
				d.logger.Warn("Failed reading from state file", logger.Ctx{"path": statePath, "err": err})
			}

			_ = pipeRead.Close()
			_ = pipeWrite.Close()
		}()

		err = d.restoreStateHandle(context.Background(), monitor, pipeRead)
		if err != nil {
			return fmt.Errorf("Failed restoring state from %q: %w", stateFile.Name(), err)
		}
	}

	return nil
}

// saveStateHandle dumps the current VM state to a file handle.
// Once started, the VM is in a paused state and it's up to the caller to wait for the transfer to complete and
// resume or kill the VM guest.
func (d *qemu) saveStateHandle(monitor *qmp.Monitor, f *os.File) error {
	// Send the target file to qemu.
	err := monitor.SendFile("migration", f)
	if err != nil {
		return err
	}

	// Issue the migration command.
	err = monitor.Migrate("migration")
	if err != nil {
		return err
	}

	return nil
}

// saveState dumps the current VM state to the state file.
// Once dumped, the VM is in a paused state and it's up to the caller to resume or kill it.
func (d *qemu) saveState(monitor *qmp.Monitor) error {
	statePath := d.StatePath()
	d.logger.Debug("Stateful checkpoint starting", logger.Ctx{"target": statePath})
	defer d.logger.Debug("Stateful checkpoint finished", logger.Ctx{"target": statePath})

	// Save the checkpoint to state file.
	_ = os.Remove(statePath)

	// Prepare the state file.
	stateFile, err := os.Create(statePath)
	if err != nil {
		return err
	}

	defer logger.WarnOnError(stateFile.Close, "Failed to close state file")

	compressedState, err := pgzip.NewWriterLevel(stateFile, pgzip.BestSpeed)
	if err != nil {
		return err
	}

	err = compressedState.SetConcurrency(1<<20, archive.CompressionThreads())
	if err != nil {
		return err
	}

	defer logger.WarnOnError(compressedState.Close, "Failed to close state writer")

	pipeRead, pipeWrite, err := os.Pipe()
	if err != nil {
		return err
	}

	defer func() {
		_ = pipeRead.Close()
		_ = pipeWrite.Close()
	}()

	go func() { _, _ = util.SafeCopy(compressedState, pipeRead) }()

	err = d.saveStateHandle(monitor, pipeWrite)
	if err != nil {
		return fmt.Errorf("Failed initializing state save to %q: %w", stateFile.Name(), err)
	}

	err = monitor.MigrateWait(context.Background(), "completed")
	if err != nil {
		return fmt.Errorf("Failed saving state to %q: %w", stateFile.Name(), err)
	}

	return nil
}

// validateStartup checks any constraints that would prevent start up from succeeding under normal circumstances.
func (d *qemu) validateStartup(stateful bool, statusCode api.StatusCode) error {
	err := d.common.validateStartup(stateful, statusCode)
	if err != nil {
		return err
	}

	// Cannot perform stateful start unless config is appropriately set.
	// NOTE: We can't use CanLiveMigrate during instance startup as the boot state hasn't yet been recorded.
	if stateful && util.IsFalseOrEmpty(d.expandedConfig["migration.stateful"]) {
		return errors.New("Stateful start requires migration.stateful to be set to true")
	}

	// gendoc:generate(entity=image, group=requirements, key=requirements.secureboot)
	//
	// ---
	//  type: bool
	//  shortdesc: If set to `false`, indicates that the image cannot boot under secure boot.
	//
	// Ensure secureboot is turned off for images that are not secureboot enabled.
	if util.IsFalse(d.localConfig["image.requirements.secureboot"]) && util.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) {
		return errors.New("The image used by this instance is incompatible with secureboot. Please set security.secureboot=false on the instance")
	}

	// Ensure secureboot is turned off when CSM is on.
	if util.IsTrue(d.expandedConfig["security.csm"]) && util.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) {
		return errors.New("Secure boot can't be enabled while CSM is turned on. Please set security.secureboot=false on the instance")
	}

	// gendoc:generate(entity=image, group=requirements, key=requirements.cdrom_agent)
	//
	// ---
	//  type: bool
	//  shortdesc: If set to `true`, indicates that the VM requires an `agent:config` disk be added.
	//
	// Ensure an agent drive is present if the image requires it.
	if util.IsTrue(d.localConfig["image.requirements.cdrom_agent"]) {
		found := false
		for _, dev := range d.expandedDevices {
			if dev["type"] == "disk" && dev["source"] == "agent:config" {
				found = true
				break
			}
		}

		if !found {
			return errors.New("This virtual machine image requires an agent:config disk be added")
		}
	}

	// gendoc:generate(entity=image, group=requirements, key=requirements.cdrom_cloud_init)
	//
	// ---
	//  type: bool
	//  shortdesc: If set to `true`, indicates that the VM requires a `cloud-init:config` disk be present every time `cloud-init` should be run.
	//
	// Ensure a `cloud-init` drive is present if the image and the VM state require it.
	if util.IsTrue(d.localConfig["image.requirements.cdrom_cloud_init"]) && d.localConfig["volatile.apply_template"] != "" {
		found := false
		for _, dev := range d.expandedDevices {
			if dev["type"] == "disk" && dev["source"] == "cloud-init:config" {
				found = true
				break
			}
		}

		if !found {
			return errors.New("This virtual machine image requires a cloud-init:config disk be added")
		}
	}

	return nil
}

func (d *qemu) checkStateStorage() error {
	// For some operations, the "size.state" of the instance root disk device must be larger than the instance memory.
	// Otherwise, there will not be enough disk space to write the instance state to disk during any subsequent stops.
	// (Only check when migration.stateful is true, otherwise the memory won't be dumped when this instance stops).

	_, rootDiskDevice, err := d.getRootDiskDevice()
	if err != nil {
		return err
	}

	// Don't access d.storagePool directly since it isn't populated at this stage.
	pool, err := d.getStoragePool()
	if err != nil {
		return err
	}

	stateDiskSizeStr := pool.Driver().Info().DefaultVMBlockFilesystemSize
	if rootDiskDevice["size.state"] != "" {
		stateDiskSizeStr = rootDiskDevice["size.state"]
	}

	stateDiskSize, err := units.ParseByteSizeString(stateDiskSizeStr)
	if err != nil {
		return err
	}

	memoryLimitStr := qemudefault.MemSize
	if d.expandedConfig["limits.memory"] != "" {
		memoryLimitStr = d.expandedConfig["limits.memory"]
	}

	memoryLimit, err := ParseMemoryStr(memoryLimitStr)
	if err != nil {
		return err
	}

	if stateDiskSize < memoryLimit {
		return errors.New("Stateful stop and snapshots require the instance limits.memory be less than or equal to the root disk size.state property")
	}

	return nil
}

// Start starts the instance.
func (d *qemu) Start(stateful bool) error {
	return d.start(stateful, nil)
}

// runStartupScriptlet runs startup scriptlets at config, early, pre-start and post-start stages.
func (d *qemu) runStartupScriptlet(monitor *qmp.Monitor, stage string) error {
	_, ok := d.expandedConfig["raw.qemu.scriptlet"]
	if ok {
		// Render cannot return errors here.
		render, _, _ := d.Render()
		instanceData, ok := render.(*api.Instance)
		if !ok {
			return errors.New("Unexpected instance type")
		}

		err := scriptlet.QEMURun(logger.Log, instanceData, &d.cmdArgs, &d.conf, monitor, stage)
		if err != nil {
			err = fmt.Errorf("Failed running QEMU scriptlet at %s stage: %w", stage, err)
			return err
		}
	}

	return nil
}

// startupHook executes QMP commands and runs startup scriptlets at early, pre-start and post-start
// stages.
func (d *qemu) startupHook(monitor *qmp.Monitor, stage string) error {
	commands, ok := d.expandedConfig["raw.qemu.qmp."+stage]
	if ok {
		var commandList []map[string]any
		err := json.Unmarshal([]byte(commands), &commandList)
		if err != nil {
			err = fmt.Errorf("Failed to parse QMP commands at %s stage (expected JSON list of objects): %w", stage, err)
			return err
		}

		for _, command := range commandList {
			id := monitor.IncreaseID()
			command["id"] = id

			var jsonCommand []byte
			jsonCommand, err = json.Marshal(command)
			if err != nil {
				err = fmt.Errorf("Failed to marshal command at %s stage: %w", stage, err)
				return err
			}

			err = monitor.RunJSON(jsonCommand, nil, true, id)
			if err != nil {
				err = fmt.Errorf("Failed to run QMP command %s at %s stage: %w", jsonCommand, stage, err)
				return err
			}
		}
	}

	return d.runStartupScriptlet(monitor, stage)
}

// start starts the instance and can use an existing InstanceOperation lock.
func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error {
	d.logger.Debug("Start started", logger.Ctx{"stateful": stateful})
	defer d.logger.Debug("Start finished", logger.Ctx{"stateful": stateful})

	// Check that we are startable before creating an operation lock.
	// Must happen before creating operation Start lock to avoid the status check returning Stopped due to the
	// existence of a Start operation lock.
	err := d.validateStartup(stateful, d.statusCode())
	if err != nil {
		return err
	}

	// Setup a new operation if needed.
	if op == nil {
		op, err = operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionStart, []operationlock.Action{operationlock.ActionRestart, operationlock.ActionRestore}, false, false)
		if err != nil {
			if errors.Is(err, operationlock.ErrNonReusuableSucceeded) {
				// An existing matching operation has now succeeded, return.
				return nil
			}

			return fmt.Errorf("Failed to create instance start operation: %w", err)
		}
	}

	defer op.Done(err)

	// Record (or load) boot state.
	bs := &qemuBootState{
		Version: qemuBootStateVersion,
	}

	if stateful {
		bs, err = d.getBootState()
		if err != nil {
			return err
		}
	}

	// Assign NUMA node(s) if needed.
	if d.expandedConfig["limits.cpu.nodes"] == "balanced" {
		err := d.balanceNUMANodes()
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Ensure the correct vhost_vsock kernel module is loaded before establishing the vsock.
	err = linux.LoadModule("vhost_vsock")
	if err != nil {
		op.Done(err)
		return err
	}

	reverter := revert.New()
	defer reverter.Fail()

	// Rotate the log files.
	for _, logfile := range []string{d.LogFilePath(), d.ConsoleBufferLogPath(), d.QMPLogFilePath()} {
		if util.PathExists(logfile) {
			_ = os.Remove(logfile + ".old")
			err := os.Rename(logfile, logfile+".old")
			if err != nil && !errors.Is(err, fs.ErrNotExist) {
				op.Done(err)
				return err
			}
		}
	}

	// Remove old pid file if needed.
	if util.PathExists(d.pidFilePath()) {
		err = os.Remove(d.pidFilePath())
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Failed removing old PID file %q: %w", d.pidFilePath(), err)
		}
	}

	// Cleanup old sockets.
	for _, socketPath := range []string{d.consolePath(), d.spicePath(), d.monitorPath(), d.nbdPath()} {
		_ = os.Remove(socketPath)
	}

	// Mount the instance's config volume.
	mountInfo, err := d.mount()
	if err != nil {
		op.Done(err)
		return err
	}

	reverter.Add(func() { _ = d.unmount() })

	// Define a set of files to open and pass their file descriptors to QEMU command.
	fdFiles := make([]*os.File, 0)

	// Ensure passed files are closed after start has returned (either because QEMU has started or on error).
	defer func() {
		for _, file := range fdFiles {
			_ = file.Close()
		}
	}()

	// New or existing vsock ID from volatile.
	vsockID, vsockF, err := d.nextVsockID()
	if err != nil {
		return err
	}

	// Add allocated QEMU vhost file descriptor.
	vsockFD := d.addFileDescriptor(&fdFiles, vsockF)

	volatileSet := make(map[string]string)

	if !stateful {
		volatileSet["volatile.vm.needs_reset"] = ""
	}

	// Update vsock ID in volatile if needed for recovery (do this before UpdateBackupFile() call).
	oldVsockID := d.localConfig["volatile.vsock_id"]
	newVsockID := strconv.FormatUint(uint64(vsockID), 10)
	if oldVsockID != newVsockID {
		volatileSet["volatile.vsock_id"] = newVsockID
	}

	// Generate UUID if not present (do this before UpdateBackupFile() call).
	instUUID := d.localConfig["volatile.uuid"]
	if instUUID == "" {
		instUUID = uuid.New().String()
		volatileSet["volatile.uuid"] = instUUID
	}

	// For a VM instance, we must also set the VM generation ID.
	vmGenUUID := d.localConfig["volatile.uuid.generation"]
	if vmGenUUID == "" {
		vmGenUUID = instUUID
		volatileSet["volatile.uuid.generation"] = vmGenUUID
	}

	// Generate the config drive.
	err = d.generateConfigShare(volatileSet)
	if err != nil {
		op.Done(err)
		return err
	}

	// Create all needed paths.
	err = os.MkdirAll(d.LogPath(), 0o700)
	if err != nil {
		op.Done(err)
		return err
	}

	err = os.MkdirAll(d.RunPath(), 0o700)
	if err != nil {
		op.Done(err)
		return err
	}

	err = os.MkdirAll(d.DevicesPath(), 0o711)
	if err != nil {
		op.Done(err)
		return err
	}

	err = os.MkdirAll(d.ShmountsPath(), 0o711)
	if err != nil {
		op.Done(err)
		return err
	}

	// Copy EDK2 settings firmware to nvram file if needed.
	// Set up EDK2 NVRAM when on EFI.
	if d.architectureSupportsUEFI(d.architecture) {
		fi, err := os.Lstat(d.nvramPath())
		if err != nil && !errors.Is(err, fs.ErrNotExist) {
			return err
		}

		// Generate new NVRAM if missing, or if requested by the user or if the NVRAM file is of an invalid format (needs to be a valid symlink).
		if util.IsTrue(d.localConfig["volatile.apply_nvram"]) || fi == nil || fi.Mode()&os.ModeSymlink != os.ModeSymlink {
			err = d.setupNvram()
			if err != nil {
				op.Done(err)
				return err
			}
		}
	}

	// Clear volatile.apply_nvram if set.
	if d.localConfig["volatile.apply_nvram"] != "" {
		volatileSet["volatile.apply_nvram"] = ""
	}

	// Apply any volatile changes that need to be made.
	err = d.VolatileSet(volatileSet)
	if err != nil {
		err = fmt.Errorf("Failed setting volatile keys: %w", err)
		op.Done(err)
		return err
	}

	devConfs := make([]*deviceConfig.RunConfig, 0, len(d.expandedDevices))
	postStartHooks := []func() error{}

	sortedDevices := d.expandedDevices.Sorted()
	startDevices := make([]device.Device, 0, len(sortedDevices))

	// Load devices in sorted order, this ensures that device mounts are added in path order.
	// Loading all devices first means that validation of all devices occurs before starting any of them.
	for _, entry := range sortedDevices {
		dev, err := d.deviceLoad(d, entry.Name, entry.Config, false)
		if err != nil {
			if errors.Is(err, device.ErrUnsupportedDevType) {
				continue // Skip unsupported device (allows for mixed instance type profiles).
			}

			err = fmt.Errorf("Failed start validation for device %q: %w", entry.Name, err)
			op.Done(err)
			return err
		}

		// Run pre-start of check all devices before starting any device to avoid expensive revert.
		err = dev.PreStartCheck()
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Failed pre-start check for device %q: %w", dev.Name(), err)
		}

		startDevices = append(startDevices, dev)
	}

	// Start devices in order.
	for i := range startDevices {
		dev := startDevices[i] // Local var for revert.

		// Start the device.
		runConf, err := d.deviceStart(dev, false)
		if err != nil {
			err = fmt.Errorf("Failed to start device %q: %w", dev.Name(), err)
			op.Done(err)
			return err
		}

		reverter.Add(func() {
			err := d.deviceStop(dev, false, "")
			if err != nil {
				d.logger.Error("Failed to cleanup device", logger.Ctx{"device": dev.Name(), "err": err})
			}
		})

		if runConf == nil {
			continue
		}

		if runConf.Revert != nil {
			reverter.Add(runConf.Revert)
		}

		// Add post-start hooks
		if len(runConf.PostHooks) > 0 {
			postStartHooks = append(postStartHooks, runConf.PostHooks...)
		}

		devConfs = append(devConfs, runConf)
	}

	// Setup the config drive readonly bind mount. Important that this come after the root disk device start.
	// in order to allow unmounts triggered by deferred resizes of the root volume.
	configMntPath := d.configDriveMountPath()
	err = d.configDriveMountPathClear()
	if err != nil {
		err = fmt.Errorf("Failed cleaning config drive mount path %q: %w", configMntPath, err)
		op.Done(err)
		return err
	}

	err = os.Mkdir(configMntPath, 0o700)
	if err != nil {
		err = fmt.Errorf("Failed creating device mount path %q for config drive: %w", configMntPath, err)
		op.Done(err)
		return err
	}

	reverter.Add(func() { _ = d.configDriveMountPathClear() })

	// Mount the config drive device as readonly. This way it will be readonly irrespective of whether its
	// exported via 9p for virtio-fs.
	configSrcPath := filepath.Join(d.Path(), "config")
	err = device.DiskMount(configSrcPath, configMntPath, false, "", []string{"ro"}, "none")
	if err != nil {
		err = fmt.Errorf("Failed mounting device mount path %q for config drive: %w", configMntPath, err)
		op.Done(err)
		return err
	}

	// Get qemu configuration and check qemu is installed.
	qemuPath, qemuBus, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		op.Done(err)
		return err
	}

	// Snapshot if needed.
	snapName, expiry, err := d.getStartupSnapNameAndExpiry(d)
	if err != nil {
		err = fmt.Errorf("Failed getting startup snapshot info: %w", err)
		op.Done(err)
		return err
	}

	if snapName != "" && expiry != nil {
		err := d.snapshot(snapName, *expiry, false)
		if err != nil {
			err = fmt.Errorf("Failed taking startup snapshot: %w", err)
			op.Done(err)
			return err
		}
	}

	// Setup the CPU.
	if bs.CPUTopology == nil {
		// Get the CPU topology.
		cpuTopology, err := d.cpuTopology()
		if err != nil {
			return err
		}

		bs.CPUTopology = cpuTopology
	} else if !bs.CPUTopology.Explicit {
		// Re-compute the topology if the current configuration uses CPU pinning.
		// The pins are host-specific so may have changed, but the topology must keep the same shape.
		cpuTopology, err := d.cpuTopology()
		if err != nil {
			return err
		}

		if cpuTopology.VCPUs != nil {
			if cpuTopology.Sockets != bs.CPUTopology.Sockets || cpuTopology.Cores != bs.CPUTopology.Cores || cpuTopology.Threads != bs.CPUTopology.Threads {
				err = errors.New("Current CPU topology doesn't match the topology the instance was started with")
				op.Done(err)
				return err
			}

			bs.CPUTopology = cpuTopology
		}
	}

	if bs.CPUType == "" {
		cpuType, err := d.cpuType(bs)
		if err != nil {
			return err
		}

		bs.CPUType = cpuType
	}

	// Setup the memory.
	if bs.MemoryTopology == nil {
		// Get the memory topology.
		memoryTopology, err := d.memoryTopology(bs)
		if err != nil {
			return err
		}

		bs.MemoryTopology = memoryTopology
	}

	// Generate the QEMU configuration.
	monHooks, err := d.generateQemuConfig(bs, mountInfo, qemuBus, vsockFD, devConfs, &fdFiles)
	if err != nil {
		op.Done(err)
		return err
	}

	confFile := filepath.Join(d.RunPath(), "qemu.conf")
	// Start QEMU.
	qemuArgs := []string{
		"-S",
		"-name", d.Name(),
		"-uuid", instUUID,
		"-daemonize",
		"-cpu", bs.CPUType,
		"-nographic",
		"-serial", "chardev:console",
		"-nodefaults",
		"-no-user-config",
		"-sandbox", "on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=deny",
		"-readconfig", confFile,
		"-pidfile", d.pidFilePath(),
		"-D", d.LogFilePath(),
	}

	// Get the feature flags.
	info := DriverStatuses()[instancetype.VM].Info
	_, spiceSupported := info.Features["spice"]
	if spiceSupported {
		spiceConfig, err := d.spiceCmdlineConfig(&fdFiles)
		if err != nil {
			op.Done(err)
			return err
		}

		qemuArgs = append(qemuArgs, "-spice", spiceConfig)
	}

	// When a GPU is using virtio-gpu DRM native context, the guest needs a host-backed
	// GL display. Switch the default headless setup to egl-headless and point it at the
	// resolved render node (if any) so rendering is offloaded to the host GPU.
	nativeContext, rendernode, _ := d.gpuNativeContextConfig(devConfs)
	if nativeContext {
		display := "egl-headless,gl=on"
		if rendernode != "" {
			display += fmt.Sprintf(",rendernode=%s", rendernode)
		}

		qemuArgs = append(qemuArgs, "-display", display)
	}

	// If stateful, restore now.
	if stateful {
		if d.stateful {
			qemuArgs = append(qemuArgs, "-incoming", "defer")
		} else {
			// No state to restore, just start as normal.
			stateful = false
		}
	} else if d.stateful {
		// Stateless start requested but state is present, delete it.
		err := os.Remove(d.StatePath())
		if err != nil && !errors.Is(err, fs.ErrNotExist) {
			op.Done(err)
			return err
		}

		d.stateful = false
		err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			return tx.UpdateInstanceStatefulFlag(ctx, d.id, false)
		})
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Error updating instance stateful flag: %w", err)
		}
	}

	// SMBIOS only on x86_64 and aarch64.
	if d.architectureSupportsUEFI(d.architecture) {
		qemuArgs = append(qemuArgs, "-smbios", "type=2,manufacturer=LinuxContainers,product=Incus")

		// We'll pass the values through a file to avoid needlessly long
		// command line arguments and the values being visible in the process list.
		smbios11 := filepath.Join(d.RunPath(), "smbios11")

		err = os.RemoveAll(smbios11)
		if err != nil {
			op.Done(err)
			return err
		}

		err = os.Mkdir(smbios11, 0o700)
		if err != nil {
			op.Done(err)
			return err
		}

		smbios11Idx := 0
		for k, v := range d.expandedConfig {
			var configPrefix, smbiosPrefix string
			if strings.HasPrefix(k, "smbios11.") {
				configPrefix = "smbios11."
				smbiosPrefix = ""
			} else if strings.HasPrefix(k, "systemd.credential.") {
				configPrefix = "systemd.credential."
				smbiosPrefix = "io.systemd.credential:"
			} else if strings.HasPrefix(k, "systemd.credential-binary.") {
				configPrefix = "systemd.credential-binary."
				smbiosPrefix = "io.systemd.credential.binary:"
				data, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(v, "="))
				if err != nil {
					return fmt.Errorf("Invalid base64 value for %q: %q", k, v)
				}

				v = base64.StdEncoding.EncodeToString(data)
			} else {
				continue
			}

			smbios11File := filepath.Join(smbios11, strconv.Itoa(smbios11Idx))

			content := fmt.Sprintf("%s%s=%s", smbiosPrefix, strings.TrimPrefix(k, configPrefix), v)
			err = os.WriteFile(smbios11File, []byte(content), 0o400)
			if err != nil {
				op.Done(err)
				return err
			}

			qemuArgs = append(qemuArgs, "-smbios", fmt.Sprintf("type=11,path=%s", qemuEscapeCmdline(smbios11File)))

			smbios11Idx++
		}
	}

	// Attempt to drop privileges (doesn't work when restoring state).
	if !stateful && d.state.OS.UnprivUser != "" {
		qemuVer, _ := d.version()
		qemuVer91, _ := version.NewDottedVersion("9.1.0")

		// Since QEMU 9.1 the parameter `runas` has been marked as deprecated.
		if qemuVer != nil && qemuVer.Compare(qemuVer91) >= 0 {
			qemuArgs = append(qemuArgs, "-run-with", fmt.Sprintf("user=%s", d.state.OS.UnprivUser))
		} else {
			qemuArgs = append(qemuArgs, "-runas", d.state.OS.UnprivUser)
		}

		nvRAMPath := d.nvramPath()
		if d.architectureSupportsUEFI(d.architecture) && util.PathExists(nvRAMPath) {
			// Ensure UEFI nvram file is writable by the QEMU process.
			// This is needed when doing stateful snapshots because the QEMU process will reopen the
			// file for writing.
			err = os.Chown(nvRAMPath, int(d.state.OS.UnprivUID), -1)
			if err != nil {
				op.Done(err)
				return err
			}

			err = os.Chmod(nvRAMPath, 0o600)
			if err != nil {
				op.Done(err)
				return err
			}
		}

		// Change ownership of main instance directory.
		err = os.Chown(d.Path(), int(d.state.OS.UnprivUID), -1)
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Failed to chown instance path: %w", err)
		}

		// Change ownership of config directory files so they are accessible to the
		// unprivileged qemu process so that the 9p share can work.
		//
		// Security note: The 9P share will present the UID owner of these files on the host
		// to the VM. In order to ensure that non-root users in the VM cannot access these
		// files be sure to mount the 9P share in the VM with the "access=0" option to allow
		// only root user in VM to access the mounted share.
		err := filepath.Walk(filepath.Join(d.Path(), "config"),
			func(path string, info os.FileInfo, err error) error {
				if err != nil {
					return err
				}

				err = os.Chown(path, int(d.state.OS.UnprivUID), -1)
				if err != nil {
					return err
				}

				return nil
			})
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Handle hugepages on architectures where we don't set NUMA nodes.
	if d.architecture != osarch.ARCH_64BIT_INTEL_X86 && util.IsTrue(d.expandedConfig["limits.memory.hugepages"]) {
		hugetlb, err := localUtil.HugepagesPath()
		if err != nil {
			op.Done(err)
			return err
		}

		qemuArgs = append(qemuArgs, "-mem-path", hugetlb, "-mem-prealloc")
	}

	if d.expandedConfig["raw.qemu"] != "" {
		fields, err := shellquote.Split(d.expandedConfig["raw.qemu"])
		if err != nil {
			op.Done(err)
			return err
		}

		qemuArgs = append(qemuArgs, fields...)
	}

	// Apply the RTC configuration.
	// This needs to happen close to creating the full qemu cmd or the time might drift in between.
	adjustment := d.getStartupRTCAdjustment()

	if d.GuestOS() == osinfo.Windows || adjustment != 0 {
		base := time.Now().Add(adjustment)
		if d.GuestOS() == osinfo.Windows {
			// set base to localtime on windows.
			base = base.Local()
		} else {
			// set base to UTC on !windows.
			base = base.UTC()
		}

		datetime := base.Format("2006-01-02T15:04:05")
		qemuArgs = append(qemuArgs, "-rtc", fmt.Sprintf("base=%s", datetime))
	}

	d.cmdArgs = qemuArgs

	// Precompile the QEMU scriptlet
	src, ok := d.expandedConfig["raw.qemu.scriptlet"]
	if ok {
		instanceName := d.Name()

		err := scriptletLoad.QEMUSet(src, instanceName)
		if err != nil {
			err = fmt.Errorf("Failed loading QEMU scriptlet: %w", err)
			return err
		}
	}

	// Config startup hook.
	err = d.runStartupScriptlet(nil, "config")
	if err != nil {
		op.Done(err)
		return err
	}

	// Write the config file.
	err = d.writeQemuConfigFile(confFile)
	if err != nil {
		op.Done(err)
		return err
	}

	// Run the qemu command via forkqemu so we can selectively increase ulimits.
	forkQemuCmd := []string{
		"forkqemu",
	}

	if !d.state.OS.RunningInUserNS {
		// Required for PCI passthrough.
		forkQemuCmd = append(forkQemuCmd, "limit=memlock:unlimited:unlimited")
	}

	// On systems with heterogeneous CPU types, confine QEMU startup to a single
	// type as KVM vCPU initialization fails when scheduled across types.
	startupCPUs := d.startupCPUSet(bs.CPUTopology)
	if len(startupCPUs) > 0 {
		cpus := make([]string, 0, len(startupCPUs))
		for _, id := range startupCPUs {
			cpus = append(cpus, strconv.FormatInt(id, 10))
		}

		forkQemuCmd = append(forkQemuCmd, "cpus="+strings.Join(cpus, ","))
	}

	for i := range fdFiles {
		// Pass through any file descriptors as 3+i (as first 3 file descriptors are taken as standard).
		forkQemuCmd = append(forkQemuCmd, fmt.Sprintf("fd=%d", 3+i))
	}

	// Ensure SELinux context is generated and persisted.
	contextIsNew, err := d.selinuxEnsureContext()
	if err != nil {
		return err
	}

	seCtx := d.localConfig["volatile.selinux.context"]
	if seCtx != "" {
		forkQemuCmd = append(forkQemuCmd, "secontext="+seCtx)
	}

	// Log the QEMU command line.
	fullCmd := append(forkQemuCmd, "--", qemuPath)
	fullCmd = append(fullCmd, d.cmdArgs...)
	d.logger.Debug("Starting QEMU", logger.Ctx{"command": fullCmd})

	// Setup background process.
	p, err := subprocess.NewProcess(d.state.OS.ExecPath, fullCmd, d.EarlyLogFilePath(), d.EarlyLogFilePath())
	if err != nil {
		op.Done(err)
		return err
	}

	// Load the AppArmor profile
	err = apparmor.InstanceLoad(d.state.OS, d, []string{qemuPath})
	if err != nil {
		op.Done(err)
		return err
	}

	p.SetApparmor(apparmor.InstanceProfileName(d))

	// Update the backup.yaml file just before starting the instance process, but after all devices have been
	// setup, so that the backup file contains the volatile keys used for this instance start, so that they can
	// be used for instance cleanup.
	err = d.UpdateBackupFile()
	if err != nil {
		err = fmt.Errorf("Failed updating backup file: %w", err)
		op.Done(err)
		return err
	}

	err = p.StartWithFiles(context.Background(), fdFiles)
	if err != nil {
		op.Done(err)
		return err
	}

	_, err = p.Wait(context.Background())
	if err != nil {
		stderr, _ := os.ReadFile(d.EarlyLogFilePath())
		err = fmt.Errorf("Failed to run: %s: %s: %w", strings.Join(p.Args, " "), string(stderr), err)
		op.Done(err)
		return err
	}

	pid, err := d.pid()
	if err != nil || pid <= 0 {
		d.logger.Error("Failed to get VM process ID", logger.Ctx{"err": err, "pid": pid})
		op.Done(err)
		return err
	}

	reverter.Add(func() {
		_ = d.killQemuProcess(pid)
	})

	// Start QMP monitoring.
	monitor, err := d.qmpConnect()
	if err != nil {
		op.Done(err)
		return err
	}

	// Record the QEMU machine definition.
	// NOTE: We can't use CanLiveMigrate during instance startup as the boot state hasn't yet been recorded.
	if !stateful && util.IsTrue(d.expandedConfig["migration.stateful"]) {
		definition, err := monitor.MachineDefinition()
		if err != nil {
			op.Done(err)
			return err
		}

		bs.MachineType = definition
	}

	// Don't allow the monitor to trigger a disconnection shutdown event until cleanly started so that the
	// onStop hook isn't triggered prematurely (as this function's reverter will clean up on failure to start).
	monitor.SetInitialized(false)

	// Early startup hook
	err = d.startupHook(monitor, "early")
	if err != nil {
		op.Done(err)
		return err
	}

	// Apply CPU pinning.
	if bs.CPUTopology.VCPUs == nil {
		if d.architectureSupportsCPUHotplug() && !bs.CPUTopology.Explicit && bs.CPUTopology.Cores > 1 {
			// Hotplug the CPUs.
			err := d.setCPUs(monitor, bs.CPUTopology.Cores)
			if err != nil {
				err = fmt.Errorf("Failed to add CPUs: %w", err)
				op.Done(err)
				return err
			}
		}
	} else {
		// Get the list of PIDs from the VM.
		pids, err := monitor.GetCPUs()
		if err != nil {
			op.Done(err)
			return err
		}

		// Confirm nothing weird is going on.
		if len(bs.CPUTopology.VCPUs) != len(pids) {
			err = errors.New("QEMU has less vCPUs than configured")
			op.Done(err)
			return err
		}

		// Apply the CPU pins.
		for i, pid := range pids {
			set := unix.CPUSet{}
			set.Set(int(bs.CPUTopology.VCPUs[uint64(i)]))

			// Apply the pin.
			err := unix.SchedSetaffinity(pid, &set)
			if err != nil {
				op.Done(err)
				return err
			}
		}

		// Create a core scheduling group.
		err = d.setCoreSched(pids)
		if err != nil {
			err = fmt.Errorf("Failed to allocate new core scheduling domain for vCPU threads: %w", err)
			op.Done(err)
			return err
		}
	}

	// After a constrained startup, let the non-vCPU threads spread across the system again.
	if len(startupCPUs) > 0 {
		err = d.resetSupportThreadsAffinity(monitor, pid)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Run monitor hooks from devices.
	for _, monHook := range monHooks {
		err = monHook(monitor)
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Failed setting up device via monitor: %w", err)
		}
	}

	// Pre-start startup hook
	err = d.startupHook(monitor, "pre-start")
	if err != nil {
		op.Done(err)
		return err
	}

	// Due to a bug in QEMU, devices added using QMP's device_add command do not have their bootindex option
	// respected (even if added before emuation is started). To workaround this we must reset the VM in order
	// for it to rebuild its boot config and to take into account the devices bootindex settings.
	// This also means we cannot start the QEMU process with the -no-reboot flag, so we set the same reboot
	// action below after this call.
	err = monitor.Reset()
	if err != nil {
		op.Done(err)
		return fmt.Errorf("Failed resetting VM: %w", err)
	}

	// Set our default actions. Those can still be overridden in the event handler when Incus is running.
	actions := map[string]string{
		"shutdown": "poweroff",
		"reboot":   "reset",
		"panic":    "exit-failure",
	}

	err = monitor.SetAction(actions)
	if err != nil {
		op.Done(err)
		return fmt.Errorf("Failed setting reboot action: %w", err)
	}

	// Restore the state.
	if stateful {
		// Add back any memory hotplug slot.
		if bs.MemoryTopology != nil {
			for _, memSize := range bs.MemoryTopology.Extra {
				err := d.hotplugMemory(monitor, memSize)
				if err != nil {
					return err
				}
			}
		}

		// Receive the state.
		err = d.restoreState(monitor)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Label rootfs if SELinux context is set and labels are missing.
	err = d.selinuxLabelFiles(contextIsNew)
	if err != nil {
		return err
	}

	// Start the VM.
	err = monitor.Start()
	if err != nil {
		err = fmt.Errorf("Failed starting VM: %w", err)
		op.Done(err)
		return err
	}

	// Finish handling stateful start.
	if stateful {
		// Cleanup state.
		_ = os.Remove(d.StatePath())
		d.stateful = false

		err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			return tx.UpdateInstanceStatefulFlag(ctx, d.id, false)
		})
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Error updating instance stateful flag: %w", err)
		}
	}

	// Record last start state.
	err = d.recordLastState()
	if err != nil {
		op.Done(err)
		return err
	}

	reverter.Success()

	// Post-start startup hook
	err = d.startupHook(monitor, "post-start")
	if err != nil {
		op.Done(err)

		// Shut down the VM if the post-start commands fail.
		_ = d.Stop(false)
		return err
	}

	// Run any post-start hooks.
	err = d.runHooks(postStartHooks)
	if err != nil {
		op.Done(err) // Must come before Stop() otherwise stop will not proceed.

		// Shut down the VM if hooks fail.
		_ = d.Stop(false)
		return err
	}

	// Apply OOM priority after container is started and hooks completed.
	err = d.setOOMPriority(d.InitPID())
	if err != nil {
		d.logger.Warn("Failed to set OOM priority", logger.Ctx{
			"err":      err,
			"instance": d.Name(),
			"project":  d.Project().Name,
		})
	}

	if op.Action() == "start" {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStarted.Event(d, nil))
	}

	// The VM started cleanly so now enable the unexpected disconnection event to ensure the onStop hook is
	// run if QMP unexpectedly disconnects.
	monitor.SetInitialized(true)
	op.Done(nil)

	// Record the final boot state data.
	err = d.saveBootState(*bs)
	if err != nil {
		return err
	}

	return nil
}

func (d *qemu) setupSEV(fdFiles *[]*os.File) (*qemuSevOpts, error) {
	if d.architecture != osarch.ARCH_64BIT_INTEL_X86 {
		return nil, errors.New("AMD SEV support is only available on x86_64 systems")
	}

	// Get the QEMU features to check if AMD SEV is supported.
	info := DriverStatuses()[instancetype.VM].Info
	_, smeFound := info.Features["sme"] // codespell:ignore sme
	sev, sevFound := info.Features["sev"]
	if !smeFound || !sevFound {
		return nil, errors.New("AMD SEV is not supported by the host")
	}

	// Get the SEV guest `cbitpos` and `reducedPhysBits`.
	sevCapabilities, ok := sev.(qmp.AMDSEVCapabilities)
	if !ok {
		return nil, errors.New(`Failed to get the guest "sev" capabilities`)
	}

	cbitpos := sevCapabilities.CBitPos
	reducedPhysBits := sevCapabilities.ReducedPhysBits

	// Write user's dh-cert and session-data to file descriptors.
	var dhCertFD, sessionDataFD int
	if d.expandedConfig["security.sev.session.dh"] != "" {
		dhCert, err := os.CreateTemp("", "incus_sev_dh_cert_")
		if err != nil {
			return nil, err
		}

		err = os.Remove(dhCert.Name())
		if err != nil {
			return nil, err
		}

		_, err = dhCert.WriteString(d.expandedConfig["security.sev.session.dh"])
		if err != nil {
			return nil, err
		}

		dhCertFD = d.addFileDescriptor(fdFiles, dhCert)
	}

	if d.expandedConfig["security.sev.session.data"] != "" {
		sessionData, err := os.CreateTemp("", "incus_sev_session_data_")
		if err != nil {
			return nil, err
		}

		err = os.Remove(sessionData.Name())
		if err != nil {
			return nil, err
		}

		_, err = sessionData.WriteString(d.expandedConfig["security.sev.session.data"])
		if err != nil {
			return nil, err
		}

		sessionDataFD = d.addFileDescriptor(fdFiles, sessionData)
	}

	sevOpts := &qemuSevOpts{}
	sevOpts.cbitpos = cbitpos
	sevOpts.reducedPhysBits = reducedPhysBits
	if dhCertFD > 0 && sessionDataFD > 0 {
		sevOpts.dhCertFD = fmt.Sprintf("/proc/self/fd/%d", dhCertFD)
		sevOpts.sessionDataFD = fmt.Sprintf("/proc/self/fd/%d", sessionDataFD)
	}

	if util.IsTrue(d.expandedConfig["security.sev.policy.es"]) {
		_, sevES := info.Features["sev-es"]
		if !sevES {
			return nil, errors.New("AMD SEV-ES is not supported by the host")
		}

		// This bit mask is used to specify a guest policy. '0x5' is for SEV-ES. The details of the available policies can be found in the link below (see chapter 3)
		// https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf
		sevOpts.policy = "0x5"
	} else {
		// '0x1' is for a regular SEV policy.
		sevOpts.policy = "0x1"
	}

	return sevOpts, nil
}

// getAgentConnectionInfo returns the connection info the agent needs to connect to the server.
func (d *qemu) getAgentConnectionInfo() (*agentAPI.API10Put, error) {
	addr := d.state.Endpoints.VsockAddress()
	if addr == nil {
		return nil, nil
	}

	vsockaddr, ok := addr.(*vsock.Addr)
	if !ok {
		return nil, errors.New("Listen address is not vsock.Addr")
	}

	req := agentAPI.API10Put{
		Certificate: string(d.state.Endpoints.NetworkCert().PublicKey()),
		DevIncus:    util.IsTrueOrEmpty(d.expandedConfig["security.guestapi"]),
		CID:         vsock.Host, // Always tell the agent to connect to the server using the Host Context ID to support nesting.
		Port:        vsockaddr.Port,
	}

	return &req, nil
}

// advertiseVsockAddress advertises the CID and port to the VM.
func (d *qemu) advertiseVsockAddress() error {
	client, err := d.getAgentClient()
	if err != nil {
		return fmt.Errorf("Failed getting agent client handle: %w", err)
	}

	agentArgs := &incus.ConnectionArgs{
		SkipGetEvents: true,
		SkipGetServer: true,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	agent, err := incus.ConnectIncusHTTPWithContext(ctx, agentArgs, client)
	if err != nil {
		return fmt.Errorf("Failed connecting to the agent: %w", err)
	}

	defer agent.Disconnect()

	connInfo, err := d.getAgentConnectionInfo()
	if err != nil {
		return err
	}

	if connInfo == nil {
		return nil
	}

	_, _, err = agent.RawQuery("PUT", "/1.0", connInfo, "")
	if err != nil {
		return fmt.Errorf("Failed sending host vsock information to the agent: %w", err)
	}

	return nil
}

// AgentCertificate returns the server certificate of the agent.
func (d *qemu) AgentCertificate() *x509.Certificate {
	agentCert := filepath.Join(d.Path(), "config", "agent.crt")
	if !util.PathExists(agentCert) {
		return nil
	}

	cert, err := localtls.ReadCert(agentCert)
	if err != nil {
		return nil
	}

	return cert
}

func (d *qemu) architectureSupportsUEFI(arch int) bool {
	return slices.Contains([]int{osarch.ARCH_64BIT_INTEL_X86, osarch.ARCH_64BIT_ARMV8_LITTLE_ENDIAN}, arch)
}

// firmwarePairs returns the candidate firmware pairs for the instance based on its configuration.
// When AMD SEV is enabled, a SEV-capable firmware is preferred but falls back to the regular firmware
// (GENERIC, SECUREBOOT or CSM) if no SEV build is available.
func (d *qemu) firmwarePairs() ([]edk2.FirmwarePair, error) {
	if util.IsTrue(d.expandedConfig["security.sev"]) {
		firmwares, err := edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SEV)
		if err != nil {
			return nil, err
		}

		if len(firmwares) > 0 {
			return firmwares, nil
		}
	}

	switch {
	case util.IsTrue(d.expandedConfig["security.csm"]):
		return edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.CSM)
	case util.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]):
		return edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SECUREBOOT)
	default:
		return edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.GENERIC)
	}
}

func (d *qemu) setupNvram() error {
	var err error

	d.logger.Debug("Generating NVRAM")

	// Cleanup existing variables.
	firmwares, err := edk2.GetArchitectureFirmwarePairs(d.architecture)
	if err != nil {
		return err
	}

	for _, firmwarePair := range firmwares {
		err := os.Remove(filepath.Join(d.Path(), filepath.Base(firmwarePair.Vars)))
		if err != nil && !errors.Is(err, fs.ErrNotExist) {
			return err
		}
	}

	// Determine expected firmware.
	firmwares, err = d.firmwarePairs()
	if err != nil {
		return err
	}

	// Unified firmware images (e.g. AMD SEV) carry their own variable store and need no NVRAM.
	needsNvram := false
	for _, firmware := range firmwares {
		if firmware.Vars != "" {
			needsNvram = true
			break
		}
	}

	if !needsNvram {
		return nil
	}

	// Find the template file.
	var efiVarsPath string
	var efiVarsName string
	for _, firmware := range firmwares {
		if firmware.Vars == "" {
			continue
		}

		varsPath, err := filepath.EvalSymlinks(firmware.Vars)
		if err != nil {
			continue
		}

		if util.PathExists(varsPath) {
			efiVarsPath = varsPath
			efiVarsName = filepath.Base(firmware.Vars)
			break
		}
	}

	if efiVarsPath == "" {
		return fmt.Errorf("Couldn't find one of the required UEFI firmware files: %+v", firmwares)
	}

	// Copy the template.
	err = internalUtil.FileCopy(efiVarsPath, filepath.Join(d.Path(), efiVarsName))
	if err != nil {
		return err
	}

	nvramPath := d.nvramPath()

	// Handle the case where the firmware vars filename matches our internal one.
	if efiVarsName == filepath.Base(nvramPath) {
		return nil
	}

	// Generate a symlink.
	// This is so qemu.nvram can always be assumed to be the EDK2 vars file.
	// The real file name is then used to determine what firmware must be selected.
	_ = os.Remove(nvramPath)
	err = os.Symlink(efiVarsName, nvramPath)
	if err != nil {
		return err
	}

	return nil
}

func (d *qemu) qemuArchConfig(arch int) (string, string, error) {
	var bus string
	var qemuCmd string

	switch arch {
	case osarch.ARCH_64BIT_INTEL_X86:
		qemuCmd = "qemu-system-x86_64"
		bus = "pcie"
	case osarch.ARCH_64BIT_ARMV8_LITTLE_ENDIAN:
		qemuCmd = "qemu-system-aarch64"
		bus = "pcie"
	case osarch.ARCH_64BIT_POWERPC_LITTLE_ENDIAN:
		qemuCmd = "qemu-system-ppc64"
		bus = "pci"
	case osarch.ARCH_64BIT_S390_BIG_ENDIAN:
		qemuCmd = "qemu-system-s390x"
		bus = "ccw"
	default:
		return "", "", errors.New("Architecture isn't supported for virtual machines")
	}

	qemuPath, err := exec.LookPath(qemuCmd)
	if err != nil {
		hostArch, archErr := osarch.ArchitectureGetLocalID()
		if archErr != nil {
			return "", "", err
		}

		if arch == hostArch && util.PathExists("/usr/libexec/qemu-kvm") {
			return "/usr/libexec/qemu-kvm", bus, nil
		}

		return "", "", err
	}

	return qemuPath, bus, nil
}

// RegisterDevices calls the Register() function on all of the instance's devices.
func (d *qemu) RegisterDevices() {
	d.devicesRegister(d)
}

func (d *qemu) saveConnectionInfo(connInfo *agentAPI.API10Put) error {
	configDrivePath := filepath.Join(d.Path(), "config")

	f, err := os.Create(filepath.Join(configDrivePath, "agent.conf"))
	if err != nil {
		return err
	}

	defer func() {
		_ = f.Close()
	}()

	err = json.NewEncoder(f).Encode(connInfo)
	if err != nil {
		return err
	}

	return nil
}

// OnHook is the top-level hook handler.
func (d *qemu) OnHook(hookName string, args map[string]string) error {
	return instance.ErrNotImplemented
}

// deviceStart loads a new device and calls its Start() function.
func (d *qemu) deviceStart(dev device.Device, instanceRunning bool) (*deviceConfig.RunConfig, error) {
	configCopy := dev.Config()
	l := d.logger.AddContext(logger.Ctx{"device": dev.Name(), "type": configCopy["type"]})
	l.Debug("Starting device")

	reverter := revert.New()
	defer reverter.Fail()

	if instanceRunning && !dev.CanHotPlug() {
		return nil, errors.New("Device cannot be started when instance is running")
	}

	runConf, err := dev.Start()
	if err != nil {
		return nil, err
	}

	reverter.Add(func() {
		runConf, _ := dev.Stop()
		if runConf != nil {
			_ = d.runHooks(runConf.PostHooks)
		}
	})

	// If runConf supplied, perform any instance specific setup of device.
	if runConf != nil {
		// If instance is running and then live attach device.
		if instanceRunning {
			// Attach NIC to running instance.
			if len(runConf.NetworkInterface) > 0 {
				err = d.deviceAttachNIC(dev.Name(), configCopy, runConf)
				if err != nil {
					return nil, err
				}
			}

			// Attach disk to running instance.
			for _, mount := range runConf.Mounts {
				if mount.FSType == "9p" {
					err = d.deviceAttachPath(dev.Name(), configCopy, mount)
					if err != nil {
						return nil, err
					}
				} else if mount.TargetPath != "/" {
					err = d.deviceAttachBlockDevice(dev.Name(), configCopy, mount)
					if err != nil {
						return nil, err
					}
				}
			}

			// Attach USB to running instance.
			for _, usbDev := range runConf.USBDevice {
				err = d.deviceAttachUSB(usbDev)
				if err != nil {
					return nil, err
				}
			}

			// Attach PCI to running instance.
			if len(runConf.PCIDevice) > 0 {
				err = d.deviceAttachPCI(dev.Name(), configCopy, runConf.PCIDevice)
				if err != nil {
					return nil, err
				}
			}

			// If running, run post start hooks now (if not, they will be run
			// once the instance is started).
			err = d.runHooks(runConf.PostHooks)
			if err != nil {
				return nil, err
			}
		}
	}

	reverter.Success()

	return runConf, nil
}

func (d *qemu) deviceAttachPath(deviceName string, configCopy map[string]string, mount deviceConfig.MountEntryItem) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return fmt.Errorf("Failed to connect to QMP monitor: %w", err)
	}

	monHook, err := d.addDriveDirConfigVirtiofs(nil, nil, mount)
	if err != nil {
		return fmt.Errorf("Failed to add drive config: %w", err)
	}

	err = monHook(monitor)
	if err != nil {
		return fmt.Errorf("Failed to call monitor hook for block device: %w", err)
	}

	return nil
}

func (d *qemu) deviceAttachBlockDevice(deviceName string, configCopy map[string]string, mount deviceConfig.MountEntryItem) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return fmt.Errorf("Failed to connect to QMP monitor: %w", err)
	}

	monHook, err := d.addDriveConfig(nil, nil, mount)
	if err != nil {
		return fmt.Errorf("Failed to add drive config: %w", err)
	}

	err = monHook(monitor)
	if err != nil {
		return fmt.Errorf("Failed to call monitor hook for block device: %w", err)
	}

	return nil
}

func (d *qemu) deviceDetachPath(deviceName string, rawConfig deviceConfig.Device) error {
	escapedDeviceName := linux.PathNameEncode(deviceName)
	deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)
	mountTag := d.mountTagName(deviceName, qemuMountTagMaxLength)

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	err = monitor.RemoveDevice(deviceID)
	if err != nil {
		return err
	}

	waitDuration := time.Duration(time.Second * time.Duration(10))
	waitUntil := time.Now().Add(waitDuration)
	for {
		err = monitor.RemoveCharDevice(mountTag)
		if err == nil {
			break
		}

		if api.StatusErrorCheck(err, http.StatusLocked) {
			time.Sleep(time.Second * time.Duration(2))
			continue
		}

		if time.Now().After(waitUntil) {
			return fmt.Errorf("Failed to detach path device after %v", waitDuration)
		}
	}

	return nil
}

func (d *qemu) deviceDetachBlockDevice(deviceName string, rawConfig deviceConfig.Device) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	escapedDeviceName := linux.PathNameEncode(deviceName)
	deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)
	blockDevName := d.blockNodeName(escapedDeviceName)

	blockDevs, err := d.fetchBlockDeviceChain(monitor, blockDevName)
	if err != nil {
		return err
	}

	for i := len(blockDevs); i > 0; i-- {
		blockDev := blockDevs[i-1]
		err = monitor.RemoveFDFromFDSet(blockDev)
		if err != nil {
			return err
		}
	}

	err = monitor.RemoveDevice(deviceID)
	if err != nil {
		return err
	}

	if rawConfig["io.bus"] == "usb" {
		// When dealing with USB, remove the intermediate USB device too.
		err = monitor.RemoveDevice("usb_" + deviceID)
		if err != nil {
			return err
		}
	}

	for i := len(blockDevs); i > 0; i-- {
		blockDev := blockDevs[i-1]
		err = d.detachBlockDeviceAndWait(monitor, blockDev)
		if err != nil {
			return err
		}
	}

	return nil
}

func (d *qemu) detachBlockDeviceAndWait(m *qmp.Monitor, blockDevName string) error {
	waitDuration := time.Duration(time.Second * time.Duration(10))
	waitUntil := time.Now().Add(waitDuration)
	for {
		err := m.RemoveBlockDevice(blockDevName)
		if err == nil {
			break
		}

		if api.StatusErrorCheck(err, http.StatusLocked) {
			time.Sleep(time.Second * time.Duration(2))
			continue
		}

		if time.Now().After(waitUntil) {
			return fmt.Errorf("Failed to detach block device after %v", waitDuration)
		}
	}

	return nil
}

// deviceAttachNIC live attaches a NIC device to the instance.
func (d *qemu) deviceAttachNIC(deviceName string, configCopy map[string]string, runConf *deviceConfig.RunConfig) error {
	devName := ""
	for _, dev := range runConf.NetworkInterface {
		if dev.Key == "link" {
			devName = dev.Value
			break
		}
	}

	if devName == "" {
		return errors.New("Device didn't provide a link property to use")
	}

	_, qemuBus, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		return err
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	qemuDev := make(map[string]any)
	if runConf.UseUSBBus {
		qemuBus = "usb"
		qemuDev["bus"] = "qemu_usb.0"
	} else if slices.Contains([]string{"pcie", "pci"}, qemuBus) {
		// Try to get a PCI address for hotplugging.
		pciDeviceName, err := d.getPCIHotplug()
		if err != nil {
			return err
		}

		d.logger.Debug("Using PCI bus device to hotplug NIC into", logger.Ctx{"device": deviceName, "port": pciDeviceName})
		qemuDev["bus"] = pciDeviceName
		qemuDev["addr"] = "00.0"
	}

	monHook, err := d.addNetDevConfig(qemuBus, qemuDev, nil, runConf.NetworkInterface)
	if err != nil {
		return err
	}

	err = monHook(monitor)
	if err != nil {
		return err
	}

	return nil
}

func (d *qemu) getPCIHotplug() (string, error) {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return "", err
	}

	// Get the current PCI devices.
	devices, err := monitor.QueryPCI()
	if err != nil {
		return "", err
	}

	for _, dev := range devices {
		// Skip built-in devices.
		if dev.DevID == "" || dev.DevID == "qemu_iommu" {
			continue
		}

		// Skip used bridges.
		if len(dev.Bridge.Devices) > 0 {
			continue
		}

		// Found an empty slot.
		return dev.DevID, nil
	}

	return "", errors.New("No available PCI hotplug slots could be found")
}

// deviceAttachPCI live attaches a generic PCI device to the instance.
func (d *qemu) deviceAttachPCI(deviceName string, configCopy map[string]string, pciConfig []deviceConfig.RunConfigItem) error {
	reverter := revert.New()
	defer reverter.Fail()

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	// Get the device config.
	var devName, pciSlotName, pciIOMMUGroup string
	for _, pciItem := range pciConfig {
		switch pciItem.Key {
		case "devName":
			devName = pciItem.Value
		case "pciSlotName":
			pciSlotName = pciItem.Value
		case "pciIOMMUGroup":
			pciIOMMUGroup = pciItem.Value
		}
	}

	// PCIe and PCI require a port device name to hotplug the NIC into.
	_, qemuBus, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		return err
	}

	if !slices.Contains([]string{"pcie", "pci"}, qemuBus) {
		return errors.New("Attempting PCI passthrough on a non-PCI system")
	}

	// Try to get a PCI address for hotplugging.
	pciDeviceName, err := d.getPCIHotplug()
	if err != nil {
		return err
	}

	qemuDev := make(map[string]any)
	escapedDeviceName := linux.PathNameEncode(devName)

	d.logger.Debug("Using PCI bus device to hotplug NIC into", logger.Ctx{"device": deviceName, "port": pciDeviceName})

	qemuDev["bus"] = pciDeviceName
	qemuDev["addr"] = "00.0"
	qemuDev["driver"] = "vfio-pci"
	qemuDev["id"] = fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)
	qemuDev["host"] = pciSlotName

	if d.state.OS.UnprivUser != "" {
		if pciIOMMUGroup == "" {
			return errors.New("No PCI IOMMU group supplied")
		}

		vfioGroupFile := fmt.Sprintf("/dev/vfio/%s", pciIOMMUGroup)
		err := os.Chown(vfioGroupFile, int(d.state.OS.UnprivUID), -1)
		if err != nil {
			return fmt.Errorf("Failed to chown vfio group device %q: %w", vfioGroupFile, err)
		}
	}

	err = monitor.AddDevice(qemuDev)
	if err != nil {
		return fmt.Errorf("Failed setting up device %q: %w", devName, err)
	}

	return nil
}

// deviceStop loads a new device and calls its Stop() function.
func (d *qemu) deviceStop(dev device.Device, instanceRunning bool, _ string) error {
	configCopy := dev.Config()
	l := d.logger.AddContext(logger.Ctx{"device": dev.Name(), "type": configCopy["type"]})
	l.Debug("Stopping device")

	if instanceRunning && !dev.CanHotPlug() {
		return errors.New("Device cannot be stopped when instance is running")
	}

	// The root disk device can't be hot-unplugged.
	if instanceRunning && internalInstance.IsRootDiskDevice(configCopy) {
		return errors.New("Root disk device cannot be detached from a running instance")
	}

	runConf, err := dev.Stop()
	if err != nil {
		return err
	}

	if instanceRunning {
		// Detach NIC from running instance.
		if configCopy["type"] == "nic" {
			for _, usbDev := range runConf.USBDevice {
				err = d.deviceDetachUSB(usbDev)
				if err != nil {
					return err
				}
			}

			err = d.deviceDetachNIC(dev.Name())
			if err != nil {
				return err
			}
		}

		// Detach USB from running instance.
		if configCopy["type"] == "usb" && runConf != nil {
			for _, usbDev := range runConf.USBDevice {
				err = d.deviceDetachUSB(usbDev)
				if err != nil {
					return err
				}
			}
		}

		// Detach disk from running instance.
		if configCopy["type"] == "disk" {
			if configCopy["path"] != "" {
				err = d.deviceDetachPath(dev.Name(), configCopy)
				if err != nil {
					return err
				}
			} else {
				err = d.deviceDetachBlockDevice(dev.Name(), configCopy)
				if err != nil {
					return err
				}
			}
		}

		// Detach generic PCI device from running instance.
		if configCopy["type"] == "pci" {
			err = d.deviceDetachPCI(dev.Name())
			if err != nil {
				return err
			}
		}
	}

	if runConf != nil {
		// Run post stop hooks irrespective of run state of instance.
		err = d.runHooks(runConf.PostHooks)
		if err != nil {
			return err
		}
	}

	return nil
}

// deviceDetachNIC detaches a NIC device from a running instance.
func (d *qemu) deviceDetachNIC(deviceName string) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	escapedDeviceName := linux.PathNameEncode(deviceName)
	deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)
	netDevID := fmt.Sprintf("%s%s", qemuNetDevIDPrefix, escapedDeviceName)

	// Request removal of device.
	err = monitor.RemoveDevice(deviceID)
	if err != nil {
		return fmt.Errorf("Failed removing NIC device: %w", err)
	}

	err = monitor.RemoveNIC(netDevID)
	if err != nil {
		return err
	}

	_, qemuBus, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		return err
	}

	if slices.Contains([]string{"pcie", "pci"}, qemuBus) {
		// Wait until the device is actually removed (or we timeout waiting).
		waitDuration := time.Duration(time.Second * time.Duration(10))
		waitUntil := time.Now().Add(waitDuration)
		for {
			devExists, err := monitor.CheckPCIDevice(deviceID)
			if err != nil {
				return fmt.Errorf("Failed getting PCI devices to check for NIC detach: %w", err)
			}

			if !devExists {
				break
			}

			if time.Now().After(waitUntil) {
				return fmt.Errorf("Failed to detach NIC after %v", waitDuration)
			}

			d.logger.Debug("Waiting for NIC device to be detached", logger.Ctx{"device": deviceName})
			time.Sleep(time.Second * time.Duration(2))
		}
	}

	return nil
}

// deviceDetachPCI detaches a generic PCI device from a running instance.
func (d *qemu) deviceDetachPCI(deviceName string) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	escapedDeviceName := linux.PathNameEncode(deviceName)
	deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)

	// Request removal of device.
	err = monitor.RemoveDevice(deviceID)
	if err != nil {
		return fmt.Errorf("Failed removing PCI device: %w", err)
	}

	_, qemuBus, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		return err
	}

	if slices.Contains([]string{"pcie", "pci"}, qemuBus) {
		// Wait until the device is actually removed (or we timeout waiting).
		waitDuration := time.Duration(time.Second * time.Duration(10))
		waitUntil := time.Now().Add(waitDuration)
		for {
			devExists, err := monitor.CheckPCIDevice(deviceID)
			if err != nil {
				return fmt.Errorf("Failed getting PCI devices to check for detach: %w", err)
			}

			if !devExists {
				break
			}

			if time.Now().After(waitUntil) {
				return fmt.Errorf("Failed to detach PCI device after %v", waitDuration)
			}

			d.logger.Debug("Waiting for PCI device to be detached", logger.Ctx{"device": deviceName})
			time.Sleep(time.Second * time.Duration(2))
		}
	}

	return nil
}

func (d *qemu) monitorPath() string {
	return filepath.Join(d.RunPath(), "qemu.monitor")
}

func (d *qemu) nvramPath() string {
	return filepath.Join(d.Path(), "qemu.nvram")
}

func (d *qemu) consolePath() string {
	return filepath.Join(d.RunPath(), "qemu.console")
}

func (d *qemu) spicePath() string {
	return filepath.Join(d.RunPath(), "qemu.spice")
}

func (d *qemu) nbdPath() string {
	return filepath.Join(d.RunPath(), "qemu.nbd")
}

func (d *qemu) migrateSockPath() string {
	return filepath.Join(d.RunPath(), "migrate.sock")
}

func (d *qemu) spiceCmdlineConfig(fdFiles *[]*os.File) (string, error) {
	// Reference the socket through a short /proc/self/fd path to handle
	// run paths that exceed the unix socket path limit.
	spiceDir, err := os.OpenFile(d.RunPath(), unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
	if err != nil {
		return "", err
	}

	spiceDirFD := d.addFileDescriptor(fdFiles, spiceDir)
	spicePath := fmt.Sprintf("/proc/self/fd/%d/qemu.spice", spiceDirFD)

	return fmt.Sprintf("unix=on,disable-ticketing=on,addr=%s", spicePath), nil
}

// generateConfigShare generates the config share directory that will be exported to the VM via
// a 9P share. Due to the unknown size of templates inside the images this directory is created
// inside the VM's config volume so that it can be restricted by quota.
// Requires the instance be mounted before calling this function.
func (d *qemu) generateConfigShare(volatileSet map[string]string) error {
	configDrivePath := filepath.Join(d.Path(), "config")

	// Create config drive dir if doesn't exist, if it does exist, leave it around so we don't regenerate all
	// files causing unnecessary config drive snapshot usage.
	err := os.MkdirAll(configDrivePath, 0o500)
	if err != nil {
		return err
	}

	guestOS := d.GuestOS()
	if guestOS == osinfo.UnknownOS {
		guestOS = osinfo.Linux
	}

	// Windows doesn't handle shares.
	if guestOS != osinfo.Windows {
		// Add the VM agent loader.
		agentSrcPath, _ := exec.LookPath("incus-agent")
		if util.PathExists(os.Getenv("INCUS_AGENT_PATH")) {
			// Install incus-agent script (loads from agent share).
			agentFile, err := incusAgentLoader.ReadFile("agent-loader/incus-agent-" + string(guestOS))
			if err != nil {
				return err
			}

			err = os.WriteFile(filepath.Join(configDrivePath, "incus-agent"), agentFile, 0o700)
			if err != nil {
				return err
			}

			// Legacy support.
			_ = os.Remove(filepath.Join(configDrivePath, "lxd-agent"))
			err = os.Symlink("incus-agent", filepath.Join(configDrivePath, "lxd-agent"))
			if err != nil {
				return err
			}
		} else if agentSrcPath != "" {
			// Install agent into config drive dir if found.
			agentSrcPath, err = filepath.EvalSymlinks(agentSrcPath)
			if err != nil {
				return err
			}

			agentSrcInfo, err := os.Stat(agentSrcPath)
			if err != nil {
				return fmt.Errorf("Failed getting info for incus-agent source %q: %w", agentSrcPath, err)
			}

			agentInstallPath := filepath.Join(configDrivePath, "incus-agent")
			agentNeedsInstall := true

			if util.PathExists(agentInstallPath) {
				agentInstallInfo, err := os.Stat(agentInstallPath)
				if err != nil {
					return fmt.Errorf("Failed getting info for existing incus-agent install %q: %w", agentInstallPath, err)
				}

				if agentInstallInfo.ModTime().Equal(agentSrcInfo.ModTime()) && agentInstallInfo.Size() == agentSrcInfo.Size() {
					agentNeedsInstall = false
				}
			}

			// Only install the agent into config drive if the existing one is different to the source one.
			// Otherwise we would end up copying it again and this can cause unnecessary snapshot usage.
			if agentNeedsInstall {
				d.logger.Debug("Installing incus-agent", logger.Ctx{"srcPath": agentSrcPath, "installPath": agentInstallPath})
				err = internalUtil.FileCopy(agentSrcPath, agentInstallPath)
				if err != nil {
					return err
				}

				err = os.Chmod(agentInstallPath, 0o500)
				if err != nil {
					return err
				}

				err = os.Chown(agentInstallPath, 0, 0)
				if err != nil {
					return err
				}

				// Ensure we copy the source file's timestamps so they can be used for comparison later.
				err = os.Chtimes(agentInstallPath, agentSrcInfo.ModTime(), agentSrcInfo.ModTime())
				if err != nil {
					return fmt.Errorf("Failed setting incus-agent timestamps: %w", err)
				}
			} else {
				d.logger.Debug("Skipping incus-agent install as unchanged", logger.Ctx{"srcPath": agentSrcPath, "installPath": agentInstallPath})
			}

			// Legacy support.
			_ = os.Remove(filepath.Join(configDrivePath, "lxd-agent"))
			err = os.Symlink("incus-agent", filepath.Join(configDrivePath, "lxd-agent"))
			if err != nil {
				return err
			}
		} else {
			d.logger.Warn("incus-agent not found, skipping its inclusion in the VM config drive", logger.Ctx{"err": err})
		}
	}

	agentCert, agentKey, clientCert, _, err := d.generateAgentCert()
	if err != nil {
		return err
	}

	err = os.WriteFile(filepath.Join(configDrivePath, "server.crt"), []byte(clientCert), 0o400)
	if err != nil {
		return err
	}

	err = os.WriteFile(filepath.Join(configDrivePath, "agent.crt"), []byte(agentCert), 0o400)
	if err != nil {
		return err
	}

	err = os.WriteFile(filepath.Join(configDrivePath, "agent.key"), []byte(agentKey), 0o400)
	if err != nil {
		return err
	}

	// OS-specific configuration.
	switch guestOS {
	case osinfo.FreeBSD:
		// rc.d service.
		err = os.MkdirAll(filepath.Join(configDrivePath, "rc.d"), 0o500)
		if err != nil {
			return err
		}

		// rc.d service for incus-agent.
		agentFile, err := incusAgentLoader.ReadFile("agent-loader/rc.d/incus-agent")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "rc.d", "incus-agent"), agentFile, 0o500)
		if err != nil {
			return err
		}

		// Setup script for incus-agent that is executed by the incus-agent service before starting.
		// The script sets up a temporary mount point, copies data from the mount (including incus-agent binary),
		// and then unmounts it. It also ensures appropriate permissions for the Incus agent's runtime directory.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/incus-agent-setup-freebsd")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "incus-agent-setup"), agentFile, 0o500)
		if err != nil {
			return err
		}

		// Install script for manual installs.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/install-freebsd.sh")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "install.sh"), agentFile, 0o500)
		if err != nil {
			return err
		}

	case osinfo.Linux:
		// Systemd units.
		err = os.MkdirAll(filepath.Join(configDrivePath, "systemd"), 0o500)
		if err != nil {
			return err
		}

		// Systemd unit for incus-agent. It ensures the incus-agent is copied from the shared filesystem before it is
		// started. The service is triggered dynamically via udev rules when certain virtio-ports are detected,
		// rather than being enabled at boot.
		agentFile, err := incusAgentLoader.ReadFile("agent-loader/systemd/incus-agent.service")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "systemd", "incus-agent.service"), agentFile, 0o400)
		if err != nil {
			return err
		}

		// Setup script for incus-agent that is executed by the incus-agent systemd unit before incus-agent is started.
		// The script sets up a temporary mount point, copies data from the mount (including incus-agent binary),
		// and then unmounts it. It also ensures appropriate permissions for the Incus agent's runtime directory.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/incus-agent-setup-linux")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "systemd", "incus-agent-setup"), agentFile, 0o500)
		if err != nil {
			return err
		}

		err = os.MkdirAll(filepath.Join(configDrivePath, "udev"), 0o500)
		if err != nil {
			return err
		}

		// Udev rules to start the incus-agent.service when QEMU serial devices (symlinks in virtio-ports) appear.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/systemd/incus-agent.rules")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "udev", "99-incus-agent.rules"), agentFile, 0o400)
		if err != nil {
			return err
		}

		// Install script for manual installs.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/install-linux.sh")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "install.sh"), agentFile, 0o700)
		if err != nil {
			return err
		}

	case osinfo.MacOS:
		// Launchd daemons.
		err = os.MkdirAll(filepath.Join(configDrivePath, "launchd"), 0o500)
		if err != nil {
			return err
		}

		// Launchd daemon for incus-agent.
		agentFile, err := incusAgentLoader.ReadFile("agent-loader/launchd/org.linuxcontainers.incus.macos-agent.plist")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "launchd", "org.linuxcontainers.incus.macos-agent.plist"), agentFile, 0o644)
		if err != nil {
			return err
		}

		// Setup script for incus-agent that is executed by launchd. For convenience, this script also
		// launches the agent. Because of Apple TCC, sh must be given full disk access in the relevant
		// system settings page. Other than that, this agent behaves roughly the same as the Linux one.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/incus-agent-setup-macos")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "incus-agent-setup"), agentFile, 0o500)
		if err != nil {
			return err
		}

		// Install script for manual installs.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/install-macos.sh")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "install.sh"), agentFile, 0o700)
		if err != nil {
			return err
		}

	case osinfo.Windows:
		// Setup script for incus-agent that is executed by Service Control Manager (SCM). Since by
		// default Windows cannot run a PowerShell script as a service without the help of a third
		// party, a bat file is used to then execute the PowerShell script doing the job.
		agentFile, err := incusAgentLoader.ReadFile("agent-loader/incus-agent-setup.ps1")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "incus-agent-setup.ps1"), agentFile, 0o500)
		if err != nil {
			return err
		}

		// Install script for manual installs.
		agentFile, err = incusAgentLoader.ReadFile("agent-loader/install.ps1")
		if err != nil {
			return err
		}

		err = os.WriteFile(filepath.Join(configDrivePath, "install.ps1"), agentFile, 0o700)
		if err != nil {
			return err
		}
	}

	// Templated files.
	templateFilesPath := filepath.Join(configDrivePath, "files")

	// Clear path and recreate.
	_ = os.RemoveAll(templateFilesPath)
	err = os.MkdirAll(templateFilesPath, 0o500)
	if err != nil {
		return err
	}

	// Template anything that needs templating.
	key := "volatile.apply_template"
	if d.localConfig[key] != "" {
		// Run any template that needs running.
		err = d.templateApplyNow(instance.TemplateTrigger(d.localConfig[key]), templateFilesPath)
		if err != nil {
			return err
		}

		// Record that the instance devices got modified and a full reset will be needed to get a consistent state.
		volatileSet[key] = ""
		volatileSet["volatile.vm.needs_reset"] = "true"
	}

	err = d.templateApplyNow("start", templateFilesPath)
	if err != nil {
		return err
	}

	// Copy the template metadata itself too.
	metaPath := filepath.Join(d.Path(), "metadata.yaml")
	if util.PathExists(metaPath) {
		err = internalUtil.FileCopy(metaPath, filepath.Join(templateFilesPath, "metadata.yaml"))
		if err != nil {
			return err
		}
	}

	// Only Linux guests support dynamic NIC configuration.
	if guestOS == osinfo.Linux {
		// Clear NICConfigDir to ensure that no leftover configuration is erroneously applied by the agent.
		nicConfigPath := filepath.Join(configDrivePath, deviceConfig.NICConfigDir)
		_ = os.RemoveAll(nicConfigPath)
		err = os.MkdirAll(nicConfigPath, 0o500)
		if err != nil {
			return err
		}

		// Add the NIC config.
		if util.IsTrue(d.expandedConfig["agent.nic_config"]) {
			sortedDevices := d.expandedDevices.Sorted()
			for _, entry := range sortedDevices {
				if entry.Config["type"] != "nic" {
					continue // Only keep NIC devices.
				}

				dev, err := d.FillNetworkDevice(entry.Name, entry.Config)
				if err != nil {
					return err
				}

				err = d.writeNICDevConfig(dev["mtu"], entry.Name, dev["name"], dev["hwaddr"])
				if err != nil {
					return fmt.Errorf("Failed writing NIC config for device %q: %w", entry.Name, err)
				}
			}
		}

		// Writing the connection info the config drive allows the agent to start /dev/incus very
		// early. This is important for systemd services which want or require /dev/incus/sock.
		connInfo, err := d.getAgentConnectionInfo()
		if err != nil {
			return err
		}

		if connInfo != nil {
			err = d.saveConnectionInfo(connInfo)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *qemu) templateApplyNow(trigger instance.TemplateTrigger, path string) error {
	// If there's no metadata, just return.
	fname := filepath.Join(d.Path(), "metadata.yaml")
	if !util.PathExists(fname) {
		return nil
	}

	// Parse the metadata.
	content, err := os.ReadFile(fname)
	if err != nil {
		return fmt.Errorf("Failed to read metadata: %w", err)
	}

	metadata := &api.ImageMetadata{}
	err = yaml.Load(content, metadata)
	if err != nil {
		return fmt.Errorf("Could not parse %s: %w", fname, err)
	}

	// Figure out the instance architecture.
	arch, err := osarch.ArchitectureName(d.architecture)
	if err != nil {
		arch, err = osarch.ArchitectureName(d.state.OS.Architectures[0])
		if err != nil {
			return fmt.Errorf("Failed to detect system architecture: %w", err)
		}
	}

	// Generate the instance metadata.
	instanceMeta := make(map[string]string)
	instanceMeta["name"] = d.name
	instanceMeta["type"] = "virtual-machine"
	instanceMeta["architecture"] = arch

	if d.ephemeral {
		instanceMeta["ephemeral"] = "true"
	} else {
		instanceMeta["ephemeral"] = "false"
	}

	// Open the output directory as an os.Root so all template writes stay confined to it.
	outputRoot, err := os.OpenRoot(path)
	if err != nil {
		return fmt.Errorf("Failed to open template output path: %w", err)
	}

	defer logger.WarnOnError(outputRoot.Close, "Failed to close template output path")

	// Go through the templates.
	for tplPath, tpl := range metadata.Templates {
		err = func(tplPath string, tpl *api.ImageMetadataTemplate) error {
			var w *os.File

			// Check if the template should be applied now.
			found := slices.Contains(tpl.When, string(trigger))

			if !found {
				return nil
			}

			// Perform some early security checks on the template itself.
			if filepath.Base(tpl.Template) != tpl.Template {
				return errors.New("Template path is attempting to read outside of template directory")
			}

			tplDirStat, err := os.Lstat(d.TemplatesPath())
			if err != nil {
				return fmt.Errorf("Couldn't access template directory: %w", err)
			}

			if !tplDirStat.IsDir() {
				return errors.New("Template directory isn't a regular directory")
			}

			tplFileStat, err := os.Lstat(filepath.Join(d.TemplatesPath(), tpl.Template))
			if err != nil {
				return fmt.Errorf("Couldn't access template file: %w", err)
			}

			if tplFileStat.Mode()&os.ModeSymlink == os.ModeSymlink {
				return errors.New("Template file is a symlink")
			}

			// Create the file itself.
			w, err = outputRoot.Create(fmt.Sprintf("%s.out", tpl.Template))
			if err != nil {
				return err
			}

			// Fix ownership and mode.
			err = w.Chmod(0o644)
			if err != nil {
				return err
			}

			defer logger.WarnOnError(w.Close, "Failed to close file")

			// Read the template.
			tplString, err := os.ReadFile(filepath.Join(d.TemplatesPath(), tpl.Template))
			if err != nil {
				return fmt.Errorf("Failed to read template file: %w", err)
			}

			configGet := func(confKey, confDefault *pongo2.Value) *pongo2.Value {
				val, ok := d.expandedConfig[confKey.String()]
				if !ok {
					return confDefault
				}

				return pongo2.AsValue(strings.TrimRight(val, "\r\n"))
			}

			// Render the template.
			err = internalUtil.RenderTemplateFile(w, string(tplString), pongo2.Context{
				"trigger":    trigger,
				"path":       tplPath,
				"container":  instanceMeta, // FIXME: remove once most images have moved away.
				"instance":   instanceMeta,
				"config":     d.expandedConfig,
				"devices":    d.expandedDevices,
				"properties": tpl.Properties,
				"config_get": configGet,
			})
			if err != nil {
				return fmt.Errorf("Failed to render template: %w", err)
			}

			return w.Close()
		}(tplPath, tpl)
		if err != nil {
			return err
		}
	}

	return nil
}

// deviceBootPriorities returns a map keyed on device name containing the boot index to use.
// Qemu tries to boot devices in order of boot index (lowest first).
func (d *qemu) deviceBootPriorities(base int) (map[string]int, error) {
	type devicePrios struct {
		Name     string
		BootPrio uint32
	}

	devices := []devicePrios{}

	for _, dev := range d.expandedDevices.Sorted() {
		if dev.Config["type"] != "disk" && dev.Config["type"] != "nic" {
			continue
		}

		bootPrio := uint32(0) // Default to lowest priority.
		if dev.Config["boot.priority"] != "" {
			prio, err := strconv.ParseInt(dev.Config["boot.priority"], 10, 32)
			if err != nil {
				return nil, fmt.Errorf("Invalid boot.priority for device %q: %w", dev.Name, err)
			}

			bootPrio = uint32(prio)
		} else if dev.Config["path"] == "/" {
			bootPrio = 1 // Set boot priority of root disk higher than any device without a boot prio.
		}

		devices = append(devices, devicePrios{Name: dev.Name, BootPrio: bootPrio})
	}

	// Sort devices by priority (use SliceStable so that devices with the same boot priority stay in the same
	// order each boot based on the device order provided by the d.expandedDevices.Sorted() function).
	// This is important because as well as providing a predictable boot index order, the boot index number can
	// also be used for other properties (such as disk SCSI ID) which can result in it being given different
	// device names inside the guest based on the device order.
	sort.SliceStable(devices, func(i, j int) bool { return devices[i].BootPrio > devices[j].BootPrio })

	sortedDevs := make(map[string]int, len(devices))
	for bootIndex, dev := range devices {
		sortedDevs[dev.Name] = bootIndex + base
	}

	return sortedDevs, nil
}

func (d *qemu) getStartupRTCAdjustment() time.Duration {
	// Get the current values.
	adjustment := d.parseRTC("volatile.vm.rtc_adjustment")
	offset := d.parseRTC("volatile.vm.rtc_offset")

	// Reset to handle new VM-generated updates.
	adjustment += offset
	offset = 0

	changes := map[string]string{
		"volatile.vm.rtc_adjustment": strconv.Itoa(adjustment),
		"volatile.vm.rtc_offset":     strconv.Itoa(offset),
	}

	err := d.VolatileSet(changes)
	if err != nil {
		d.logger.Error("Failed to set RTC change offset", logger.Ctx{"changes": changes, "err": err})
	}

	return time.Duration(adjustment) * time.Second
}

func (d *qemu) parseRTC(key string) int {
	offset := 0

	val, ok := d.localConfig[key]
	if ok {
		var err error

		offset, err = strconv.Atoi(val)
		if err != nil {
			offset = 0
			d.logger.Error("Failed to parse RTC volatile key")
		}
	}

	return offset
}

// onRTCChange saves rtc change.
func (d *qemu) onRTCChange(change int) error {
	offset := d.parseRTC("volatile.vm.rtc_offset")
	if offset != change {
		changes := map[string]string{"volatile.vm.rtc_offset": strconv.Itoa(change)}
		err := d.VolatileSet(changes)
		if err != nil {
			d.logger.Error("Failed to set rtc change offset ", logger.Ctx{"changes": changes, "err": err})
		}

		return err
	}

	return nil
}

// gpuNativeContextConfig scans the device run configs for a virtio-gpu DRM native
// context GPU. It returns whether one is present, its resolved host DRM render node
// (which may be empty, meaning QEMU should use its default render node), and the
// host-visible blob window size in bytes for the device's hostmem property.
func (d *qemu) gpuNativeContextConfig(devConfs []*deviceConfig.RunConfig) (bool, string, string) {
	enabled := false
	rendernode := ""
	hostmem := ""

	for _, runConf := range devConfs {
		isNativeContext := false
		devRenderNode := ""
		devHostmem := ""

		for _, item := range runConf.GPUDevice {
			switch item.Key {
			case "gpuType":
				if item.Value == "native-context" {
					isNativeContext = true
				}

			case "rendernode":
				devRenderNode = item.Value

			case "hostmem":
				devHostmem = item.Value
			}
		}

		if isNativeContext {
			enabled = true
			if devRenderNode != "" {
				rendernode = devRenderNode
			}

			if devHostmem != "" {
				hostmem = devHostmem
			}
		}
	}

	return enabled, rendernode, hostmem
}

// generateQemuConfig generates the QEMU configuration.
func (d *qemu) generateQemuConfig(bs *qemuBootState, mountInfo *storagePools.MountInfo, busName string, vsockFD int, devConfs []*deviceConfig.RunConfig, fdFiles *[]*os.File) ([]monitorHook, error) {
	var monHooks []monitorHook

	isWindows := d.GuestOS() == osinfo.Windows
	conf := qemuBase(&qemuBaseOpts{d.Architecture(), util.IsTrue(d.expandedConfig["security.iommu"]), bs.MachineType})

	// Set OS Specific qemu args.
	conf = append(conf, d.osVersionSpecificOptions()...)

	err := d.addCPUMemoryConfig(&conf, bs)
	if err != nil {
		return nil, err
	}

	// Parse raw.qemu.
	rawOptions := []string{}
	if d.expandedConfig["raw.qemu"] != "" {
		rawOptions, err = shellquote.Split(d.expandedConfig["raw.qemu"])
		if err != nil {
			return nil, err
		}
	}

	// Allow disabling the UEFI firmware.
	if slices.Contains(rawOptions, "-bios") || slices.Contains(rawOptions, "-kernel") {
		d.logger.Warn("Starting VM without default firmware (-bios or -kernel in raw.qemu)")
	} else if d.architectureSupportsUEFI(d.architecture) {
		// Determine expected firmware.
		firmwares, err := d.firmwarePairs()
		if err != nil {
			return nil, err
		}

		var efiCode string
		unified := false
		for _, firmware := range firmwares {
			if firmware.Vars == "" {
				// Unified firmware image (e.g. AMD SEV) with no separate vars store.
				if util.PathExists(firmware.Code) {
					efiCode = firmware.Code
					unified = true
					break
				}
			} else if util.PathExists(filepath.Join(d.Path(), filepath.Base(firmware.Vars))) {
				efiCode = firmware.Code
				break
			}
		}

		if efiCode == "" {
			return nil, fmt.Errorf("Unable to locate matching firmware: %+v", firmwares)
		}

		driveFirmwareOpts := qemuDriveFirmwareOpts{
			roPath: efiCode,
		}

		if !unified {
			// Open the UEFI NVRAM file and pass it via file descriptor to QEMU.
			// This is so the QEMU process can still read/write the file after it has dropped its user privs.
			nvRAMFile, err := os.Open(d.nvramPath())
			if err != nil {
				return nil, fmt.Errorf("Failed opening NVRAM file: %w", err)
			}

			driveFirmwareOpts.nvramPath = fmt.Sprintf("/dev/fd/%d", d.addFileDescriptor(fdFiles, nvRAMFile))
		}

		conf = append(conf, qemuDriveFirmware(&driveFirmwareOpts)...)
	}

	// QMP socket. Reference it through a short /proc/self/fd path to handle
	// run paths that exceed the unix socket path limit.
	monitorDir, err := os.OpenFile(d.RunPath(), unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
	if err != nil {
		return nil, err
	}

	monitorDirFD := d.addFileDescriptor(fdFiles, monitorDir)
	monitorPath := fmt.Sprintf("/proc/self/fd/%d/qemu.monitor", monitorDirFD)

	conf = append(conf, qemuControlSocket(&qemuControlSocketOpts{monitorPath})...)

	// Console output.
	conf = append(conf, qemuConsole()...)

	// VM core info (memory dump).
	if !util.IsTrue(d.expandedConfig["security.sev"]) && !slices.Contains([]int{osarch.ARCH_64BIT_POWERPC_LITTLE_ENDIAN, osarch.ARCH_64BIT_S390_BIG_ENDIAN}, d.architecture) {
		conf = append(conf, qemuCoreInfo()...)
	}

	// Setup the bus allocator.
	bus := qemuNewBus(busName, &conf)

	// Add IOMMU.
	if util.IsTrue(d.expandedConfig["security.iommu"]) && d.architectureSupportsUEFI(d.architecture) {
		devBus, devAddr, multi := bus.allocateDirect()
		iommuOpts := qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		}

		conf = append(conf, qemuIOMMU(&iommuOpts, isWindows)...)
	}

	// Now add the fixed set of devices. The multi-function groups used for these fixed internal devices are
	// specifically chosen to ensure that we consume exactly 4 PCI bus ports (on PCIe bus). This ensures that
	// the first user device NIC added will use the 5th PCI bus port and will be consistently named enp5s0
	// on PCIe (which we need to maintain compatibility with network configuration in our existing VM images).
	// It's also meant to group all low-bandwidth internal devices onto a single address. PCIe bus allows a
	// total of 256 devices, but this assumes 32 chassis * 8 function. By using VFs for the internal fixed
	// devices we avoid consuming a chassis for each one.
	devBus, devAddr, multi := bus.allocate(busFunctionGroupGeneric)
	balloonOpts := qemuDevOpts{
		busName:       bus.name,
		devBus:        devBus,
		devAddr:       devAddr,
		multifunction: multi,
	}

	conf = append(conf, qemuBalloon(&balloonOpts)...)

	devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
	rngOpts := qemuDevOpts{
		busName:       bus.name,
		devBus:        devBus,
		devAddr:       devAddr,
		multifunction: multi,
	}

	conf = append(conf, qemuRNG(&rngOpts)...)

	devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
	keyboardOpts := qemuDevOpts{
		busName:       bus.name,
		devBus:        devBus,
		devAddr:       devAddr,
		multifunction: multi,
	}

	conf = append(conf, qemuKeyboard(&keyboardOpts)...)

	devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
	tabletOpts := qemuDevOpts{
		busName:       bus.name,
		devBus:        devBus,
		devAddr:       devAddr,
		multifunction: multi,
	}

	conf = append(conf, qemuTablet(&tabletOpts)...)

	// Existing vsock ID from volatile.
	vsockID, err := d.getVsockID()
	if err != nil {
		return nil, err
	}

	devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
	vsockOpts := qemuVsockOpts{
		dev: qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		},
		vsockFD: vsockFD,
		vsockID: vsockID,
	}

	conf = append(conf, qemuVsock(&vsockOpts)...)

	info := DriverStatuses()[instancetype.VM].Info
	_, spice := info.Features["spice"]
	_, plan9 := info.Features["plan9"]
	_, virtioSound := info.Features["virtio-sound"]
	_, virtioVGA := info.Features["virtio-vga"]

	devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
	serialOpts := qemuSerialOpts{
		dev: qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		},
		charDevName:      qemuSerialChardevName,
		ringbufSizeBytes: qmp.RingbufSize,
		spice:            spice,
	}

	conf = append(conf, qemuSerial(&serialOpts)...)

	// s390x doesn't really have USB.
	if d.architecture != osarch.ARCH_64BIT_S390_BIG_ENDIAN {
		devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
		usbOpts := qemuUSBOpts{
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
			ports:         qemuSparseUSBPorts,
			spice:         spice,
		}

		conf = append(conf, qemuUSB(&usbOpts)...)
	}

	// virtio-sound-pci devices can't be migrated and don't have a CCW equivalent.
	// NOTE: We can't use CanLiveMigrate during instance startup as the boot state hasn't yet been recorded.
	if virtioSound && !isWindows && util.IsFalseOrEmpty(d.expandedConfig["migration.stateful"]) && d.architecture != osarch.ARCH_64BIT_S390_BIG_ENDIAN {
		devBus, devAddr, multi = bus.allocate(busFunctionGroupGeneric)
		audioOpts := qemuAudioOpts{
			dev: qemuDevOpts{
				busName:       bus.name,
				devBus:        devBus,
				devAddr:       devAddr,
				multifunction: multi,
			},
			spice: spice,
		}

		conf = append(conf, qemuAudio(&audioOpts)...)
	}

	if util.IsTrue(d.expandedConfig["security.csm"]) {
		// Allocate a regular entry to keep things aligned normally (avoid NICs getting a different name).
		_, _, _ = bus.allocate(busFunctionGroupNone)

		// Allocate a direct entry so the SCSI controller can be seen by seabios.
		devBus, devAddr, multi = bus.allocateDirect()
	} else {
		devBus, devAddr, multi = bus.allocate(busFunctionGroupNone)
	}

	scsiOpts := qemuDevOpts{
		busName:       bus.name,
		devBus:        devBus,
		devAddr:       devAddr,
		multifunction: multi,
	}

	conf = append(conf, qemuSCSI(&scsiOpts, bs.getSCSIQueues())...)

	// Export the config directory and agent as 9p drives when supported.
	if !isWindows && plan9 {
		devBus, devAddr, multi = bus.allocate(busFunctionGroup9p)
		driveConfig9pOpts := qemuDriveConfigOpts{
			dev: qemuDevOpts{
				busName:       bus.name,
				devBus:        devBus,
				devAddr:       devAddr,
				multifunction: multi,
			},
			name:     "config",
			protocol: "9p",
			path:     d.configDriveMountPath(),
		}

		conf = append(conf, qemuDriveConfig(&driveConfig9pOpts)...)

		// Pass in the agents if INCUS_AGENT_PATH is set.
		if util.PathExists(os.Getenv("INCUS_AGENT_PATH")) {
			devBus, devAddr, multi = bus.allocate(busFunctionGroup9p)
			driveConfig9pOpts := qemuDriveConfigOpts{
				dev: qemuDevOpts{
					busName:       bus.name,
					devBus:        devBus,
					devAddr:       devAddr,
					multifunction: multi,
				},
				name:     "agent",
				protocol: "9p",
				path:     os.Getenv("INCUS_AGENT_PATH"),
			}

			conf = append(conf, qemuDriveConfig(&driveConfig9pOpts)...)
		}
	}

	// If user has requested AMD SEV, check if supported and add to QEMU config.
	if util.IsTrue(d.expandedConfig["security.sev"]) {
		sevOpts, err := d.setupSEV(fdFiles)
		if err != nil {
			return nil, err
		}

		if sevOpts != nil {
			for i := range conf {
				if conf[i].Name == "machine" {
					conf[i].Entries["memory-encryption"] = "sev0"
					break
				}
			}

			conf = append(conf, qemuSEV(sevOpts)...)
		}
	}

	if util.IsTrue(d.expandedConfig["security.csm"]) {
		// Allocate a regular entry to keep things aligned normally (avoid NICs getting a different name).
		_, _, _ = bus.allocate(busFunctionGroupNone)

		// Allocate a direct entry so the GPU can be seen by seabios.
		devBus, devAddr, multi = bus.allocateDirect()
	} else {
		devBus, devAddr, multi = bus.allocate(busFunctionGroupNone)
	}

	// When any attached GPU device requests virtio-gpu DRM native context, the default
	// emulated GPU is switched to a GL-capable device with blob and native context
	// enabled rather than a plain virtio-gpu. This requires a recent QEMU.
	nativeContext, _, hostmem := d.gpuNativeContextConfig(devConfs)
	if nativeContext {
		qemuVer, err := d.version()
		if err != nil {
			return nil, fmt.Errorf("GPU native context requires a known QEMU version: %w", err)
		}

		qemuVer11, _ := version.NewDottedVersion("11.0.0")
		if qemuVer.Compare(qemuVer11) < 0 {
			return nil, fmt.Errorf("GPU native context requires QEMU 11.0.0 or newer (have %s)", qemuVer.String())
		}
	}

	gpuOpts := qemuGpuOpts{
		dev: qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		},
		architecture:  d.Architecture(),
		virtioVGA:     virtioVGA,
		nativeContext: nativeContext,
		hostmem:       hostmem,
	}

	conf = append(conf, qemuGPU(&gpuOpts)...)

	// Dynamic devices.
	base := 0
	if slices.Contains(rawOptions, "-kernel") {
		base = 1
	}

	bootIndexes, err := d.deviceBootPriorities(base)
	if err != nil {
		return nil, fmt.Errorf("Error calculating boot indexes: %w", err)
	}

	// Record the mounts we are going to do inside the VM using the agent.
	agentMounts := []instancetype.VMAgentMount{}

	// These devices are sorted so that NICs are added first to ensure that the first NIC can use the 5th
	// PCIe bus port and will be consistently named enp5s0 for compatibility with network configuration in our
	// existing VM images. Even on non-PCIe buses having NICs first means that their names won't change when
	// other devices are added.
	for _, runConf := range devConfs {
		// Add drive devices.
		if len(runConf.Mounts) > 0 {
			for _, drive := range runConf.Mounts {
				var monHook monitorHook

				// Check if the user has overridden the bus.
				busName := "virtio-scsi"
				for _, opt := range drive.Opts {
					if !strings.HasPrefix(opt, "bus=") {
						continue
					}

					busName = strings.TrimPrefix(opt, "bus=")
					break
				}

				qemuDev := make(map[string]any)
				if slices.Contains([]string{"9p", "nvme", "virtio-blk", "virtiofs"}, busName) {
					// Allocate a PCI(e) port and write it to the config file so QMP can "hotplug" the
					// drive into it later.
					functionGroup := busFunctionGroupNone
					if busName == "9p" {
						functionGroup = busFunctionGroup9p
					}

					devBus, devAddr, multi := bus.allocate(functionGroup)

					// Populate the qemu device with port info.
					qemuDev["bus"] = devBus
					qemuDev["addr"] = devAddr
					qemuDev["multifunction"] = multi
				}

				if drive.TargetPath == "/" {
					monHook, err = d.addRootDriveConfig(qemuDev, mountInfo, bootIndexes, drive)
				} else if drive.FSType == "9p" {
					if busName == "9p" {
						conf = append(conf, d.driveDirConfig9p(qemuDev, bus.name, &agentMounts, drive)...)
					} else {
						monHook, err = d.addDriveDirConfigVirtiofs(qemuDev, &agentMounts, drive)
					}
				} else {
					monHook, err = d.addDriveConfig(qemuDev, bootIndexes, drive)
				}

				if err != nil {
					return nil, fmt.Errorf("Failed setting up disk device %q: %w", drive.DevName, err)
				}

				if monHook != nil {
					monHooks = append(monHooks, monHook)
				}
			}
		}

		// Add network device.
		if len(runConf.NetworkInterface) > 0 {
			qemuDev := make(map[string]any)
			busName := bus.name
			if runConf.UseUSBBus {
				busName = "usb"
				qemuDev["bus"] = "qemu_usb.0"
			} else if slices.Contains([]string{"pcie", "pci"}, busName) {
				// Allocate a PCI(e) port and write it to the config file so QMP can "hotplug" the
				// NIC into it later.
				devBus, devAddr, multi := bus.allocate(busFunctionGroupNone)

				// Populate the qemu device with port info.
				qemuDev["bus"] = devBus
				qemuDev["addr"] = devAddr

				if multi {
					qemuDev["multifunction"] = true
				}
			}

			monHook, err := d.addNetDevConfig(busName, qemuDev, bootIndexes, runConf.NetworkInterface)
			if err != nil {
				return nil, err
			}

			monHooks = append(monHooks, monHook)
		}

		// Add GPU device.
		if len(runConf.GPUDevice) > 0 {
			err = d.addGPUDevConfig(&conf, bus, runConf.GPUDevice)
			if err != nil {
				return nil, err
			}
		}

		// Add PCI device.
		if len(runConf.PCIDevice) > 0 {
			err = d.addPCIDevConfig(&conf, bus, runConf.PCIDevice)
			if err != nil {
				return nil, err
			}
		}

		// Add USB devices.
		for _, usbDev := range runConf.USBDevice {
			monHook, err := d.addUSBDeviceConfig(usbDev)
			if err != nil {
				return nil, err
			}

			monHooks = append(monHooks, monHook)
		}

		// Add TPM device.
		if len(runConf.TPMDevice) > 0 {
			err = d.addTPMDeviceConfig(&conf, runConf.TPMDevice, fdFiles)
			if err != nil {
				return nil, err
			}
		}
	}

	// VM generation ID is only available on x86.
	if d.architecture == osarch.ARCH_64BIT_INTEL_X86 {
		err = d.addVmgenDeviceConfig(&conf, d.localConfig["volatile.uuid.generation"])
		if err != nil {
			return nil, err
		}
	}

	// Allocate 8 PCI slots for hotplug devices.
	for range 8 {
		bus.allocate(busFunctionGroupNone)
	}

	if !isWindows {
		// Write the agent mount config.
		agentMountJSON, err := json.Marshal(agentMounts)
		if err != nil {
			return nil, fmt.Errorf("Failed marshalling agent mounts to JSON: %w", err)
		}

		agentMountFile := filepath.Join(d.Path(), "config", "agent-mounts.json")
		err = os.WriteFile(agentMountFile, agentMountJSON, 0o400)
		if err != nil {
			return nil, fmt.Errorf("Failed writing agent mounts file: %w", err)
		}
	}

	// process any user-specified overrides
	confOverride, ok := d.expandedConfig["raw.qemu.conf"]
	if ok {
		d.conf, err = qemuRawCfgOverride(conf, confOverride)
		if err != nil {
			return nil, err
		}
	} else {
		d.conf = conf
	}

	return monHooks, nil
}

// writeQemuConfigFile writes the QEMU config file.
// It writes the config file inside the VM's log path.
func (d *qemu) writeQemuConfigFile(configPath string) error {
	// Write the config file to disk.
	sb := qemuStringifyCfg(d.conf...)
	return os.WriteFile(configPath, []byte(sb.String()), 0o640)
}

// getCPUOpts retrieves configuration options for virtualized CPUs and memory.
func (d *qemu) getCPUOpts(cpuInfo *qemuCPUTopology, memSizeBytes int64) (*qemuCPUOpts, error) {
	cpuOpts := qemuCPUOpts{
		architecture: d.architecture,
	}

	hostNodes := []uint64{}
	if cpuInfo.VCPUs == nil {
		if cpuInfo.Explicit {
			// An explicit CPU topology was requested, expose it verbatim to the guest.
			// This is incompatible with CPU hotplugging.
			cpuOpts.cpuSockets = cpuInfo.Sockets
			cpuOpts.cpuCores = cpuInfo.Cores
			cpuOpts.cpuThreads = cpuInfo.Threads
			cpuOpts.cpuCount = cpuInfo.Sockets * cpuInfo.Cores * cpuInfo.Threads
		} else if d.architectureSupportsCPUHotplug() {
			// If not pinning, default to exposing cores.
			// Only one CPU will be added here, as the others will be hotplugged during start.
			cpuOpts.cpuCount = 1
			cpuOpts.cpuCores = 1
			cpuOpts.cpuSockets = 1
			cpuOpts.cpuThreads = 1

			// Expose the total requested by the user already so the hotplug limit can be set higher if needed.
			cpuOpts.cpuRequested = cpuInfo.Cores
		} else {
			cpuOpts.cpuCount = cpuInfo.Cores
			cpuOpts.cpuCores = cpuInfo.Cores
			cpuOpts.cpuSockets = 1
			cpuOpts.cpuThreads = 1
		}

		hostNodes = []uint64{0}

		// Handle NUMA restrictions.
		numaNodes := d.expandedConfig["limits.cpu.nodes"]
		if numaNodes != "" {
			if numaNodes == "balanced" {
				numaNodes = d.expandedConfig["volatile.cpu.nodes"]
			}

			// Parse the NUMA restriction.
			numaNodeSet, err := resources.ParseNumaNodeSet(numaNodes)
			if err != nil {
				return nil, err
			}

			cpuOpts.memoryHostNodes = numaNodeSet
		}
	} else {
		// Figure out socket-id/core-id/thread-id for all vcpus.
		vcpuSocket := map[uint64]uint64{}
		vcpuCore := map[uint64]uint64{}
		vcpuThread := map[uint64]uint64{}
		vcpu := uint64(0)
		for i := range cpuInfo.Sockets {
			for j := range cpuInfo.Cores {
				for k := range cpuInfo.Threads {
					vcpuSocket[vcpu] = uint64(i)
					vcpuCore[vcpu] = uint64(j)
					vcpuThread[vcpu] = uint64(k)
					vcpu++
				}
			}
		}

		// Prepare the NUMA map.
		numa := []qemuNumaEntry{}
		numaIDs := []uint64{}
		numaNode := uint64(0)
		for hostNode, entry := range cpuInfo.Nodes {
			hostNodes = append(hostNodes, hostNode)

			numaIDs = append(numaIDs, numaNode)
			for _, vcpu := range entry {
				numa = append(numa, qemuNumaEntry{
					node:   numaNode,
					socket: vcpuSocket[vcpu],
					core:   vcpuCore[vcpu],
					thread: vcpuThread[vcpu],
				})
			}

			numaNode++
		}

		// Prepare context.
		cpuOpts.cpuCount = len(cpuInfo.VCPUs)
		cpuOpts.cpuSockets = cpuInfo.Sockets
		cpuOpts.cpuCores = cpuInfo.Cores
		cpuOpts.cpuThreads = cpuInfo.Threads
		cpuOpts.cpuNumaNodes = numaIDs
		cpuOpts.cpuNumaMapping = numa
		cpuOpts.cpuNumaHostNodes = hostNodes
	}

	cpuOpts.hugepages = ""
	if util.IsTrue(d.expandedConfig["limits.memory.hugepages"]) {
		hugetlb, err := localUtil.HugepagesPath()
		if err != nil {
			return nil, err
		}

		cpuOpts.hugepages = hugetlb
	}

	// Determine per-node memory limit.
	memSizeMB := memSizeBytes / 1024 / 1024
	nodeMemory := memSizeMB
	if d.architecture == osarch.ARCH_64BIT_INTEL_X86 {
		nodeMemory = memSizeMB / int64(len(hostNodes))
	}

	cpuOpts.memory = nodeMemory

	return &cpuOpts, nil
}

// addCPUMemoryConfig adds the qemu config required for setting the number of virtualised CPUs and memory.
// If sb is nil then no config is written.
func (d *qemu) addCPUMemoryConfig(conf *[]cfg.Section, bs *qemuBootState) error {
	cpuOpts, err := d.getCPUOpts(bs.CPUTopology, bs.MemoryTopology.Base)
	if err != nil {
		return err
	}

	// A fixed topology is written verbatim, either due to CPU pinning or an explicit topology request.
	cpuFixedTopology := bs.CPUTopology.VCPUs != nil || bs.CPUTopology.Explicit

	*conf = append(*conf, qemuMemory(&qemuMemoryOpts{bs.MemoryTopology.Base / 1024 / 1024, bs.MemoryTopology.Max / 1024 / 1024})...)
	*conf = append(*conf, qemuCPU(cpuOpts, cpuFixedTopology)...)

	return nil
}

// addFileDescriptor adds a file path to the list of files to open and pass file descriptor to qemu.
// Returns the file descriptor number that qemu will receive.
func (d *qemu) addFileDescriptor(fdFiles *[]*os.File, file *os.File) int {
	// Append the tap device file path to the list of files to be opened and passed to qemu.
	*fdFiles = append(*fdFiles, file)
	return 2 + len(*fdFiles) // Use 2+fdFiles count, as first user file descriptor is 3.
}

// ImageMetadataDir returns the metadata image directory for the given instance path.
func ImageMetadataDir(instancePath string) string {
	return filepath.Join(instancePath, "image_metadata")
}

// imageMetadataDir returns the instance's metadata image directory.
func (d *qemu) imageMetadataDir() string {
	return ImageMetadataDir(d.Path())
}

// imageMetadataPath returns the instance's image metadata file path.
func (d *qemu) imageMetadataPath(devName string) string {
	return filepath.Join(d.imageMetadataDir(), fmt.Sprintf("%s.qcow2", devName))
}

// ensureMetadataImage creates the metadata file if needed.
func (d *qemu) ensureMetadataImage(rawPath string, devName string) (string, string, error) {
	if rawPath == "" {
		return "", "", errors.New("Raw disk path is empty")
	}

	isQcow2, err := d.isQCOW2(rawPath)
	if err != nil {
		return "", "", err
	}

	if isQcow2 {
		// Image is qcow2 already, don't touch anything
		return rawPath, "", nil
	}

	qcow2Dir := d.imageMetadataDir()
	err = os.MkdirAll(qcow2Dir, 0o700)
	if err != nil {
		return "", "", fmt.Errorf("Failed creating metadata image directory: %w", err)
	}

	qcow2Path := d.imageMetadataPath(devName)

	// If we already have metadata image, then just use it
	if util.PathExists(qcow2Path) {
		imgInfo, err := storageDrivers.Qcow2Info(qcow2Path)
		if err != nil {
			return "", "", err
		}

		if imgInfo.Format != storageDrivers.BlockVolumeTypeQcow2 {
			return "", "", fmt.Errorf("Existing metadata image %q is not qcow2", qcow2Path)
		}

		// Get size of disk block device.
		blockDiskSize, err := storageDrivers.BlockDiskSizeBytes(rawPath)
		if err != nil {
			return "", "", fmt.Errorf("Error getting block device size %q: %w", rawPath, err)
		}

		// Keep the metadata image in sync with the disk size as the volume
		// may have been resized while the instance was stopped.
		if int64(imgInfo.VirtualSize) != blockDiskSize {
			err = d.resizeMetadataImage(qcow2Path, rawPath, blockDiskSize, int64(imgInfo.VirtualSize))
			if err != nil {
				return "", "", err
			}
		}

		return qcow2Path, rawPath, nil
	}

	// Metadata image is not there yet, let's create it!
	// To do this, we need a tiny trick.
	// We can't use existing raw image (block device) with qemu-img, because
	// qemu-img will overwrite it and we lose data. So, instead we should
	// calculate it's size and create an empty temporary raw image with the same size
	// and use it.

	// Get size of disk block device.
	blockDiskSize, err := storageDrivers.BlockDiskSizeBytes(rawPath)
	if err != nil {
		return "", "", fmt.Errorf("Error getting block device size %q: %w", rawPath, err)
	}

	rawSize := fmt.Sprintf("%d", blockDiskSize)
	tmpPath := filepath.Join(qcow2Dir, fmt.Sprintf("%s.raw.tmp", devName))
	defer func() {
		_ = os.Remove(tmpPath)
	}()

	_, err = subprocess.RunCommand("qemu-img", "create", "-f", "raw", tmpPath, rawSize)
	if err != nil {
		return "", "", fmt.Errorf("Failed creating temporary raw data-file: %w", err)
	}

	_, err = subprocess.RunCommand("qemu-img", "create", "-f", "qcow2", "-o", fmt.Sprintf("data_file=%s,data_file_raw=on,preallocation=metadata", tmpPath), qcow2Path, rawSize)
	if err != nil {
		return "", "", fmt.Errorf("Failed creating qcow2 metadata image %q: %w", qcow2Path, err)
	}

	// Now everything is ready, we have a qcow2 image to keep metadata (bitmaps),
	// and have set rawPath as a data-file for this image. But notice, it won't work
	// just as it is, because QEMU will fail to open device from rawPath, instead we will
	// have to use some trickery later to replace data-file in the existing image with
	// /dev/fdset/<x> path and send rawPath as an FD.

	return qcow2Path, rawPath, nil
}

// resizeMetadataImage resizes the qcow2 metadata image to match the raw disk size.
// The data-file has to be overridden as the one recorded in the image no longer exists.
func (d *qemu) resizeMetadataImage(qcow2Path string, rawPath string, newSize int64, oldSize int64) error {
	fInfo, err := os.Stat(rawPath)
	if err != nil {
		return err
	}

	rawDriver := "file"
	if linux.IsBlockdev(fInfo.Mode()) {
		rawDriver = "host_device"
	}

	escape := func(s string) string {
		return strings.ReplaceAll(s, ",", ",,")
	}

	args := []string{"resize"}
	if newSize < oldSize {
		args = append(args, "--shrink")
	} else {
		args = append(args, "--preallocation=metadata")
	}

	args = append(args, "--image-opts", fmt.Sprintf("driver=qcow2,file.filename=%s,data-file.driver=%s,data-file.filename=%s", escape(qcow2Path), rawDriver, escape(rawPath)), fmt.Sprintf("%d", newSize))

	_, err = subprocess.RunCommand("qemu-img", args...)
	if err != nil {
		return fmt.Errorf("Failed resizing qcow2 metadata image %q: %w", qcow2Path, err)
	}

	return nil
}

// addRootDriveConfig adds the qemu config required for adding the root drive.
func (d *qemu) addRootDriveConfig(qemuDev map[string]any, mountInfo *storagePools.MountInfo, bootIndexes map[string]int, rootDriveConf deviceConfig.MountEntryItem) (monitorHook, error) {
	if rootDriveConf.TargetPath != "/" {
		return nil, errors.New("Non-root drive config supplied")
	}

	if !d.storagePool.Driver().Info().Remote && mountInfo.DiskPath == "" {
		return nil, errors.New("No root disk path available from mount")
	}

	devPath, dataFilePath, err := d.ensureMetadataImage(mountInfo.DiskPath, rootDriveConf.DevName)
	if err != nil {
		return nil, err
	}

	// Generate a new device config with the root device path expanded.
	driveConf := deviceConfig.MountEntryItem{
		DevName:      rootDriveConf.DevName,
		DevPath:      devPath,
		DataFilePath: dataFilePath,
		BackingPath:  mountInfo.BackingPath,
		Opts:         rootDriveConf.Opts,
		TargetPath:   rootDriveConf.TargetPath,
		Limits:       rootDriveConf.Limits,
	}

	if d.storagePool.Driver().Info().Remote {
		vol := d.storagePool.GetVolume(storageDrivers.VolumeTypeVM, storageDrivers.ContentTypeBlock, project.Instance(d.project.Name, d.name), nil)

		if slices.Contains([]string{"ceph", "cephfs"}, d.storagePool.Driver().Info().Name) {
			config := d.storagePool.ToAPI().Config

			userName := config["ceph.user.name"]
			if userName == "" {
				userName = storageDrivers.CephDefaultUser
			}

			clusterName := config["ceph.cluster_name"]
			if clusterName == "" {
				clusterName = storageDrivers.CephDefaultUser
			}

			rbdImageName := storageDrivers.CephGetRBDImageName(vol, "", false)
			driveConf.DevPath = device.DiskGetRBDFormat(clusterName, userName, config["ceph.osd.pool_name"], rbdImageName)
		}
	}

	return d.addDriveConfig(qemuDev, bootIndexes, driveConf)
}

// driveDirConfig9p generates the qemu config required for adding a supplementary drive directory share using 9p.
func (d *qemu) driveDirConfig9p(qemuDev map[string]any, busName string, agentMounts *[]instancetype.VMAgentMount, driveConf deviceConfig.MountEntryItem) []cfg.Section {
	mountTag := d.mountTagName(driveConf.DevName, qemuMountTag9pMaxLength)

	agentMount := instancetype.VMAgentMount{
		Source: mountTag,
		Target: driveConf.TargetPath,
		FSType: "9p",

		// We need to specify to use the virtio transport to support more VM guest OSes.
		// Also set the msize to 32MB to allow for reasonably fast 9p access.
		Options: []string{"trans=virtio,msize=33554432"},
	}

	readonly := slices.Contains(driveConf.Opts, "ro")

	// Indicate to agent to mount this readonly. Note: This is purely to indicate to VM guest that this is
	// readonly, it should *not* be used as a security measure, as the VM guest could remount it R/W.
	if readonly {
		agentMount.Options = append(agentMount.Options, "ro")
	}

	// Record the mount for the agent.
	*agentMounts = append(*agentMounts, agentMount)

	// Add 9p share config.
	driveDir9pOpts := qemuDriveDirOpts{
		dev: qemuDevOpts{
			busName:       busName,
			devBus:        qemuDev["bus"].(string),
			devAddr:       qemuDev["addr"].(string),
			multifunction: qemuDev["multifunction"].(bool),
		},
		devName:  driveConf.DevName,
		mountTag: mountTag,
		readonly: readonly,
		path:     driveConf.DevPath,
		protocol: "9p",
	}

	return qemuDriveDir(&driveDir9pOpts)
}

// addDriveDirConfigVirtiofs adds the qemu config required for adding a supplementary drive directory share using virtiofs.
func (d *qemu) addDriveDirConfigVirtiofs(qemuDev map[string]any, agentMounts *[]instancetype.VMAgentMount, driveConf deviceConfig.MountEntryItem) (monitorHook, error) {
	escapedDeviceName := linux.PathNameEncode(driveConf.DevName)
	deviceID := qemuDeviceIDPrefix + escapedDeviceName
	mountTag := d.mountTagName(driveConf.DevName, qemuMountTagMaxLength)

	if agentMounts != nil {
		agentMount := instancetype.VMAgentMount{
			Source: mountTag,
			Target: driveConf.TargetPath,
			FSType: "virtiofs",
		}

		// Indicate to agent to mount this readonly. Note: This is purely to indicate to VM guest that this is
		// readonly, it should *not* be used as a security measure, as the VM guest could remount it R/W.
		if slices.Contains(driveConf.Opts, "ro") {
			agentMount.Options = append(agentMount.Options, "ro")
		}

		// Record the mount for the agent.
		*agentMounts = append(*agentMounts, agentMount)
	}

	if qemuDev == nil {
		qemuDev = map[string]any{}
	}

	qemuDev["driver"] = "vhost-user-fs-pci"
	qemuDev["tag"] = mountTag
	qemuDev["chardev"] = mountTag
	qemuDev["id"] = deviceID

	monHook := func(m *qmp.Monitor) error {
		reverter := revert.New()
		defer reverter.Fail()

		// Detect virtiofsd path.
		virtiofsdSockPath := filepath.Join(d.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", linux.PathNameEncode(driveConf.DevName)))
		if !util.PathExists(virtiofsdSockPath) {
			return errors.New("Virtiofsd isn't running")
		}

		virtiofsSock, err := linux.DialUnix(virtiofsdSockPath)
		if err != nil {
			return fmt.Errorf("Error connecting to virtiofs socket %q: %w", virtiofsdSockPath, err)
		}

		defer logger.WarnOnError(virtiofsSock.Close, "Failed to close virtiofs socket") // Close file after device has been added.

		virtiofsFile, err := virtiofsSock.File()
		if err != nil {
			return fmt.Errorf("Error opening virtiofs socket %q: %w", virtiofsdSockPath, err)
		}

		err = m.SendFile(virtiofsdSockPath, virtiofsFile)
		if err != nil {
			return fmt.Errorf("Failed to send virtiofs file descriptor: %w", err)
		}

		reverter.Add(func() { _ = m.CloseFile(virtiofsdSockPath) })

		err = m.AddCharDevice(map[string]any{
			"id": mountTag,
			"backend": map[string]any{
				"type": "socket",
				"data": map[string]any{
					"addr": map[string]any{
						"type": "fd",
						"data": map[string]any{
							"str": virtiofsdSockPath,
						},
					},
					"server": false,
				},
			},
		})
		if err != nil {
			return fmt.Errorf("Failed to add the character device: %w", err)
		}

		reverter.Add(func() { _ = m.RemoveCharDevice(mountTag) })

		_, ok := qemuDev["bus"]
		if !ok {
			// Try to get a PCI address for hotplugging.
			pciDeviceName, err := d.getPCIHotplug()
			if err != nil {
				return err
			}

			d.logger.Debug("Using PCI bus device to hotplug virtiofs into", logger.Ctx{"device": driveConf.DevName, "port": pciDeviceName, "was": qemuDev["bus"]})
			qemuDev["bus"] = pciDeviceName
			qemuDev["addr"] = "00.0"
		}

		err = m.AddDevice(qemuDev)
		if err != nil {
			return fmt.Errorf("Failed to add the virtiofs device: %w", err)
		}

		reverter.Success()
		return nil
	}

	return monHook, nil
}

// addDriveConfig adds the qemu config required for adding a supplementary drive.
func (d *qemu) addDriveConfig(qemuDev map[string]any, bootIndexes map[string]int, driveConf deviceConfig.MountEntryItem) (monitorHook, error) {
	aioMode := "native" // Use native kernel async IO and O_DIRECT by default.
	cacheMode := "none" // Bypass host cache, use O_DIRECT semantics by default.
	media := "disk"
	isRBDImage := strings.HasPrefix(driveConf.DevPath, device.RBDFormatPrefix)

	// Use io_uring over native for added performance when supported by QEMU.
	info := DriverStatuses()[instancetype.VM].Info
	_, ioUring := info.Features["io_uring"]
	if slices.Contains(driveConf.Opts, device.DiskIOUring) && ioUring {
		aioMode = "io_uring"
	}

	var isBlockDev bool
	var srcDevPath string

	// Detect device caches and I/O modes.
	if isRBDImage {
		// For RBD, we want writeback to allow for the system-configured "rbd cache" to take effect if present.
		cacheMode = "writeback"
	} else {
		srcDevPath = driveConf.DevPath // This should not be used for passing to QEMU, only for probing.

		// Detect if existing file descriptor format is being supplied.
		if strings.HasPrefix(driveConf.DevPath, fmt.Sprintf("%s:", device.DiskFileDescriptorMountPrefix)) {
			// Expect devPath in format "fd:<fdNum>:<devPath>".
			devPathParts := strings.SplitN(driveConf.DevPath, ":", 3)
			if len(devPathParts) != 3 || !strings.HasPrefix(driveConf.DevPath, fmt.Sprintf("%s:", device.DiskFileDescriptorMountPrefix)) {
				return nil, fmt.Errorf("Unexpected devPath file descriptor format %q", driveConf.DevPath)
			}

			// Map the file descriptor to the file descriptor path it will be in the QEMU process.
			fd, err := strconv.Atoi(devPathParts[1])
			if err != nil {
				return nil, fmt.Errorf("Invalid file descriptor %q: %w", devPathParts[1], err)
			}

			// Extract original dev path for additional probing below.
			srcDevPath = devPathParts[2]
			if srcDevPath == "" {
				return nil, errors.New("Device source path is empty")
			}

			driveConf.DevPath = fmt.Sprintf("/proc/self/fd/%d", fd)
		} else if driveConf.TargetPath != "/" {
			// Only the root disk device is allowed to pass local devices to us without using an FD.
			return nil, fmt.Errorf("Invalid device path format %q", driveConf.DevPath)
		}

		srcDevPathInfo, err := os.Stat(srcDevPath)
		if err != nil {
			return nil, fmt.Errorf("Invalid source path %q: %w", srcDevPath, err)
		}

		isBlockDev = linux.IsBlockdev(srcDevPathInfo.Mode())

		// Handle I/O mode configuration.
		if !isBlockDev {
			// Disk dev path is a file, check what the backing filesystem is.
			fsType, err := linux.DetectFilesystem(srcDevPath)
			if err != nil {
				return nil, fmt.Errorf("Failed detecting filesystem type of %q: %w", srcDevPath, err)
			}

			// If backing FS is ZFS or BTRFS, avoid using direct I/O and use host page cache only.
			// We've seen ZFS lock up and BTRFS checksum issues when using direct I/O on image files.
			if fsType == "zfs" || fsType == "btrfs" {
				aioMode = "threads"
				cacheMode = "writeback" // Use host cache, with neither O_DSYNC nor O_DIRECT semantics.
			} else {
				// Use host cache, with neither O_DSYNC nor O_DIRECT semantics if filesystem
				// doesn't support Direct I/O.
				f, err := os.OpenFile(srcDevPath, unix.O_DIRECT|unix.O_RDONLY, 0)
				if err != nil {
					cacheMode = "writeback"
				} else {
					_ = f.Close() // Don't leak FD.
				}
			}

			if cacheMode == "writeback" && driveConf.FSType != "iso9660" {
				// Only warn about using writeback cache if the drive image is writable.
				d.logger.Warn("Using writeback cache I/O", logger.Ctx{"device": driveConf.DevName, "devPath": srcDevPath, "fsType": fsType})
			}
		} else if !slices.Contains(driveConf.Opts, device.DiskDirectIO) {
			// If drive config indicates we need to use unsafe I/O then use it.
			d.logger.Warn("Using unsafe cache I/O", logger.Ctx{"device": driveConf.DevName, "devPath": srcDevPath})
			aioMode = "threads"
			cacheMode = "unsafe" // Use host cache, but ignore all sync requests from guest.
		}
	}

	// Special case ISO images as cdroms.
	if driveConf.FSType == "iso9660" {
		media = "cdrom"
	}

	// Check if the user has overridden the bus.
	bus := "virtio-scsi"
	for _, opt := range driveConf.Opts {
		if !strings.HasPrefix(opt, "bus=") {
			continue
		}

		bus = strings.TrimPrefix(opt, "bus=")
		break
	}

	// Check if the user has overridden the cache mode.
	for _, opt := range driveConf.Opts {
		if !strings.HasPrefix(opt, "cache=") {
			continue
		}

		cacheMode = strings.TrimPrefix(opt, "cache=")
		break
	}

	// Check if the user has overridden the WWN.
	var wwn string
	for _, opt := range driveConf.Opts {
		if !strings.HasPrefix(opt, "wwn=") {
			continue
		}

		wwn = strings.TrimPrefix(opt, "wwn=")
		break
	}

	// QMP uses two separate values for the cache.
	directCache := true   // Bypass host cache, use O_DIRECT semantics by default.
	noFlushCache := false // Don't ignore any flush requests for the device.

	switch cacheMode {
	case "unsafe":
		aioMode = "threads"
		directCache = false
		noFlushCache = true
	case "writeback":
		aioMode = "threads"
		directCache = false
	}

	escapedDeviceName := linux.PathNameEncode(driveConf.DevName)

	blockDev := map[string]any{
		"aio": aioMode,
		"cache": map[string]any{
			"direct":   directCache,
			"no-flush": noFlushCache,
		},
		"discard":   "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file.
		"driver":    "file",
		"node-name": d.blockNodeName(escapedDeviceName),
		"read-only": false,
	}

	var rbdSecret string

	// If driver is "file", QEMU requires the file to be a regular file.
	// However, if the file is a character or block device, driver needs to be set to "host_device".
	if isBlockDev {
		blockDev["driver"] = "host_device"
	} else if isRBDImage {
		blockDev["driver"] = "rbd"

		poolName, imageName, opts, err := device.DiskParseRBDFormat(driveConf.DevPath)
		if err != nil {
			return nil, fmt.Errorf("Failed parsing rbd string: %w", err)
		}

		// Scan & pass through options.
		clusterName := storageDrivers.CephDefaultCluster
		userName := storageDrivers.CephDefaultUser

		blockDev["pool"] = poolName
		blockDev["image"] = imageName
		for key, val := range opts {
			// We use 'id' where qemu uses 'user'.
			switch key {
			case "id":
				blockDev["user"] = val
				userName = val
			case "cluster":
				clusterName = val
			default:
				blockDev[key] = val
			}
		}

		// Parse the secret (QEMU runs unprivileged and can't read the keyring directly).
		rbdSecret, err = storageDrivers.CephKeyring(clusterName, userName)
		if err != nil {
			return nil, err
		}

		// The aio option isn't available when using the rbd driver.
		delete(blockDev, "aio")
	}

	readonly := slices.Contains(driveConf.Opts, "ro") || media == "cdrom"

	if readonly {
		blockDev["read-only"] = true
	}

	if !isRBDImage {
		blockDev["locking"] = "off"
	}

	if qemuDev == nil {
		qemuDev = map[string]any{}
	}

	qemuDev["id"] = fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)

	nodeName, ok := blockDev["node-name"].(string)
	if !ok {
		return nil, fmt.Errorf("Invalid block device node-name for %q", driveConf.DevName)
	}

	qemuDev["drive"] = nodeName

	// Max serial length is 36 characters: prefix + 30 chars.
	// For nvme and virtio-blk, the maximum serial length is 20 characters: prefix + 14 chars.
	serialMaxLength := 30
	if slices.Contains([]string{"nvme", "virtio-blk"}, bus) {
		serialMaxLength = 14
	}

	qemuDev["serial"] = fmt.Sprintf("%s%s", qemuBlockDevIDPrefix, hashValue(escapedDeviceName, serialMaxLength))

	if wwn != "" {
		wwnID, err := strconv.ParseUint(strings.TrimPrefix(wwn, "0x"), 16, 64)
		if err != nil {
			return nil, err
		}

		qemuDev["wwn"] = wwnID
	}

	if bus == "virtio-scsi" {
		qemuDev["device_id"] = d.blockNodeName(escapedDeviceName)
		qemuDev["channel"] = 0
		qemuDev["lun"] = 1
		qemuDev["bus"] = "qemu_scsi.0"

		switch media {
		case "disk":
			qemuDev["driver"] = "scsi-hd"
		case "cdrom":
			qemuDev["driver"] = "scsi-cd"
		}
	} else if slices.Contains([]string{"nvme", "virtio-blk"}, bus) {
		if qemuDev["bus"] == nil {
			// Try to get a PCI address for hotplugging.
			pciDeviceName, err := d.getPCIHotplug()
			if err != nil {
				return nil, err
			}

			d.logger.Debug("Using PCI bus device to hotplug drive into", logger.Ctx{"device": driveConf.DevName, "port": pciDeviceName})
			qemuDev["bus"] = pciDeviceName
			qemuDev["addr"] = "00.0"
		}

		qemuDev["driver"] = bus
	} else if bus == "usb" {
		qemuDev["lun"] = 0

		switch media {
		case "disk":
			qemuDev["driver"] = "scsi-hd"
		case "cdrom":
			qemuDev["driver"] = "scsi-cd"
		}
	}

	if bootIndexes != nil {
		qemuDev["bootindex"] = bootIndexes[driveConf.DevName]
	}

	monHook := func(m *qmp.Monitor) error {
		reverter := revert.New()
		defer reverter.Fail()

		nodeName := d.blockNodeName(escapedDeviceName)

		if isRBDImage {
			secretID := fmt.Sprintf("pool_%s_%s", blockDev["pool"], blockDev["user"])

			err := m.AddSecret(secretID, rbdSecret)
			if err != nil {
				return err
			}

			blockDev["key-secret"] = secretID
		} else {
			permissions := unix.O_RDWR

			if readonly {
				permissions = unix.O_RDONLY
			}

			if directCache {
				permissions |= unix.O_DIRECT
			}

			f, err := os.OpenFile(driveConf.DevPath, permissions, 0)
			if err != nil {
				return fmt.Errorf("Failed opening file descriptor for disk device %q: %w", driveConf.DevName, err)
			}

			defer logger.WarnOnError(f.Close, "Failed to close file")

			info, err := m.SendFileWithFDSet(nodeName, f, readonly)
			if err != nil {
				return fmt.Errorf("Failed sending file descriptor of %q for disk device %q: %w", f.Name(), driveConf.DevName, err)
			}

			reverter.Add(func() {
				_ = m.RemoveFDFromFDSet(nodeName)
			})

			isQcow2, err := d.isQCOW2(srcDevPath)
			if err != nil {
				return fmt.Errorf("Failed checking disk format: %w", err)
			}

			if isQcow2 {
				srcDevPathInfo, err := os.Stat(srcDevPath)
				if err != nil {
					return fmt.Errorf("Invalid source path %q: %w", srcDevPath, err)
				}

				isBlockDev := linux.IsBlockdev(srcDevPathInfo.Mode())

				blockDevFile := map[string]any{
					"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
					"aio":      aioMode,
					"cache": map[string]any{
						"direct":   directCache,
						"no-flush": noFlushCache,
					},
				}

				if isBlockDev {
					blockDevFile["driver"] = "host_device"
				} else {
					blockDevFile["driver"] = "file"
				}

				blockDev = map[string]any{
					"driver":    "qcow2",
					"discard":   "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file.
					"node-name": d.blockNodeName(escapedDeviceName),
					"read-only": false,
					"file":      blockDevFile,
				}

				// If there are any children, load block information about them.
				if len(driveConf.BackingPath) > 0 {
					backingBlockDev, err := d.qcow2BlockDev(m, nodeName, aioMode, directCache, noFlushCache, permissions, readonly, driveConf.BackingPath, 0)
					if err != nil {
						return err
					}

					blockDev["backing"] = backingBlockDev
				}

				// If the qcow2 has data-file set, add that file to the FD set and
				// configure the qcow2 blockdev to use /dev/fdset/<x> path.
				if driveConf.DataFilePath != "" {
					dataDev, err := buildDataFileInfo(nodeName, m, driveConf, permissions, readonly, aioMode, directCache, noFlushCache)
					if err != nil {
						return err
					}

					// Tell QEMU to ignore qcow2's data-file path and use the one we provide
					blockDev["data-file"] = dataDev
				}
			} else {
				blockDev["filename"] = fmt.Sprintf("/dev/fdset/%d", info.ID)
			}
		}

		err := m.AddBlockDevice(blockDev, qemuDev, bus == "usb")
		if err != nil {
			return fmt.Errorf("Failed adding block device for disk device %q: %w", driveConf.DevName, err)
		}

		if driveConf.Limits != nil {
			err = m.SetBlockThrottle(qemuDev["id"].(string), int(driveConf.Limits.ReadBytes), int(driveConf.Limits.WriteBytes), int(driveConf.Limits.ReadIOps), int(driveConf.Limits.WriteIOps))
			if err != nil {
				return fmt.Errorf("Failed applying limits for disk device %q: %w", driveConf.DevName, err)
			}
		}

		reverter.Success()
		return nil
	}

	return monHook, nil
}

// addNetDevConfig adds the qemu config required for adding a network device.
// The qemuDev map is expected to be preconfigured with the settings for an existing port to use for the device.
func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndexes map[string]int, nicConfig []deviceConfig.RunConfigItem) (monitorHook, error) {
	reverter := revert.New()
	defer reverter.Fail()

	var devName, nicName, devHwaddr, pciSlotName, pciIOMMUGroup, vDPADevName, vhostVDPAPath, maxVQP string
	connected := true
	for _, nicItem := range nicConfig {
		switch nicItem.Key {
		case "devName":
			devName = nicItem.Value
		case "link":
			nicName = nicItem.Value
		case "hwaddr":
			devHwaddr = nicItem.Value
		case "pciSlotName":
			pciSlotName = nicItem.Value
		case "pciIOMMUGroup":
			pciIOMMUGroup = nicItem.Value
		case "vDPADevName":
			vDPADevName = nicItem.Value
		case "vhostVDPAPath":
			vhostVDPAPath = nicItem.Value
		case "maxVQP":
			maxVQP = nicItem.Value
		case "connected":
			connected = util.IsTrueOrEmpty(nicItem.Value)
		}
	}

	escapedDeviceName := linux.PathNameEncode(devName)
	qemuDev["id"] = fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName)

	if len(bootIndexes) > 0 {
		bootIndex, found := bootIndexes[devName]
		if found {
			qemuDev["bootindex"] = bootIndex
		}
	}

	var monHook func(m *qmp.Monitor) error

	// configureQueues modifies qemuDev with the queue configuration based on vCPUs.
	// Returns the number of queues to use with NIC.
	configureQueues := func(cpuCount int) int {
		// Number of queues is the same as number of vCPUs. Run with a minimum of two queues.
		queueCount := max(cpuCount, 2)

		// Number of vectors is number of vCPUs * 2 (RX/TX) + 2 (config/control MSI-X).
		vectors := 2*queueCount + 2
		if busName != "usb" {
			qemuDev["mq"] = true
			if slices.Contains([]string{"pcie", "pci"}, busName) {
				qemuDev["vectors"] = vectors
			}
		}

		return queueCount
	}

	// tapMonHook is a helper function used as the monitor hook for macvtap and tap interfaces to open
	// multi-queue file handles to both the interface device and the vhost-net device and pass them to QEMU.
	tapMonHook := func(deviceFile func() (*os.File, error)) func(m *qmp.Monitor) error {
		return func(m *qmp.Monitor) error {
			reverter := revert.New()
			defer reverter.Fail()

			cpus, err := m.QueryCPUs()
			if err != nil {
				return errors.New("Failed getting CPU list for NIC queues")
			}

			queueCount := configureQueues(len(cpus))

			// Enable vhost_net offloading if available.
			info := DriverStatuses()[instancetype.VM].Info
			_, vhostNetEnabled := info.Features["vhost_net"]

			// Open the device once for each queue and pass to QEMU.
			fds := make([]string, 0, queueCount)
			vhostfds := make([]string, 0, queueCount)

			// Collect the opened file handles so they can all be closed once the devices have been added.
			openFiles := make([]*os.File, 0, queueCount)
			defer func() {
				for _, f := range openFiles {
					_ = f.Close()
				}
			}()

			for i := range queueCount {
				devFile, err := deviceFile()
				if err != nil {
					return fmt.Errorf("Error opening netdev file for queue %d: %w", i, err)
				}

				openFiles = append(openFiles, devFile)

				devFDName := fmt.Sprintf("%s.%d", devFile.Name(), i)
				err = m.SendFile(devFDName, devFile)
				if err != nil {
					return fmt.Errorf("Failed to send %q file descriptor for queue %d: %w", devFDName, i, err)
				}

				reverter.Add(func() { _ = m.CloseFile(devFDName) })

				fds = append(fds, devFDName)

				if vhostNetEnabled {
					// Open a vhost-net file handle for each device file handle.
					vhostFile, err := os.OpenFile("/dev/vhost-net", os.O_RDWR, 0)
					if err != nil {
						return fmt.Errorf("Error opening /dev/vhost-net for queue %d: %w", i, err)
					}

					openFiles = append(openFiles, vhostFile)

					vhostFDName := fmt.Sprintf("%s.%d", vhostFile.Name(), i)
					err = m.SendFile(vhostFDName, vhostFile)
					if err != nil {
						return fmt.Errorf("Failed to send %q file descriptor for queue %d: %w", vhostFDName, i, err)
					}

					reverter.Add(func() { _ = m.CloseFile(vhostFDName) })

					vhostfds = append(vhostfds, vhostFDName)
				}
			}

			qemuNetDev := map[string]any{
				"id":    fmt.Sprintf("%s%s", qemuNetDevIDPrefix, escapedDeviceName),
				"type":  "tap",
				"vhost": vhostNetEnabled,
			}

			if slices.Contains([]string{"pcie", "pci"}, busName) {
				qemuDev["driver"] = "virtio-net-pci"
			} else if busName == "ccw" {
				qemuDev["driver"] = "virtio-net-ccw"
			} else if busName == "usb" {
				qemuDev["driver"] = "usb-net"
			}

			qemuNetDev["fds"] = strings.Join(fds, ":")

			if len(vhostfds) > 0 {
				qemuNetDev["vhostfds"] = strings.Join(vhostfds, ":")
			}

			netDevID, ok := qemuNetDev["id"].(string)
			if !ok {
				return errors.New("Invalid network device ID")
			}

			qemuDev["netdev"] = netDevID
			qemuDev["mac"] = devHwaddr

			err = m.AddNIC(qemuNetDev, qemuDev, connected)
			if err != nil {
				return fmt.Errorf("Failed setting up device %q: %w", devName, err)
			}

			reverter.Success()

			return nil
		}
	}

	// Detect MACVTAP interface types and figure out which tap device is being used.
	// This is so we can open a file handle to the tap device and pass it to the qemu process.
	if util.PathExists(fmt.Sprintf("/sys/class/net/%s/macvtap", nicName)) {
		content, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/ifindex", nicName))
		if err != nil {
			return nil, fmt.Errorf("Error getting tap device ifindex: %w", err)
		}

		ifindex, err := strconv.Atoi(strings.TrimSpace(string(content)))
		if err != nil {
			return nil, fmt.Errorf("Error parsing tap device ifindex: %w", err)
		}

		devFile := func() (*os.File, error) {
			return os.OpenFile(fmt.Sprintf("/dev/tap%d", ifindex), os.O_RDWR, 0)
		}

		monHook = tapMonHook(devFile)
	} else if util.PathExists(fmt.Sprintf("/sys/class/net/%s/tun_flags", nicName)) {
		// Detect TAP interface and use IOCTL TUNSETIFF on /dev/net/tun to get the file handle to it.
		// This is so we can open a file handle to the tap device and pass it to the qemu process.
		devFile := func() (*os.File, error) {
			reverter := revert.New()
			defer reverter.Fail()

			f, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0)
			if err != nil {
				return nil, err
			}

			reverter.Add(func() { _ = f.Close() })

			ifr, err := unix.NewIfreq(nicName)
			if err != nil {
				return nil, fmt.Errorf("Error creating new ifreq for %q: %w", nicName, err)
			}

			// These settings need to be compatible with what the device created the interface with
			// and what QEMU is expecting.
			ifr.SetUint16(unix.IFF_TAP | unix.IFF_NO_PI | unix.IFF_ONE_QUEUE | unix.IFF_MULTI_QUEUE | unix.IFF_VNET_HDR)

			// Sets the file handle to point to the requested NIC interface.
			err = unix.IoctlIfreq(int(f.Fd()), unix.TUNSETIFF, ifr)
			if err != nil {
				return nil, fmt.Errorf("Error getting TAP file handle for %q: %w", nicName, err)
			}

			reverter.Success()

			return f, nil
		}

		monHook = tapMonHook(devFile)
	} else if util.PathExists(vhostVDPAPath) {
		monHook = func(m *qmp.Monitor) error {
			reverter := revert.New()
			defer reverter.Fail()

			vdpaDevFile, err := os.OpenFile(vhostVDPAPath, os.O_RDWR, 0)
			if err != nil {
				return fmt.Errorf("Error opening vDPA device file %q: %w", vdpaDevFile.Name(), err)
			}

			defer logger.WarnOnError(vdpaDevFile.Close, "Failed to close vDPA device file") // Close file after device has been added.

			vDPADevFDName := fmt.Sprintf("%s.0", vdpaDevFile.Name())
			err = m.SendFile(vDPADevFDName, vdpaDevFile)
			if err != nil {
				return fmt.Errorf("Failed to send %q file descriptor: %w", vDPADevFDName, err)
			}

			reverter.Add(func() { _ = m.CloseFile(vDPADevFDName) })

			queues, err := strconv.Atoi(maxVQP)
			if err != nil {
				return fmt.Errorf("Failed to convert maxVQP (%q) to int: %w", maxVQP, err)
			}

			qemuNetDev := map[string]any{
				"id":      fmt.Sprintf("vhost-%s", vDPADevName),
				"type":    "vhost-vdpa",
				"vhostfd": vDPADevFDName,
				"queues":  queues,
			}

			if slices.Contains([]string{"pcie", "pci"}, busName) {
				qemuDev["driver"] = "virtio-net-pci"
			} else if busName == "ccw" {
				qemuDev["driver"] = "virtio-net-ccw"
			} else if busName == "usb" {
				qemuDev["driver"] = "usb-net"
			}

			netDevID, ok := qemuNetDev["id"].(string)
			if !ok {
				return errors.New("Invalid network device ID")
			}

			qemuDev["netdev"] = netDevID
			qemuDev["page-per-vq"] = true
			qemuDev["iommu_platform"] = true
			qemuDev["disable-legacy"] = true

			err = m.AddNIC(qemuNetDev, qemuDev, connected)
			if err != nil {
				return fmt.Errorf("Failed setting up device %q: %w", devName, err)
			}

			reverter.Success()
			return nil
		}
	} else if pciSlotName != "" {
		// Detect physical passthrough device.
		if slices.Contains([]string{"pcie", "pci"}, busName) {
			qemuDev["driver"] = "vfio-pci"
		} else if busName == "ccw" {
			qemuDev["driver"] = "vfio-ccw"
		}

		qemuDev["host"] = pciSlotName

		if d.state.OS.UnprivUser != "" {
			if pciIOMMUGroup == "" {
				return nil, errors.New("No PCI IOMMU group supplied")
			}

			vfioGroupFile := fmt.Sprintf("/dev/vfio/%s", pciIOMMUGroup)
			err := os.Chown(vfioGroupFile, int(d.state.OS.UnprivUID), -1)
			if err != nil {
				return nil, fmt.Errorf("Failed to chown vfio group device %q: %w", vfioGroupFile, err)
			}

			reverter.Add(func() { _ = os.Chown(vfioGroupFile, 0, -1) })
		}

		monHook = func(m *qmp.Monitor) error {
			err := m.AddNIC(nil, qemuDev, connected)
			if err != nil {
				return fmt.Errorf("Failed setting up device %q: %w", devName, err)
			}

			return nil
		}
	}

	if monHook == nil {
		return nil, errors.New("Unrecognised device type")
	}

	reverter.Success()

	return monHook, nil
}

// writeNICDevConfig writes the NIC config for the specified device into the NICConfigDir.
// This will be used by the agent to rename the NIC interfaces inside the VM guest.
func (d *qemu) writeNICDevConfig(mtuStr string, devName string, nicName string, devHwaddr string) error {
	// Parse MAC address to ensure it is in a canonical form (avoiding casing/presentation differences).
	hw, err := net.ParseMAC(devHwaddr)
	if err != nil {
		return fmt.Errorf("Failed parsing MAC %q: %w", devHwaddr, err)
	}

	nicConfig := deviceConfig.NICConfig{
		DeviceName: devName,
		NICName:    nicName,
		MACAddress: hw.String(),
	}

	if mtuStr != "" {
		mtuInt, err := strconv.ParseUint(mtuStr, 10, 32)
		if err != nil {
			return fmt.Errorf("Failed parsing MTU: %w", err)
		}

		nicConfig.MTU = uint32(mtuInt)
	}

	nicConfigBytes, err := json.Marshal(nicConfig)
	if err != nil {
		return fmt.Errorf("Failed encoding NIC config: %w", err)
	}

	nicFile := filepath.Join(d.Path(), "config", deviceConfig.NICConfigDir, fmt.Sprintf("%s.json", linux.PathNameEncode(nicConfig.DeviceName)))

	err = os.WriteFile(nicFile, nicConfigBytes, 0o700)
	if err != nil {
		return fmt.Errorf("Failed writing NIC config: %w", err)
	}

	return nil
}

// addPCIDevConfig adds the qemu config required for adding a raw PCI device.
func (d *qemu) addPCIDevConfig(conf *[]cfg.Section, bus *qemuBus, pciConfig []deviceConfig.RunConfigItem) error {
	var devName, pciSlotName string

	firmware := true
	for _, pciItem := range pciConfig {
		switch pciItem.Key {
		case "devName":
			devName = pciItem.Value
		case "pciSlotName":
			pciSlotName = pciItem.Value
		case "firmware":
			firmware = util.IsTrueOrEmpty(pciItem.Value)
		}
	}

	devBus, devAddr, multi := bus.allocate(fmt.Sprintf("incus_%s", devName))
	pciPhysicalOpts := qemuPCIPhysicalOpts{
		dev: qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		},
		devName:     devName,
		pciSlotName: pciSlotName,
		firmware:    firmware,
	}
	*conf = append(*conf, qemuPCIPhysical(&pciPhysicalOpts)...)

	return nil
}

// addGPUDevConfig adds the qemu config required for adding a GPU device.
func (d *qemu) addGPUDevConfig(conf *[]cfg.Section, bus *qemuBus, gpuConfig []deviceConfig.RunConfigItem) error {
	var devName, pciSlotName, vgpu, gpuType string
	for _, gpuItem := range gpuConfig {
		switch gpuItem.Key {
		case "devName":
			devName = gpuItem.Value
		case "pciSlotName":
			pciSlotName = gpuItem.Value
		case "vgpu":
			vgpu = gpuItem.Value
		case "gpuType":
			gpuType = gpuItem.Value
		}
	}

	// A native-context GPU is not passed through as a PCI device. It is realized by the
	// default virtio-gpu device (configured for GL/blob/native-context in generateQemuConfig)
	// together with QEMU's egl-headless display, so there is nothing to add here.
	if gpuType == "native-context" {
		return nil
	}

	vgaMode := func() bool {
		// No VGA mode on mdev.
		if vgpu != "" {
			return false
		}

		// No VGA mode on non-x86.
		if d.architecture != osarch.ARCH_64BIT_INTEL_X86 {
			return false
		}

		// Only enable if present on the card.
		if !util.PathExists(filepath.Join("/sys/bus/pci/devices", pciSlotName, "boot_vga")) {
			return false
		}

		// Skip SRIOV VFs as those are shared with the host card.
		if util.PathExists(filepath.Join("/sys/bus/pci/devices", pciSlotName, "physfn")) {
			return false
		}

		return true
	}()

	devBus, devAddr, multi := bus.allocate(fmt.Sprintf("incus_%s", devName))
	gpuDevPhysicalOpts := qemuGPUDevPhysicalOpts{
		dev: qemuDevOpts{
			busName:       bus.name,
			devBus:        devBus,
			devAddr:       devAddr,
			multifunction: multi,
		},
		devName:     devName,
		pciSlotName: pciSlotName,
		vga:         vgaMode,
		vgpu:        vgpu,
	}

	// Add main GPU device in VGA mode to qemu config.
	*conf = append(*conf, qemuGPUDevPhysical(&gpuDevPhysicalOpts)...)

	var iommuGroupPath string

	if vgpu != "" {
		iommuGroupPath = filepath.Join("/sys/bus/mdev/devices", vgpu, "iommu_group", "devices")
	} else {
		// Add any other related IOMMU VFs as generic PCI devices.
		iommuGroupPath = filepath.Join("/sys/bus/pci/devices", pciSlotName, "iommu_group", "devices")
	}

	if util.PathExists(iommuGroupPath) {
		// Extract parent slot name by removing any virtual function ID.
		parts := strings.SplitN(pciSlotName, ".", 2)
		prefix := parts[0]

		// Iterate the members of the IOMMU group and override any that match the parent slot name prefix.
		err := filepath.Walk(iommuGroupPath, func(path string, _ os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			iommuSlotName := filepath.Base(path) // Virtual function's address is dir name.

			// Match any VFs that are related to the GPU device (but not the GPU device itself).
			if strings.HasPrefix(iommuSlotName, prefix) && iommuSlotName != pciSlotName {
				// Add VF device without VGA mode to qemu config.
				devBus, devAddr, multi := bus.allocate(fmt.Sprintf("incus_%s", devName))
				gpuDevPhysicalOpts := qemuGPUDevPhysicalOpts{
					dev: qemuDevOpts{
						busName:       bus.name,
						devBus:        devBus,
						devAddr:       devAddr,
						multifunction: multi,
					},
					// Generate associated device name by combining main device name and VF ID.
					devName:     fmt.Sprintf("%s_%s", devName, devAddr),
					pciSlotName: iommuSlotName,
					vga:         false,
					vgpu:        "",
				}

				*conf = append(*conf, qemuGPUDevPhysical(&gpuDevPhysicalOpts)...)
			}

			return nil
		})
		if err != nil {
			return err
		}
	}

	return nil
}

func (d *qemu) addUSBDeviceConfig(usbDev deviceConfig.USBDeviceItem) (monitorHook, error) {
	qemuDev := map[string]any{
		"id":     fmt.Sprintf("%s%s", qemuDeviceIDPrefix, usbDev.DeviceName),
		"driver": "usb-host",
		"bus":    "qemu_usb.0",
	}

	monHook := func(m *qmp.Monitor) error {
		reverter := revert.New()
		defer reverter.Fail()

		f, err := os.OpenFile(usbDev.HostDevicePath, unix.O_RDWR, 0)
		if err != nil {
			return fmt.Errorf("Failed to open host device: %w", err)
		}

		defer logger.WarnOnError(f.Close, "Failed to close file")

		info, err := m.SendFileWithFDSet(qemuDev["id"].(string), f, false)
		if err != nil {
			return fmt.Errorf("Failed to send file descriptor: %w", err)
		}

		reverter.Add(func() {
			_ = m.RemoveFDFromFDSet(qemuDev["id"].(string))
		})

		qemuDev["hostdevice"] = fmt.Sprintf("/dev/fdset/%d", info.ID)

		err = m.AddDevice(qemuDev)
		if err != nil {
			return fmt.Errorf("Failed to add device: %w", err)
		}

		reverter.Success()

		return nil
	}

	return monHook, nil
}

func (d *qemu) addTPMDeviceConfig(conf *[]cfg.Section, tpmConfig []deviceConfig.RunConfigItem, fdFiles *[]*os.File) error {
	var devName, socketPath string

	for _, tpmItem := range tpmConfig {
		switch tpmItem.Key {
		case "path":
			socketPath = tpmItem.Value
		case "devName":
			devName = tpmItem.Value
		}
	}

	fd, err := unix.Open(socketPath, unix.O_PATH|unix.O_CLOEXEC, 0)
	if err != nil {
		return err
	}

	tpmFD := d.addFileDescriptor(fdFiles, os.NewFile(uintptr(fd), socketPath))

	tpmDriver := "tpm-tis-device"
	if d.architecture == osarch.ARCH_64BIT_INTEL_X86 {
		tpmDriver = "tpm-crb"
	}

	tpmOpts := qemuTPMOpts{
		devName: devName,
		path:    fmt.Sprintf("/proc/self/fd/%d", tpmFD),
		driver:  tpmDriver,
	}
	*conf = append(*conf, qemuTPM(&tpmOpts)...)

	return nil
}

func (d *qemu) addVmgenDeviceConfig(conf *[]cfg.Section, guid string) error {
	vmgenIDOpts := qemuVmgenIDOpts{
		guid: guid,
	}
	*conf = append(*conf, qemuVmgen(&vmgenIDOpts)...)

	return nil
}

// pidFilePath returns the path where the qemu process should write its PID.
func (d *qemu) pidFilePath() string {
	return filepath.Join(d.RunPath(), "qemu.pid")
}

// pid gets the PID of the running qemu process. Returns 0 if PID file or process not found, and -1 if err non-nil.
func (d *qemu) pid() (int, error) {
	pidStr, err := os.ReadFile(d.pidFilePath())
	if errors.Is(err, fs.ErrNotExist) {
		return 0, nil // PID file has gone.
	}

	if err != nil {
		return -1, err
	}

	pid, err := strconv.Atoi(strings.TrimSpace(string(pidStr)))
	if err != nil {
		return -1, err
	}

	cmdLineProcFilePath := fmt.Sprintf("/proc/%d/cmdline", pid)
	cmdLine, err := os.ReadFile(cmdLineProcFilePath)
	if err != nil {
		return 0, nil // Process has gone.
	}

	qemuSearchString := []byte("qemu-system")
	instUUID := []byte(d.localConfig["volatile.uuid"])
	if !bytes.Contains(cmdLine, qemuSearchString) || !bytes.Contains(cmdLine, instUUID) {
		return -1, errors.New("PID doesn't match the running process")
	}

	return pid, nil
}

// forceStop kills the QEMU prorcess if running.
func (d *qemu) forceStop() error {
	pid, _ := d.pid()
	if pid > 0 {
		err := d.killQemuProcess(pid)
		if err != nil {
			return fmt.Errorf("Failed to stop VM process %d: %w", pid, err)
		}
	}

	return nil
}

// Stop the VM.
func (d *qemu) Stop(stateful bool) error {
	d.logger.Debug("Stop started", logger.Ctx{"stateful": stateful})
	defer d.logger.Debug("Stop finished", logger.Ctx{"stateful": stateful})

	// Must be run prior to creating the operation lock.
	// Allow to proceed if statusCode is Error or Frozen as we may need to forcefully kill the QEMU process.
	// Also Stop() is called from migrateSendLive in some cases, and instance status will be Frozen then.
	statusCode := d.statusCode()
	if !d.isRunningStatusCode(statusCode) && statusCode != api.Error && statusCode != api.Frozen {
		return ErrInstanceIsStopped
	}

	// Check for stateful.
	if stateful {
		// Confirm the instance has stateful migration enabled.
		if !d.CanLiveMigrate() {
			return errors.New("Stateful stop requires migration.stateful to be set to true")
		}

		// Confirm the instance has sufficient reserved state space.
		err := d.checkStateStorage()
		if err != nil {
			return err
		}
	}

	// Attempt to save the console log from ring buffer before the instance is stopped. Must be run prior to creating the operation lock.
	_, _ = d.ConsoleLog()

	// Setup a new operation.
	// Allow inheriting of ongoing restart or restore operation (we are called from restartCommon and Restore).
	// Don't allow reuse when creating a new stop operation. This prevents other operations from interfering.
	// Allow reuse of a reusable ongoing stop operation as Shutdown() may be called first, which allows reuse
	// of its operations. This allow for Stop() to inherit from Shutdown() where instance is stuck.
	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionStop, []operationlock.Action{operationlock.ActionRestart, operationlock.ActionRestore, operationlock.ActionMigrate}, false, true)
	if err != nil {
		if errors.Is(err, operationlock.ErrNonReusuableSucceeded) {
			// An existing matching operation has now succeeded, return.
			return nil
		}

		return err
	}

	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		d.logger.Warn("Failed connecting to monitor, forcing stop", logger.Ctx{"err": err})

		// If we fail to connect, it's most likely because the VM is already off, but it could also be
		// because the qemu process is not responding, check if process still exists and kill it if needed.
		err = d.forceStop()
		if err != nil {
			op.Done(err)
			return err
		}

		// Wait for QEMU process to exit and perform device cleanup.
		// Treat as host-qmp-quit so autoRestart isn't triggered for a user-requested force stop.
		err = d.onStop("stop", qmp.EventVMShutdownReasonQuit)
		if err != nil {
			op.Done(err)
			return err
		}

		op.Done(nil)
		return nil
	}

	// Handle stateful stop.
	if stateful {
		// Dump the state.
		err = d.saveState(monitor)
		if err != nil {
			op.Done(err)
			return err
		}

		d.stateful = true
		err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			return tx.UpdateInstanceStatefulFlag(ctx, d.id, true)
		})
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Get the wait channel.
	chDisconnect, err := monitor.Wait()
	if err != nil {
		d.logger.Warn("Failed getting monitor disconnection channel, forcing stop", logger.Ctx{"err": err})
		err = d.forceStop()
		if err != nil {
			op.Done(err)
			return err
		}
	} else {
		// Request the VM stop immediately.
		err = monitor.Quit()
		if err != nil {
			d.logger.Warn("Failed sending monitor quit command, forcing stop", logger.Ctx{"err": err})
			err = d.forceStop()
			if err != nil {
				op.Done(err)
				return err
			}
		}

		// Wait for QEMU to exit (can take a while if pending I/O).
		// As this is a forceful stop of the VM we don't wait as long as during a clean shutdown because
		// the QEMU process may be not responding correctly.
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
		defer cancel()

		select {
		case <-chDisconnect:
		case <-ctx.Done():
			d.logger.Warn("Timed out waiting for monitor to disconnect, forcing stop")

			err = d.forceStop()
			if err != nil {
				op.Done(err)
				return err
			}
		}
	}

	// Wait for operation lock to be Done. This is normally completed by onStop which picks up the same
	// operation lock and then marks it as Done after the instance stops and the devices have been cleaned up.
	// However if the operation has failed for another reason we will collect the error here.
	err = op.Wait(context.Background())
	status := d.statusCode()
	if status != api.Stopped {
		errPrefix := fmt.Errorf("Failed stopping instance, status is %q", status)

		if err != nil {
			return fmt.Errorf("%s: %w", errPrefix.Error(), err)
		}

		return errPrefix
	}

	// Now handle errors from stop sequence and return to caller if wasn't completed cleanly.
	if err != nil {
		return err
	}

	return nil
}

// Unfreeze restores the instance to running.
func (d *qemu) Unfreeze() error {
	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	// Send the cont command.
	err = monitor.Start()
	if err != nil {
		return err
	}

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceResumed.Event(d, nil))
	return nil
}

// IsPrivileged does not apply to virtual machines. Always returns false.
func (d *qemu) IsPrivileged() bool {
	return false
}

// snapshot creates a snapshot of the instance.
func (d *qemu) snapshot(name string, expiry time.Time, stateful bool) error {
	var err error
	var monitor *qmp.Monitor

	// Deal with state.
	if stateful {
		// Confirm the instance has stateful migration enabled.
		if !d.CanLiveMigrate() {
			return errors.New("Stateful snapshot requires migration.stateful to be set to true")
		}

		// Confirm the instance has sufficient reserved state space.
		err = d.checkStateStorage()
		if err != nil {
			return err
		}

		// Quick checks.
		if !d.IsRunning() {
			return errors.New("Unable to create a stateful snapshot. The instance isn't running")
		}

		// Connect to the monitor.
		monitor, err = d.qmpConnect()
		if err != nil {
			return err
		}

		// Dump the state.
		err = d.saveState(monitor)
		if err != nil {
			return err
		}
	}

	// Create the snapshot.
	err = d.snapshotCommon(d, name, expiry, stateful)
	if err != nil {
		return err
	}

	// Resume the VM once the disk state has been saved.
	if stateful {
		// Remove the state from the main volume.
		err = os.Remove(d.StatePath())
		if err != nil {
			return err
		}

		err = monitor.Start()
		if err != nil {
			return err
		}
	}

	return nil
}

// Snapshot takes a new snapshot.
func (d *qemu) Snapshot(name string, expiry time.Time, stateful bool) error {
	return d.snapshot(name, expiry, stateful)
}

// Restore restores an instance snapshot.
func (d *qemu) Restore(source instance.Instance, stateful bool, diskOnly bool) error {
	op, err := operationlock.Create(d.Project().Name, d.Name(), d.op, operationlock.ActionRestore, false, false)
	if err != nil {
		return fmt.Errorf("Failed to create instance restore operation: %w", err)
	}

	defer op.Done(nil)

	var ctxMap logger.Ctx

	// Load the storage driver.
	pool, err := storagePools.LoadByInstance(d.state, d)
	if err != nil {
		op.Done(err)
		return err
	}

	err = pool.CanRestoreInstanceSnapshot(d, source)
	if err != nil {
		op.Done(err)
		return err
	}

	// Stop the instance.
	wasRunning := false
	if d.IsRunning() {
		wasRunning = true

		ephemeral := d.IsEphemeral()
		if ephemeral {
			// Unset ephemeral flag.
			args := db.InstanceArgs{
				Architecture: d.Architecture(),
				Config:       d.LocalConfig(),
				Description:  d.Description(),
				Devices:      d.LocalDevices(),
				Ephemeral:    false,
				Profiles:     d.Profiles(),
				Project:      d.Project().Name,
				Type:         d.Type(),
				Snapshot:     d.IsSnapshot(),
			}

			err := d.Update(args, false)
			if err != nil {
				op.Done(err)
				return err
			}

			// On function return, set the flag back on.
			defer func() {
				args.Ephemeral = ephemeral
				_ = d.Update(args, false)
			}()
		}

		// This will unmount the instance storage.
		err := d.Stop(false)
		if err != nil {
			op.Done(err)
			return err
		}

		// Refresh the operation as that one is now complete.
		op, err = operationlock.Create(d.Project().Name, d.Name(), d.op, operationlock.ActionRestore, false, false)
		if err != nil {
			return fmt.Errorf("Failed to create instance restore operation: %w", err)
		}

		defer op.Done(nil)
	}

	ctxMap = logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
		"source":    source.Name(),
	}

	d.logger.Info("Restoring instance", ctxMap)

	// Restore the rootfs.
	err = pool.RestoreInstanceSnapshot(d, source, nil)
	if err != nil {
		op.Done(err)
		return err
	}

	args := db.InstanceArgs{}
	if !diskOnly {
		// Restore the configuration.
		args = db.InstanceArgs{
			Architecture: source.Architecture(),
			Config:       source.LocalConfig(),
			Description:  source.Description(),
			Devices:      source.LocalDevices(),
			Ephemeral:    source.IsEphemeral(),
			Profiles:     source.Profiles(),
			Project:      source.Project().Name,
			Type:         source.Type(),
			Snapshot:     source.IsSnapshot(),
		}
	} else {
		args = db.InstanceArgs{
			Architecture: d.Architecture(),
			Config:       d.LocalConfig(),
			Description:  d.Description(),
			Devices:      d.LocalDevices(),
			Ephemeral:    d.IsEphemeral(),
			Profiles:     d.Profiles(),
			Project:      d.Project().Name,
			Type:         d.Type(),
			Snapshot:     d.IsSnapshot(),
		}

		args.Config["volatile.uuid.generation"] = source.LocalConfig()["volatile.uuid.generation"]
	}

	// Don't pass as user-requested as there's no way to fix a bad config.
	// This will call d.UpdateBackupFile() to ensure snapshot list is up to date.
	err = d.Update(args, false)
	if err != nil {
		op.Done(err)
		return err
	}

	d.stateful = stateful

	// Restart the instance.
	if wasRunning || stateful {
		d.logger.Debug("Starting instance after snapshot restore")
		err := d.Start(stateful)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceRestored.Event(d, map[string]any{"snapshot": source.Name()}))
	d.logger.Info("Restored instance", ctxMap)
	return nil
}

// Rename the instance. Accepts an argument to enable applying deferred TemplateTriggerRename.
func (d *qemu) Rename(newName string, applyTemplateTrigger bool) error {
	oldName := d.Name()
	ctxMap := logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
		"newname":   newName,
	}

	d.logger.Info("Renaming instance", ctxMap)

	// Quick checks.
	err := instance.ValidName(newName, d.IsSnapshot())
	if err != nil {
		return err
	}

	if d.IsRunning() {
		return errors.New("Renaming of running instance not allowed")
	}

	// Clean things up.
	d.cleanup()

	pool, err := storagePools.LoadByInstance(d.state, d)
	if err != nil {
		return fmt.Errorf("Failed loading instance storage pool: %w", err)
	}

	if d.IsSnapshot() {
		_, newSnapName, _ := api.GetParentAndSnapshotName(newName)
		err = pool.RenameInstanceSnapshot(d, newSnapName, nil)
		if err != nil {
			return fmt.Errorf("Rename instance snapshot: %w", err)
		}
	} else {
		err = pool.RenameInstance(d, newName, nil)
		if err != nil {
			return fmt.Errorf("Rename instance: %w", err)
		}

		if applyTemplateTrigger {
			err = d.DeferTemplateApply(instance.TemplateTriggerRename)
			if err != nil {
				return err
			}
		}
	}

	if !d.IsSnapshot() {
		var results []string

		err := d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			var err error

			// Rename all the instance snapshot database entries.
			results, err = tx.GetInstanceSnapshotsNames(ctx, d.project.Name, oldName)
			if err != nil {
				d.logger.Error("Failed to get instance snapshots", ctxMap)
				return fmt.Errorf("Failed to get instance snapshots: Failed getting instance snapshot names: %w", err)
			}

			for _, sname := range results {
				// Rename the snapshot.
				oldSnapName := strings.SplitN(sname, internalInstance.SnapshotDelimiter, 2)[1]
				baseSnapName := filepath.Base(sname)

				err := dbCluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName)
				if err != nil {
					d.logger.Error("Failed renaming snapshot", ctxMap)
					return err
				}
			}

			return nil
		})
		if err != nil {
			return err
		}
	}

	// Rename the instance database entry.
	err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		if d.IsSnapshot() {
			oldParts := strings.SplitN(oldName, internalInstance.SnapshotDelimiter, 2)
			newParts := strings.SplitN(newName, internalInstance.SnapshotDelimiter, 2)
			return dbCluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldParts[0], oldParts[1], newParts[1])
		}

		return dbCluster.RenameInstance(ctx, tx.Tx(), d.project.Name, oldName, newName)
	})
	if err != nil {
		d.logger.Error("Failed renaming instance", ctxMap)
		return err
	}

	// Rename the logging path.
	newFullName := project.Instance(d.Project().Name, d.Name())
	_ = os.RemoveAll(internalUtil.LogPath(newFullName))
	if util.PathExists(d.LogPath()) {
		err := os.Rename(d.LogPath(), internalUtil.LogPath(newFullName))
		if err != nil {
			d.logger.Error("Failed renaming instance", ctxMap)
			return err
		}
	}

	// Rename the runtime path.
	newFullName = project.Instance(d.Project().Name, d.Name())
	_ = os.RemoveAll(internalUtil.RunPath(newFullName))
	if util.PathExists(d.RunPath()) {
		err := os.Rename(d.RunPath(), internalUtil.RunPath(newFullName))
		if err != nil {
			d.logger.Error("Failed renaming instance", ctxMap)
			return err
		}
	}

	reverter := revert.New()
	defer reverter.Fail()

	// Set the new name in the struct.
	d.name = newName
	reverter.Add(func() { d.name = oldName })

	// Rename the backups.
	backups, err := d.Backups()
	if err != nil {
		return err
	}

	for _, backup := range backups {
		b := backup
		oldName := b.Name()
		backupName := strings.Split(oldName, "/")[1]
		newName := fmt.Sprintf("%s/%s", newName, backupName)

		err = b.Rename(newName)
		if err != nil {
			return err
		}

		reverter.Add(func() { _ = b.Rename(oldName) })
	}

	// Update lease files.
	err = network.UpdateDNSMasqStatic(d.state, "")
	if err != nil {
		return err
	}

	// Reset cloud-init instance-id (causes a re-run on name changes).
	if !d.IsSnapshot() {
		err = d.resetInstanceID()
		if err != nil {
			return err
		}
	}

	// Update the backup file.
	err = d.UpdateBackupFile()
	if err != nil {
		return err
	}

	d.logger.Info("Renamed instance", ctxMap)

	if d.isSnapshot {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceSnapshotRenamed.Event(d, map[string]any{"old_name": oldName}))
	} else {
		err = d.state.Authorizer.RenameInstance(d.state.ShutdownCtx, d.project.Name, oldName, newName)
		if err != nil {
			logger.Error("Failed to rename instance in authorizer", logger.Ctx{"old_name": oldName, "new_name": newName, "project": d.project.Name, "error": err})
		}

		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceRenamed.Event(d, map[string]any{"old_name": oldName}))
	}

	reverter.Success()

	return nil
}

// Indirection to detachDisk.
func qemuDetachDisk(s *state.State, id int) func(string) error {
	return func(name string) error {
		inst, err := instance.LoadByID(s, id)
		if err != nil {
			return err
		}

		qemuInst, ok := inst.(*qemu)
		if !ok {
			return fmt.Errorf("Couldn't assert QEMU object from interface")
		}

		return qemuInst.detachDisk(name)
	}
}

// Detach a disk from the instance.
func (d *qemu) detachDisk(name string) error {
	diskName := linux.PathNameDecode(strings.TrimPrefix(name, qemuDeviceIDPrefix))

	// Load and detach the disk.
	config, ok := d.expandedDevices[diskName]
	if !ok {
		return fmt.Errorf("Couldn't find device %s", diskName)
	}

	dev, err := d.deviceLoad(d, diskName, config, false)
	if err != nil {
		return err
	}

	err = d.deviceStop(dev, true, "")
	if err != nil {
		return err
	}

	// Check if it's a special device or an inherited device for which we can't save state.
	_, ok = d.localDevices[diskName]
	if !ok || slices.Contains([]string{"agent:config", "cloud-init:config"}, config["source"]) {
		// Record that the instance devices got modified and a full reset will be needed to get a consistent state.
		err = d.VolatileSet(map[string]string{
			"volatile.vm.needs_reset": "true",
		})
		if err != nil {
			return err
		}

		return nil
	}

	// Record the device as detached.
	d.localDevices[diskName]["attached"] = "false"

	return d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		devices, err := dbCluster.APIToDevices(d.localDevices.CloneNative())
		if err != nil {
			return err
		}

		return dbCluster.UpdateInstanceDevices(ctx, tx.Tx(), int64(d.id), devices)
	})
}

// Update the instance config.
func (d *qemu) Update(args db.InstanceArgs, userRequested bool) error {
	// Setup a new operation.
	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionUpdate, []operationlock.Action{operationlock.ActionRestart, operationlock.ActionRestore}, false, false)
	if err != nil {
		return fmt.Errorf("Failed to create instance update operation: %w", err)
	}

	defer op.Done(nil)

	// Setup the reverter.
	reverter := revert.New()
	defer reverter.Fail()

	// Set sane defaults for unset keys.
	if args.Project == "" {
		args.Project = api.ProjectDefaultName
	}

	if args.Architecture == 0 {
		args.Architecture = d.architecture
	}

	if args.Config == nil {
		args.Config = map[string]string{}
	}

	if args.Devices == nil {
		args.Devices = deviceConfig.Devices{}
	}

	if args.Profiles == nil {
		args.Profiles = []api.Profile{}
	}

	if userRequested {
		// Validate the new config.
		err := instance.ValidConfig(d.state.OS, args.Config, false, d.dbType)
		if err != nil {
			return fmt.Errorf("Invalid config: %w", err)
		}

		// Validate the new devices without using expanded devices validation (expensive checks disabled).
		err = instance.ValidDevices(d.state, d.project, d.Type(), args.Devices, nil)
		if err != nil {
			return fmt.Errorf("Invalid devices: %w", err)
		}
	}

	var profiles []string

	err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		// Validate the new profiles.
		profiles, err = tx.GetProfileNames(ctx, args.Project)

		return err
	})
	if err != nil {
		return fmt.Errorf("Failed to get profiles: %w", err)
	}

	checkedProfiles := []string{}
	for _, profile := range args.Profiles {
		if !slices.Contains(profiles, profile.Name) {
			return fmt.Errorf("Requested profile '%s' doesn't exist", profile.Name)
		}

		if slices.Contains(checkedProfiles, profile.Name) {
			return errors.New("Duplicate profile found in request")
		}

		checkedProfiles = append(checkedProfiles, profile.Name)
	}

	// Validate the new architecture.
	if args.Architecture != 0 {
		_, err = osarch.ArchitectureName(args.Architecture)
		if err != nil {
			return fmt.Errorf("Invalid architecture ID: %s", err)
		}
	}

	// Get a copy of the old configuration.
	oldDescription := d.Description()
	oldArchitecture := 0
	err = util.DeepCopy(&d.architecture, &oldArchitecture)
	if err != nil {
		return err
	}

	oldEphemeral := false
	err = util.DeepCopy(&d.ephemeral, &oldEphemeral)
	if err != nil {
		return err
	}

	oldExpandedDevices := deviceConfig.Devices{}
	err = util.DeepCopy(&d.expandedDevices, &oldExpandedDevices)
	if err != nil {
		return err
	}

	oldExpandedConfig := map[string]string{}
	err = util.DeepCopy(&d.expandedConfig, &oldExpandedConfig)
	if err != nil {
		return err
	}

	oldLocalDevices := deviceConfig.Devices{}
	err = util.DeepCopy(&d.localDevices, &oldLocalDevices)
	if err != nil {
		return err
	}

	oldLocalConfig := map[string]string{}
	err = util.DeepCopy(&d.localConfig, &oldLocalConfig)
	if err != nil {
		return err
	}

	oldProfiles := []api.Profile{}
	err = util.DeepCopy(&d.profiles, &oldProfiles)
	if err != nil {
		return err
	}

	oldExpiryDate := d.expiryDate

	// Revert local changes if update fails.
	reverter.Add(func() {
		d.description = oldDescription
		d.architecture = oldArchitecture
		d.ephemeral = oldEphemeral
		d.expandedConfig = oldExpandedConfig
		d.expandedDevices = oldExpandedDevices
		d.localConfig = oldLocalConfig
		d.localDevices = oldLocalDevices
		d.profiles = oldProfiles
		d.expiryDate = oldExpiryDate
	})

	// Apply the various changes to local vars.
	d.description = args.Description
	d.architecture = args.Architecture
	d.ephemeral = args.Ephemeral
	d.localConfig = args.Config
	d.localDevices = args.Devices
	d.profiles = args.Profiles
	d.expiryDate = args.ExpiryDate

	// Expand the config.
	err = d.expandConfig()
	if err != nil {
		return err
	}

	// Diff the configurations.
	changedConfig := []string{}
	for key := range oldExpandedConfig {
		if oldExpandedConfig[key] != d.expandedConfig[key] {
			if !slices.Contains(changedConfig, key) {
				changedConfig = append(changedConfig, key)
			}
		}
	}

	for key := range d.expandedConfig {
		if oldExpandedConfig[key] != d.expandedConfig[key] {
			if !slices.Contains(changedConfig, key) {
				changedConfig = append(changedConfig, key)
			}
		}
	}

	// Diff the devices.
	removeDevices, addDevices, updateDevices, allUpdatedKeys := oldExpandedDevices.Update(d.expandedDevices, func(oldDevice deviceConfig.Device, newDevice deviceConfig.Device) []string {
		// This function needs to return a list of fields that are excluded from differences
		// between oldDevice and newDevice. The result of this is that as long as the
		// devices are otherwise identical except for the fields returned here, then the
		// device is considered to be being "updated" rather than "added & removed".
		oldDevType, err := device.LoadByType(d.state, d.Project().Name, oldDevice)
		if err != nil {
			return []string{} // Couldn't create Device, so this cannot be an update.
		}

		newDevType, err := device.LoadByType(d.state, d.Project().Name, newDevice)
		if err != nil {
			return []string{} // Couldn't create Device, so this cannot be an update.
		}

		// Detached devices need to be fully recreated on update so that the update logic doesn't
		// try to access non-existing QEMU devices.
		if !util.IsTrueOrEmpty(oldDevice["attached"]) {
			return []string{}
		}

		return newDevType.UpdatableFields(oldDevType)
	})

	// Prevent adding or updating device initial configuration.
	if util.StringPrefixInSlice("initial.", allUpdatedKeys) {
		for devName, newDev := range addDevices {
			for k, newVal := range newDev {
				if !strings.HasPrefix(k, "initial.") {
					continue
				}

				if newDev["pool"] != "" && newDev["path"] != "/" && strings.Contains(newDev["source"], "/") {
					continue
				}

				oldDev, ok := removeDevices[devName]
				if !ok {
					return errors.New("New device with initial configuration cannot be added once the instance is created")
				}

				oldVal, ok := oldDev[k]
				if !ok {
					return errors.New("Device initial configuration cannot be added once the instance is created")
				}

				// If newVal is an empty string it means the initial configuration
				// has been removed.
				if newVal != "" && newVal != oldVal {
					return errors.New("Device initial configuration cannot be modified once the instance is created")
				}
			}
		}
	}

	if userRequested {
		// Do some validation of the config diff (allows mixed instance types for profiles).
		err = instance.ValidConfig(d.state.OS, d.expandedConfig, true, instancetype.Any)
		if err != nil {
			return fmt.Errorf("Invalid expanded config: %w", err)
		}

		// Do full expanded validation of the devices diff.
		err = instance.ValidDevices(d.state, d.project, d.Type(), d.localDevices, d.expandedDevices)
		if err != nil {
			return fmt.Errorf("Invalid expanded devices: %w", err)
		}

		// Validate root device
		_, oldRootDev, oldErr := internalInstance.GetRootDiskDevice(oldExpandedDevices.CloneNative())
		_, newRootDev, newErr := internalInstance.GetRootDiskDevice(d.expandedDevices.CloneNative())
		if oldErr == nil && newErr == nil && oldRootDev["pool"] != newRootDev["pool"] {
			return fmt.Errorf("Cannot update root disk device pool name to %q", newRootDev["pool"])
		}

		// Ensure the instance has a root disk.
		if newErr != nil {
			return fmt.Errorf("Invalid root disk device: %w", newErr)
		}
	}

	// If apparmor changed, re-validate the apparmor profile (even if not running).
	if slices.Contains(changedConfig, "raw.apparmor") {
		qemuPath, _, err := d.qemuArchConfig(d.architecture)
		if err != nil {
			return err
		}

		err = apparmor.InstanceValidate(d.state.OS, d, []string{qemuPath})
		if err != nil {
			return fmt.Errorf("Parse AppArmor profile: %w", err)
		}
	}

	isRunning := d.IsRunning()

	// Use the device interface to apply update changes.
	err = d.devicesUpdate(d, removeDevices, addDevices, updateDevices, oldExpandedDevices, isRunning, userRequested)
	if err != nil {
		return err
	}

	if isRunning {
		// Only certain keys can be changed on a running VM.
		liveUpdateKeys := []string{
			"cluster.evacuate",
			"limits.memory",
			"security.agent.metrics",
			"security.csm",
			"security.protection.delete",
			"security.guestapi",
			"security.secureboot",
		}

		liveUpdateKeyPrefixes := []string{
			"boot.",
			"cloud-init.",
			"environment.",
			"image.",
			"snapshots.",
			"user.",
			"volatile.",
		}

		isLiveUpdatable := func(key string) bool {
			// Skip container config keys for VMs
			_, ok := internalInstance.InstanceConfigKeysContainer[key]
			if ok {
				return true
			}

			if key == "limits.cpu" {
				return d.architectureSupportsCPUHotplug()
			}

			if slices.Contains(liveUpdateKeys, key) {
				return true
			}

			if util.StringHasPrefix(key, liveUpdateKeyPrefixes...) {
				return true
			}

			if key == "limits.memory.oom_priority" {
				return true
			}

			return false
		}

		// Check only keys that support live update have changed.
		for _, key := range changedConfig {
			if !isLiveUpdatable(key) {
				return fmt.Errorf("Key %q cannot be updated when VM is running", key)
			}
		}

		// Mark the VM as needing a full reset on next reboot.
		err = d.VolatileSet(map[string]string{
			"volatile.vm.needs_reset": "true",
		})
		if err != nil {
			return err
		}

		// Apply live update for each key.
		for _, key := range changedConfig {
			value := d.expandedConfig[key]

			switch key {
			case "limits.cpu":
				oldValue := oldExpandedConfig["limits.cpu"]

				if oldValue != "" {
					_, err := strconv.Atoi(oldValue)
					if err != nil {
						return fmt.Errorf("Cannot update key %q when using CPU pinning or an explicit CPU topology and the VM is running", key)
					}
				}

				// If the key is being unset, set it to default value.
				if value == "" {
					value = "1"
				}

				limit, err := strconv.Atoi(value)
				if err != nil {
					return errors.New("Cannot change to CPU pinning or an explicit CPU topology when the VM is running")
				}

				// Hotplug the CPUs.
				err = d.setCPUs(nil, limit)
				if err != nil {
					return fmt.Errorf("Failed updating cpu limit: %w", err)
				}

			case "limits.memory":
				err = d.updateMemoryLimit(value)
				if err != nil {
					if err != nil {
						return fmt.Errorf("Failed updating memory limit: %w", err)
					}
				}
			case "security.csm":
				// Defer rebuilding nvram until next start.
				d.localConfig["volatile.apply_nvram"] = "true"
			case "security.secureboot":
				// Defer rebuilding nvram until next start.
				d.localConfig["volatile.apply_nvram"] = "true"
			case "security.guestapi":
				err = d.advertiseVsockAddress()
				if err != nil {
					return err
				}

			case "limits.memory.oom_priority":
				// Configure the OOM priority.
				err = d.setOOMPriority(d.InitPID())
				if err != nil {
					d.logger.Warn("Failed to set OOM priority", logger.Ctx{
						"err":      err,
						"instance": d.Name(),
						"project":  d.Project().Name,
					})
				}
			}
		}
	}

	// Clear the "volatile.cpu.nodes" if needed.
	d.ClearLimitsCPUNodes(changedConfig)

	if d.architectureSupportsUEFI(d.architecture) && (slices.Contains(changedConfig, "security.secureboot") || slices.Contains(changedConfig, "security.csm")) {
		// setupNvram() requires instance's config volume to be mounted.
		// The easiest way to detect that is to check if instance is running.
		// TODO: extend storage API to be able to check if volume is already mounted?
		if !isRunning {
			// Mount the instance's config volume.
			_, err := d.mount()
			if err != nil {
				return err
			}

			defer logger.WarnOnError(d.unmount, "Failed to unmount instance")
		}

		// Re-generate the NVRAM.
		err = d.setupNvram()
		if err != nil {
			return err
		}
	}

	// Re-generate the instance-id if needed.
	if !d.IsSnapshot() && d.needsNewInstanceID(changedConfig, oldExpandedDevices) {
		err = d.resetInstanceID()
		if err != nil {
			return err
		}
	}

	// Finally, apply the changes to the database.
	err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		// Snapshots should update only their descriptions and expiry date.
		if d.IsSnapshot() {
			return tx.UpdateInstanceSnapshot(d.id, d.description, d.expiryDate)
		}

		object, err := dbCluster.GetInstance(ctx, tx.Tx(), d.project.Name, d.name)
		if err != nil {
			return err
		}

		object.Description = d.description
		object.Architecture = d.architecture
		object.Ephemeral = d.ephemeral
		object.ExpiryDate = sql.NullTime{Time: d.expiryDate, Valid: true}

		err = dbCluster.UpdateInstance(ctx, tx.Tx(), d.project.Name, d.name, *object)
		if err != nil {
			return err
		}

		err = dbCluster.UpdateInstanceConfig(ctx, tx.Tx(), int64(object.ID), d.localConfig)
		if err != nil {
			return err
		}

		devices, err := dbCluster.APIToDevices(d.localDevices.CloneNative())
		if err != nil {
			return err
		}

		err = dbCluster.UpdateInstanceDevices(ctx, tx.Tx(), int64(object.ID), devices)
		if err != nil {
			return err
		}

		profileNames := make([]string, 0, len(d.profiles))
		for _, profile := range d.profiles {
			profileNames = append(profileNames, profile.Name)
		}

		return dbCluster.UpdateInstanceProfiles(ctx, tx.Tx(), object.ID, object.Project, profileNames)
	})
	if err != nil {
		return fmt.Errorf("Failed to update database: %w", err)
	}

	err = d.UpdateBackupFile()
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		return fmt.Errorf("Failed to write backup file: %w", err)
	}

	// Changes have been applied and recorded, do not revert if an error occurs from here.
	reverter.Success()

	if isRunning {
		// Send devIncus notifications only for user.* key changes
		for _, key := range changedConfig {
			if !strings.HasPrefix(key, "user.") {
				continue
			}

			msg := map[string]any{
				"key":       key,
				"old_value": oldExpandedConfig[key],
				"value":     d.expandedConfig[key],
			}

			err = d.devIncusEventSend("config", msg)
			if err != nil {
				return err
			}
		}

		// Device changes
		for k, m := range removeDevices {
			msg := map[string]any{
				"action": "removed",
				"name":   k,
				"config": m,
			}

			err = d.devIncusEventSend("device", msg)
			if err != nil {
				return err
			}
		}

		for k, m := range updateDevices {
			msg := map[string]any{
				"action": "updated",
				"name":   k,
				"config": m,
			}

			err = d.devIncusEventSend("device", msg)
			if err != nil {
				return err
			}
		}

		for k, m := range addDevices {
			msg := map[string]any{
				"action": "added",
				"name":   k,
				"config": m,
			}

			err = d.devIncusEventSend("device", msg)
			if err != nil {
				return err
			}
		}
	}

	if userRequested {
		if d.isSnapshot {
			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceSnapshotUpdated.Event(d, nil))
		} else {
			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceUpdated.Event(d, nil))
		}
	}

	return nil
}

// updateMemoryLimit live updates the VM's memory limit by reszing the balloon device.
func (d *qemu) updateMemoryLimit(newLimit string) error {
	if newLimit == "" {
		return nil
	}

	if util.IsTrue(d.expandedConfig["limits.memory.hugepages"]) {
		return errors.New("Cannot live update memory limit when using huge pages")
	}

	// Check new size string is valid and convert to bytes.
	newSizeBytes, err := ParseMemoryStr(newLimit)
	if err != nil {
		return fmt.Errorf("Invalid memory size: %w", err)
	}

	newSizeMB := newSizeBytes / 1024 / 1024

	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err // The VM isn't running as no monitor socket available.
	}

	baseSizeBytes, err := monitor.GetMemorySizeBytes()
	if err != nil {
		return err
	}

	baseSizeMB := baseSizeBytes / 1024 / 1024

	curSizeBytes, err := monitor.GetMemoryBalloonSizeBytes()
	if err != nil {
		return err
	}

	curSizeMB := curSizeBytes / 1024 / 1024

	if curSizeMB == newSizeMB {
		return nil
	} else if baseSizeMB < newSizeMB {
		if util.IsFalse(d.expandedConfig["limits.memory.hotplug"]) || d.GuestOS() == osinfo.FreeBSD {
			return fmt.Errorf("Memory hotplug feature is disabled")
		}

		// Grab the current memory configuration.
		baseMem, maxMem, _, err := monitor.MemoryConfiguration()
		if err != nil {
			return err
		}

		// Make sure that we're not exceeding the VM's configured maximum.
		if newSizeBytes > maxMem {
			return fmt.Errorf("Requested memory total of %s exceeds instance current maximum of %s, restart required", units.GetByteSizeStringIEC(newSizeBytes, 2), units.GetByteSizeStringIEC(maxMem, 2))
		}

		// Add the memory.
		err = d.hotplugMemory(monitor, newSizeBytes-curSizeBytes)
		if err != nil {
			return err
		}

		// If migratable, update state information following hotplug.
		if d.CanLiveMigrate() {
			// Prepare an updated memory topology struct.
			memTopology := qemuMemoryTopology{
				Base:  baseMem,
				Max:   maxMem,
				Extra: []int64{},
			}

			memDevs, err := monitor.GetMemdev()
			if err != nil {
				return err
			}

			memSlots := map[string]int64{}
			memSlotsKeys := []string{}
			for _, memDev := range memDevs {
				// Skip base memory objects.
				if memDev.ID == "mem0" || memDev.ID == qemuDefaultRAMObject(d.architecture) {
					continue
				}

				memSlots[memDev.ID] = int64(memDev.Size)
				memSlotsKeys = append(memSlotsKeys, memDev.ID)
			}

			// The list out of QEMU is in random order...
			sort.Strings(memSlotsKeys)
			for _, k := range memSlotsKeys {
				memTopology.Extra = append(memTopology.Extra, memSlots[k])
			}

			// Update the boot state record.
			bs, err := d.getBootState()
			if err != nil {
				return err
			}

			bs.MemoryTopology = &memTopology

			err = d.saveBootState(*bs)
			if err != nil {
				return err
			}
		}
	}

	// Set effective memory size.
	err = monitor.SetMemoryBalloonSizeBytes(newSizeBytes)
	if err != nil {
		return err
	}

	// Changing the memory balloon can take time, so poll the effective size to check it has shrunk within 1%
	// of the target size, which we then take as success (it may still continue to shrink closer to target).
	for range 10 {
		curSizeBytes, err = monitor.GetMemoryBalloonSizeBytes()
		if err != nil {
			return err
		}

		curSizeMB = curSizeBytes / 1024 / 1024

		var diff int64
		if curSizeMB < newSizeMB {
			diff = newSizeMB - curSizeMB
		} else {
			diff = curSizeMB - newSizeMB
		}

		if diff <= (newSizeMB / 100) {
			return nil // We reached to within 1% of our target size.
		}

		time.Sleep(500 * time.Millisecond)
	}

	return fmt.Errorf("Failed setting memory to %dMiB (currently %dMiB) as it was taking too long", newSizeMB, curSizeMB)
}

// hotplugMemory attaches a memory device to a running VM,
// respecting NUMA node placement and hugepages.
func (d *qemu) hotplugMemory(monitor *qmp.Monitor, sizeBytes int64) error {
	// Get CPU information.
	cpuInfo, err := d.cpuTopology()
	if err != nil {
		return err
	}

	// Fetch memory configuration
	cpuOpts, err := d.getCPUOpts(cpuInfo, sizeBytes)
	if err != nil {
		return err
	}

	cpuFixedTopology := cpuInfo.VCPUs != nil || cpuInfo.Explicit

	// Get CPUs and memory configuration
	conf := qemuCPU(cpuOpts, cpuFixedTopology)

	memoryObjects := map[int]cfg.Section{}
	for _, section := range conf {
		// Name is in the form 'object "mem0"', so the last quote needs to be removed.
		// This allows proper parsing of the memory object index.
		sectionName := section.Name[:len(section.Name)-1]
		index, err := extractTrailingNumber(sectionName, "object \"mem")
		if err != nil {
			continue
		}

		memoryObjects[index] = section
	}

	// Find first available memory object index.
	nextMemIndex, err := findNextMemoryIndex(monitor)
	if err != nil {
		return err
	}

	// Find first available pc-dimm device index.
	nextDimmIndex, err := findNextDimmIndex(monitor)
	if err != nil {
		return err
	}

	for index, memory := range memoryObjects {
		memIndex := nextMemIndex + index
		dimmIndex := nextDimmIndex + index

		memObj := memoryConfigSectionToMap(&memory)
		memObj["id"] = fmt.Sprintf("mem%d", memIndex)

		err = monitor.AddObject(memObj)
		if err != nil {
			return err
		}

		memDev := map[string]any{
			"driver": "pc-dimm",
			"id":     fmt.Sprintf("dimm%d", dimmIndex),
			"memdev": fmt.Sprintf("mem%d", memIndex),
			"node":   index,
		}

		err = monitor.AddDevice(memDev)
		if err != nil {
			return err
		}
	}

	return nil
}

func (d *qemu) removeUnixDevices() error {
	// Check that we indeed have devices to remove.
	if !util.PathExists(d.DevicesPath()) {
		return nil
	}

	// Load the directory listing.
	dents, err := os.ReadDir(d.DevicesPath())
	if err != nil {
		return err
	}

	for _, f := range dents {
		// Skip non-Unix devices.
		if !strings.HasPrefix(f.Name(), "forkmknod.unix.") && !strings.HasPrefix(f.Name(), "unix.") && !strings.HasPrefix(f.Name(), "infiniband.unix.") {
			continue
		}

		// Remove the entry
		devicePath := filepath.Join(d.DevicesPath(), f.Name())
		err := os.Remove(devicePath)
		if err != nil {
			d.logger.Error("Failed removing unix device", logger.Ctx{"err": err, "path": devicePath})
		}
	}

	return nil
}

func (d *qemu) removeDiskDevices() error {
	// Check that we indeed have devices to remove.
	if !util.PathExists(d.DevicesPath()) {
		return nil
	}

	// Load the directory listing.
	dents, err := os.ReadDir(d.DevicesPath())
	if err != nil {
		return err
	}

	for _, f := range dents {
		// Skip non-disk devices
		if !strings.HasPrefix(f.Name(), "disk.") {
			continue
		}

		// Always try to unmount the host side.
		_ = unix.Unmount(filepath.Join(d.DevicesPath(), f.Name()), unix.MNT_DETACH)

		// Remove the entry.
		diskPath := filepath.Join(d.DevicesPath(), f.Name())
		err := os.Remove(diskPath)
		if err != nil {
			d.logger.Error("Failed to remove disk device path", logger.Ctx{"err": err, "path": diskPath})
		}
	}

	return nil
}

func (d *qemu) cleanup() {
	// Unmount any leftovers
	_ = d.removeUnixDevices()
	_ = d.removeDiskDevices()

	// Remove the security profiles
	_ = apparmor.InstanceDelete(d.state.OS, d)

	// Remove the devices path
	_ = os.Remove(d.DevicesPath())

	// Remove the shmounts path
	_ = os.RemoveAll(d.ShmountsPath())
}

// cleanupDevices performs any needed device cleanup steps when instance is stopped.
// Must be called before root volume is unmounted.
func (d *qemu) cleanupDevices() {
	// Clear up the config drive mount.
	err := d.configDriveMountPathClear()
	if err != nil {
		d.logger.Warn("Failed cleaning up config drive mount", logger.Ctx{"err": err})
	}

	for _, entry := range d.expandedDevices.Reversed() {
		dev, err := d.deviceLoad(d, entry.Name, entry.Config, false)
		if err != nil {
			if errors.Is(err, device.ErrUnsupportedDevType) {
				continue // Skip unsupported device (allows for mixed instance type profiles).
			}

			// Just log an error, but still allow the device to be stopped if usable device returned.
			d.logger.Error("Failed stop validation for device", logger.Ctx{"device": entry.Name, "err": err})
		}

		// If a usable device was returned from deviceLoad try to stop anyway, even if validation fails.
		// This allows for the scenario where a new version has additional validation restrictions
		// than older versions and we still need to allow previously valid devices to be stopped even if
		// they are no longer considered valid.
		if dev != nil {
			err = d.deviceStop(dev, false, "")
			if err != nil {
				d.logger.Error("Failed to stop device", logger.Ctx{"device": dev.Name(), "err": err})
			}
		}
	}
}

func (d *qemu) init() error {
	// Compute the expanded config and device list.
	err := d.expandConfig()
	if err != nil {
		return err
	}

	return nil
}

// Delete the instance.
// cleanupDependencies controls whether dependent resources (e.g. volumes,
// and related state) are removed along with the instance.
// When false, dependencies are preserved (e.g. storage-only moves).
func (d *qemu) Delete(force bool, cleanupDependencies bool) error {
	// Setup a new operation.
	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionDelete, nil, false, false)
	if err != nil {
		return fmt.Errorf("Failed to create instance delete operation: %w", err)
	}

	defer op.Done(nil)

	if d.IsRunning() {
		return api.StatusErrorf(http.StatusBadRequest, "Instance is running")
	}

	err = d.delete(force, cleanupDependencies)
	if err != nil {
		return err
	}

	// If dealing with a snapshot, refresh the backup file on the parent.
	if d.IsSnapshot() {
		parentName, _, _ := api.GetParentAndSnapshotName(d.name)

		// Load the parent.
		parent, err := instance.LoadByProjectAndName(d.state, d.project.Name, parentName)
		if err != nil {
			return fmt.Errorf("Invalid parent: %w", err)
		}

		// Update the backup file.
		err = parent.UpdateBackupFile()
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete the instance without creating an operation lock.
func (d *qemu) delete(force bool, cleanupDependencies bool) error {
	ctxMap := logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
	}

	if d.isSnapshot {
		d.logger.Info("Deleting instance snapshot", ctxMap)
	} else {
		d.logger.Info("Deleting instance", ctxMap)
	}

	// Check if instance is delete protected.
	if !force && util.IsTrue(d.expandedConfig["security.protection.delete"]) && !d.IsSnapshot() {
		return errors.New("Instance is protected")
	}

	// Delete any persistent warnings for instance.
	err := d.warningsDelete()
	if err != nil {
		return err
	}

	// Attempt to initialize storage interface for the instance.
	pool, err := d.getStoragePool()
	if err != nil && !response.IsNotFoundError(err) {
		return err
	} else if pool != nil {
		if d.IsSnapshot() {
			// Remove snapshot volume and database record.
			err = pool.DeleteInstanceSnapshot(d, nil)
			if err != nil {
				return err
			}
		} else {
			// Remove all snapshots.
			err := d.deleteSnapshots(func(snapInst instance.Instance) error {
				return snapInst.(*qemu).delete(true, cleanupDependencies) // Internal delete function that doesn't lock.
			})
			if err != nil {
				return fmt.Errorf("Failed deleting instance snapshots: %w", err)
			}

			// Remove the storage volume and database records.
			err = pool.DeleteInstance(d, nil)
			if err != nil {
				return err
			}

			if cleanupDependencies {
				// Delete all dependent volumes associated with this instance.
				err = d.ForEachDependentDiskType(func(dev deviceConfig.DeviceNamed) error {
					// Load the pool for the disk.
					diskPool, err := storagePools.LoadByName(d.state, dev.Config["pool"])
					if err != nil {
						return fmt.Errorf("Failed loading storage pool: %w", err)
					}

					volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
					err = diskPool.DeleteCustomVolume(d.Project().Name, volName, nil)
					if err != nil {
						return err
					}

					return nil
				})
				if err != nil {
					return err
				}
			}
		}
	}

	// Perform other cleanup steps if not snapshot.
	if !d.IsSnapshot() {
		// Remove all backups.
		backups, err := d.Backups()
		if err != nil {
			return err
		}

		for _, backup := range backups {
			err = backup.Delete()
			if err != nil {
				return err
			}
		}

		// Run device removal function for each device.
		d.devicesRemove(d, cleanupDependencies)

		// Clean things up.
		d.cleanup()
	}

	err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		// Remove the database record of the instance or snapshot instance.
		return tx.DeleteInstance(ctx, d.Project().Name, d.Name())
	})
	if err != nil {
		d.logger.Error("Failed deleting instance entry", logger.Ctx{"project": d.Project().Name})
		return err
	}

	if d.isSnapshot {
		d.logger.Info("Deleted instance snapshot", ctxMap)
	} else {
		d.logger.Info("Deleted instance", ctxMap)
	}

	if d.isSnapshot {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceSnapshotDeleted.Event(d, nil))
	} else {
		err = d.state.Authorizer.DeleteInstance(d.state.ShutdownCtx, d.project.Name, d.Name())
		if err != nil {
			logger.Error("Failed to remove instance from authorizer", logger.Ctx{"name": d.Name(), "project": d.project.Name, "error": err})
		}

		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceDeleted.Event(d, nil))
	}

	return nil
}

// Export publishes the instance.
func (d *qemu) Export(metaWriter io.Writer, rootfsWriter io.Writer, properties map[string]string, expiration time.Time, tracker *ioprogress.ProgressTracker) (*api.ImageMetadata, error) {
	ctxMap := logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
	}

	if d.IsRunning() {
		return nil, errors.New("Cannot export a running instance as an image")
	}

	d.logger.Info("Exporting instance", ctxMap)

	// Start the storage.
	mountInfo, err := d.mount()
	if err != nil {
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	defer logger.WarnOnError(d.unmount, "Failed to unmount instance")

	// Create the tarball.
	metaTarWriter := instancewriter.NewInstanceTarWriter(metaWriter, nil)

	// Path inside the tar image is the pathname starting after cDir.
	cDir := d.Path()
	offset := len(cDir) + 1

	writeToMetaTar := func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		err = metaTarWriter.WriteFile(path[offset:], path, fi, false)
		if err != nil {
			d.logger.Debug("Error tarring up", logger.Ctx{"path": path, "err": err})
			return err
		}

		return nil
	}

	// Get the instance's architecture.
	var arch string
	if d.IsSnapshot() {
		parentName, _, _ := api.GetParentAndSnapshotName(d.name)
		parent, err := instance.LoadByProjectAndName(d.state, d.project.Name, parentName)
		if err != nil {
			_ = metaTarWriter.Close()
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}

		arch, _ = osarch.ArchitectureName(parent.Architecture())
	} else {
		arch, _ = osarch.ArchitectureName(d.architecture)
	}

	if arch == "" {
		arch, err = osarch.ArchitectureName(d.state.OS.Architectures[0])
		if err != nil {
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	}

	// Generate metadata.yaml.
	meta := api.ImageMetadata{}
	fnam := filepath.Join(cDir, "metadata.yaml")

	if util.PathExists(fnam) {
		// Parse the metadata.
		content, err := os.ReadFile(fnam)
		if err != nil {
			_ = metaTarWriter.Close()
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}

		err = yaml.Load(content, &meta)
		if err != nil {
			_ = metaTarWriter.Close()
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	}

	// Fill in the metadata.
	meta.Architecture = arch
	meta.CreationDate = time.Now().UTC().Unix()

	if meta.Properties == nil {
		meta.Properties = map[string]string{}
	}

	maps.Copy(meta.Properties, properties)

	if !expiration.IsZero() {
		meta.ExpiryDate = expiration.UTC().Unix()
	}

	// Write the new metadata.yaml.
	tempDir, err := os.MkdirTemp("", "incus_metadata_")
	if err != nil {
		_ = metaTarWriter.Close()
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	defer logger.WarnOnError(func() error { return os.RemoveAll(tempDir) }, "Failed to remove temporary directory")

	data, err := yaml.Dump(&meta, yaml.WithV2Defaults())
	if err != nil {
		_ = metaTarWriter.Close()
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	fnam = filepath.Join(tempDir, "metadata.yaml")
	err = os.WriteFile(fnam, data, 0o644)
	if err != nil {
		_ = metaTarWriter.Close()
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	// Add metadata.yaml to the tarball.
	fi, err := os.Lstat(fnam)
	if err != nil {
		_ = metaTarWriter.Close()
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	tmpOffset := len(filepath.Dir(fnam)) + 1
	err = metaTarWriter.WriteFile(fnam[tmpOffset:], fnam, fi, false)
	if err != nil {
		_ = metaTarWriter.Close()
		d.logger.Debug("Error writing to tarfile", logger.Ctx{"err": err})
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	// Convert from raw to qcow2 and add to tarball.
	tmpPath, err := os.MkdirTemp(internalUtil.VarPath("images"), "incus_export_")
	if err != nil {
		return nil, err
	}

	defer logger.WarnOnError(func() error { return os.RemoveAll(tmpPath) }, "Failed to remove temporary directory")

	if mountInfo.DiskPath == "" {
		return nil, errors.New("No disk path available from mount")
	}

	fPath := fmt.Sprintf("%s/rootfs.img", tmpPath)

	// On some storage drivers (lvmcluster), the volume holds a qcow2 container rather than raw data.
	srcFormat := "raw"
	isQcow2, err := d.isQCOW2(mountInfo.DiskPath)
	if err != nil {
		return nil, fmt.Errorf("Failed checking disk format: %w", err)
	}

	if isQcow2 {
		srcFormat = storageDrivers.BlockVolumeTypeQcow2
	}

	// Convert to qcow2 image.
	cmd := []string{
		"nice", "-n19", // Run with low priority to reduce CPU impact on other processes.
		"qemu-img", "convert", "-p", "-f", srcFormat, "-O", "qcow2",
	}

	if rootfsWriter != nil {
		// Compress the qcow2 image if publishing a split image.
		cmd = append(cmd, "-c")
	}

	reverter := revert.New()
	defer reverter.Fail()

	// Check for Direct I/O support.
	from, err := os.OpenFile(mountInfo.DiskPath, unix.O_DIRECT|unix.O_RDONLY, 0)
	if err == nil {
		cmd = append(cmd, "-T", "none")
		_ = from.Close()
	}

	to, err := os.OpenFile(fPath, unix.O_DIRECT|unix.O_CREAT, 0o600)
	if err == nil {
		cmd = append(cmd, "-t", "none")
		_ = to.Close()
	}

	reverter.Add(func() { _ = os.Remove(fPath) })

	cmd = append(cmd, mountInfo.DiskPath, fPath)

	_, err = apparmor.QemuImg(d.state.OS, cmd, mountInfo.DiskPath, fPath, tracker)
	if err != nil {
		return nil, fmt.Errorf("Failed converting instance to qcow2: %w", err)
	}

	// Read converted file info and write file to tarball in the case of unified image
	// For split images, just write as a qcow2 file
	if rootfsWriter == nil {
		imgOffset := len(tmpPath) + 1
		fi, err = os.Lstat(fPath)
		if err != nil {
			return nil, err
		}

		err = metaTarWriter.WriteFile(fPath[imgOffset:], fPath, fi, false)
		if err != nil {
			return nil, err
		}
	} else {
		f, err := os.Open(fPath)
		if err != nil {
			return nil, err
		}

		r := io.Reader(f)
		_, err = util.SafeCopy(rootfsWriter, r)
		if err != nil {
			return nil, err
		}
	}

	// Include all the templates.
	fnam = d.TemplatesPath()
	if util.PathExists(fnam) {
		err = filepath.Walk(fnam, writeToMetaTar)
		if err != nil {
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	}

	err = metaTarWriter.Close()
	if err != nil {
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	reverter.Success()
	d.logger.Info("Exported instance", ctxMap)
	return &meta, nil
}

// MigrateSend is not currently supported.
func (d *qemu) MigrateSend(args instance.MigrateSendArgs) error {
	d.logger.Debug("Migration send starting")
	defer d.logger.Debug("Migration send stopped")

	// Check for stateful support.
	if args.Live && !d.CanLiveMigrate() {
		return errors.New("Live migration requires migration.stateful to be set to true")
	}

	// Setup a new operation.
	op := operationlock.Get(d.Project().Name, d.Name())
	if op != nil && op.ActionMatch(operationlock.ActionMigrate) {
		return errors.New("The instance is already being migrated")
	}

	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionMigrate, nil, false, true)
	if err != nil {
		return err
	}

	// Wait for essential migration connections before negotiation.
	connectionsCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	filesystemConn, err := args.FilesystemConn(connectionsCtx)
	if err != nil {
		op.Done(err)
		return err
	}

	pool, err := storagePools.LoadByInstance(d.state, d)
	if err != nil {
		err := fmt.Errorf("Failed loading instance: %w", err)
		op.Done(err)
		return err
	}

	clusterMove := args.ClusterMoveSourceName != ""
	remoteClusterMove := clusterMove && pool.Driver().Info().Remote
	storageMove := args.StoragePool != ""

	// The refresh argument passed to MigrationTypes() is always set
	// to false here. The migration source/sender doesn't need to care whether
	// or not it's doing a refresh as the migration sink/receiver will know
	// this, and adjust the migration types accordingly.
	// The same applies for clusterMove and storageMove, which are set to the most optimized defaults.
	poolMigrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(d), false, args.Snapshots, true, false)
	if len(poolMigrationTypes) == 0 {
		err := errors.New("No source migration types available")
		op.Done(err)
		return err
	}

	// Convert the pool's migration type options to an offer header to target.
	// Populate the Fs, ZfsFeatures and RsyncFeatures fields.
	offerHeader := localMigration.TypesToHeader(poolMigrationTypes...)

	// Offer to send index header.
	indexHeaderVersion := localMigration.IndexHeaderVersion
	offerHeader.IndexHeaderVersion = &indexHeaderVersion

	// For VMs, send block device size hint in offer header so that target can create the volume the same size.
	blockSize, err := storagePools.InstanceDiskBlockSize(pool, d, d.op)
	if err != nil {
		err := fmt.Errorf("Failed getting source disk size: %w", err)
		op.Done(err)
		return err
	}

	d.logger.Debug("Set migration offer volume size", logger.Ctx{"blockSize": blockSize})
	offerHeader.VolumeSize = &blockSize

	srcConfig, err := pool.GenerateInstanceBackupConfig(d, args.Snapshots, true, d.op)
	if err != nil {
		err := fmt.Errorf("Failed generating instance migration config: %w", err)
		op.Done(err)
		return err
	}

	dependentVolumesOffer, err := storagePools.GenerateDependentVolumesOffer(d.state, srcConfig, d.Project().Name, args.Snapshots, args.Devices, args.ClusterMoveSourceName != "")
	if err != nil {
		err := fmt.Errorf("Failed generating instance depending volumes offer: %w", err)
		op.Done(err)
		return err
	}

	offerHeader.DependentVolumes = dependentVolumesOffer

	contentType := storagePools.InstanceContentType(d)
	// If we are copying snapshots, retrieve a list of snapshots from source volume.
	if args.Snapshots {
		offerHeader.SnapshotNames = make([]string, 0, len(srcConfig.Snapshots))
		offerHeader.Snapshots = make([]*migration.Snapshot, 0, len(srcConfig.Snapshots))

		for i := range srcConfig.Snapshots {
			offerHeader.SnapshotNames = append(offerHeader.SnapshotNames, srcConfig.Snapshots[i].Name)

			// Calculating snapshot size can be very slow, skip unless absolutely needed.
			if !remoteClusterMove || storageMove {
				snapSize, err := storagePools.CalculateVolumeSnapshotSize(d.Project().Name, pool, contentType, storageDrivers.VolumeTypeVM, d.Name(), srcConfig.Snapshots[i].Name)
				if err != nil {
					op.Done(err)
					return err
				}

				srcConfig.Snapshots[i].Config["size"] = fmt.Sprintf("%d", snapSize)
			}

			offerHeader.Snapshots = append(offerHeader.Snapshots, instance.SnapshotToProtobuf(srcConfig.Snapshots[i]))
		}
	}

	// Offer QEMU to QEMU live state transfer state transfer feature.
	// If the request is for live migration, then offer that live QEMU to QEMU state transfer can proceed.
	// Otherwise we'll fallback to doing stateful stop, migrate, and then stateful start, which will still
	// fulfil the "live" part of the request, albeit with longer pause of the instance during the process.
	if args.Live {
		offerHeader.Criu = migration.CRIUType_VM_QEMU.Enum()
	}

	// Send offer to target.
	d.logger.Debug("Sending migration offer to target")
	err = args.ControlSend(offerHeader)
	if err != nil {
		err := fmt.Errorf("Failed sending migration offer header: %w", err)
		op.Done(err)
		return err
	}

	// Receive response from target.
	d.logger.Debug("Waiting for migration offer response from target")
	respHeader := &migration.MigrationHeader{}
	err = args.ControlReceive(respHeader, true)
	if err != nil {
		err := fmt.Errorf("Failed receiving migration offer response: %w", err)
		op.Done(err)
		return err
	}

	d.logger.Debug("Got migration offer response from target")

	// Negotiated migration types.
	migrationTypes, err := localMigration.MatchTypes(respHeader, storagePools.FallbackMigrationType(contentType), poolMigrationTypes)
	if err != nil {
		err := fmt.Errorf("Failed to negotiate migration type: %w", err)
		op.Done(err)
		return err
	}

	volumesWithTypes, err := storagePools.DependentVolumesMatchMigrationType(d.state, respHeader.DependentVolumes, args.Snapshots, nil, true)
	if err != nil {
		err := fmt.Errorf("Failed to negotiate migration types for dependent volumes: %w", err)
		op.Done(err)
		return err
	}

	dependentVolumes := []localMigration.DependentVolumeArgs{}
	for _, volWithType := range volumesWithTypes {
		dependentVolumes = append(dependentVolumes, localMigration.ProtobufToDependentVolume(volWithType.Volume, volWithType.VolumeTypes[0], nil))
	}

	volSourceArgs := &localMigration.VolumeSourceArgs{
		IndexHeaderVersion: respHeader.GetIndexHeaderVersion(), // Enable index header frame if supported.
		Name:               d.Name(),
		MigrationType:      migrationTypes[0],
		Snapshots:          offerHeader.SnapshotNames,
		TrackProgress:      true,
		Refresh:            respHeader.GetRefresh(),
		AllowInconsistent:  args.AllowInconsistent,
		VolumeOnly:         !args.Snapshots,
		Info:               &localMigration.Info{Config: srcConfig},
		ClusterMove:        clusterMove,
		StorageMove:        storageMove,
		DependentVolumes:   dependentVolumes,
	}

	// Only send the snapshots that the target requests when refreshing.
	if respHeader.GetRefresh() {
		volSourceArgs.Snapshots = respHeader.GetSnapshotNames()
		allSnapshots := volSourceArgs.Info.Config.VolumeSnapshots

		// Ensure that only the requested snapshots are included in the migration index header.
		volSourceArgs.Info.Config.VolumeSnapshots = make([]*api.StorageVolumeSnapshot, 0, len(volSourceArgs.Snapshots))
		for i := range allSnapshots {
			if slices.Contains(volSourceArgs.Snapshots, allSnapshots[i].Name) {
				volSourceArgs.Info.Config.VolumeSnapshots = append(volSourceArgs.Info.Config.VolumeSnapshots, allSnapshots[i])
			}
		}
	}

	// Detect whether the far side has chosen to use QEMU to QEMU live state transfer mode, and if so then
	// wait for the connection to be established.
	var stateConn io.ReadWriteCloser
	if args.Live && respHeader.Criu != nil && *respHeader.Criu == migration.CRIUType_VM_QEMU {
		stateConn, err = args.StateConn(connectionsCtx)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	g, ctx := errgroup.WithContext(context.Background())

	// Start control connection monitor.
	g.Go(func() error {
		d.logger.Debug("Migrate send control monitor started")
		defer d.logger.Debug("Migrate send control monitor finished")

		controlResult := make(chan error, 1) // Buffered to allow go routine to end if no readers.

		// This will read the result message from the target side and detect disconnections.
		go func() {
			resp := migration.MigrationControl{}
			err := args.ControlReceive(&resp, false)
			if err != nil {
				err = fmt.Errorf("Error reading migration control target: %w", err)
			} else if !resp.GetSuccess() {
				err = fmt.Errorf("Error from migration control target: %s", resp.GetMessage())
			}

			controlResult <- err
		}()

		// End as soon as we get control message/disconnection from the target side or a local error.
		select {
		case <-ctx.Done():
			err = ctx.Err()
		case err = <-controlResult:
		}

		return err
	})

	// Start error monitoring routine, this will detect when an error is returned from the other routines,
	// and if that happens it will disconnect the migration connections which will trigger the other routines
	// to finish.
	go func() {
		<-ctx.Done()
		args.Disconnect()
	}()

	g.Go(func() error {
		d.logger.Debug("Migrate send transfer started")
		defer d.logger.Debug("Migrate send transfer finished")

		var err error

		// Start live state transfer using state connection if supported.
		if stateConn != nil {
			// When performing intra-cluster same-name move, take steps to prevent corruption
			// of volatile device config keys during start & stop of instance on source/target.
			if args.ClusterMoveSourceName == d.name {
				// Disable VolatileSet from persisting changes to the database.
				// This is so the volatile changes written by the running receiving member
				// are not lost when the source instance is stopped.
				d.volatileSetPersistDisable = true

				// Store a reference to this instance (which has the old volatile settings)
				// to allow the onStop hook to pick it up, which allows the devices being
				// stopped to access their volatile settings stored when the instance
				// originally started on this cluster member.
				instanceRefSet(d)
				defer instanceRefClear(d)
			}

			err = d.migrateSendLive(ctx, pool, args.ClusterMoveSourceName, args.StoragePool, blockSize, filesystemConn, stateConn, volSourceArgs)
			if err != nil {
				return err
			}
		} else {
			// Perform stateful stop if live state transfer is not supported by target.
			if args.Live {
				err = d.Stop(true)
				if err != nil {
					return fmt.Errorf("Failed statefully stopping instance: %w", err)
				}
			}

			err = pool.MigrateInstance(d, filesystemConn, volSourceArgs, d.op)
			if err != nil {
				return err
			}
		}

		return nil
	})

	// Wait for routines to finish and collect first error.
	{
		err := g.Wait()
		if err != nil {
			op.Done(err)
			return err
		}

		op.Done(nil)

		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceMigrated.Event(d, nil))

		return nil
	}
}

// prepareEphemeralSnapshot sets up an overlay block device suitable for short lived operations.
// When backed is true, the overlay is opened with the disk's current top node as its backing
// node, as needed by copy-before-write overlays.
func (d *qemu) prepareEphemeralSnapshot(monitor *qmp.Monitor, diskName string, diskSize int64, backed bool) (string, string, func(), error) {
	snapshotDiskName := ephemeralSnapshotName(diskName)

	// Create snapshot of the disk.
	// We use the VM's config volume for this so that the maximum size of the snapshot can be limited
	// by setting the root disk's `size.state` property.
	snapshotFile := filepath.Join(d.Path(), fmt.Sprintf("%s.qcow2", snapshotDiskName))

	// Ensure there are no existing migration snapshot files.
	err := os.Remove(snapshotFile)
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		return "", "", nil, err
	}

	// Create qcow2 disk image with the maximum size set to the instance's root disk size for use as
	// a CoW target for the migration snapshot. This will be used during migration to store writes in
	// the guest whilst the storage driver is transferring the root disk and snapshots to the target.
	_, err = subprocess.RunCommand("qemu-img", "create", "-f", "qcow2", snapshotFile, fmt.Sprintf("%d", diskSize))
	if err != nil {
		return "", "", nil, fmt.Errorf("Failed opening file image for migration storage snapshot %q: %w", snapshotFile, err)
	}

	defer logger.WarnOnError(func() error { return os.Remove(snapshotFile) }, "Failed to remove snapshot file")

	// Pass the snapshot file to the running QEMU process.
	snapFile, err := os.OpenFile(snapshotFile, unix.O_RDWR, 0)
	if err != nil {
		return "", "", nil, fmt.Errorf("Failed opening file descriptor for migration storage snapshot %q: %w", snapshotFile, err)
	}

	defer logger.WarnOnError(snapFile.Close, "Failed to close snapshot file")

	// Remove the snapshot file as we don't want to sync this to the target.
	err = os.Remove(snapshotFile)
	if err != nil {
		return "", "", nil, err
	}

	info, err := monitor.SendFileWithFDSet(snapshotDiskName, snapFile, false)
	if err != nil {
		return "", "", nil, fmt.Errorf("Failed sending file descriptor of %q for migration storage snapshot: %w", snapFile.Name(), err)
	}

	defer logger.WarnOnError(func() error { return monitor.RemoveFDFromFDSet(snapshotDiskName) }, "Failed to remove FD from FD set")

	_ = snapFile.Close() // Don't prevent clean unmount when instance is stopped.

	// Find the disk's current top node, the base of the new overlay.
	blockDevs, err := d.fetchBlockDeviceChain(monitor, diskName)
	if err != nil {
		return "", "", nil, fmt.Errorf("Failed fetching block device chain: %w", err)
	}

	blockDevName := blockDevs[len(blockDevs)-1]

	blockDev := map[string]any{
		"driver":    "qcow2",
		"node-name": snapshotDiskName,
		"read-only": false,
		"file": map[string]any{
			"driver":   "file",
			"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
		},
	}

	if backed {
		blockDev["backing"] = blockDevName
	}

	// Add the snapshot file as a block device (not visible to the guest OS).
	err = monitor.AddBlockDevice(blockDev, nil, false)
	if err != nil {
		return "", "", nil, fmt.Errorf("Failed adding migration storage snapshot block device: %w", err)
	}

	removeOverlay := func() {
		err := monitor.RemoveBlockDevice(snapshotDiskName)
		if err != nil {
			d.logger.Error("Failed removing temporary snapshot disk device", logger.Ctx{"err": err})
		}
	}

	return snapshotDiskName, blockDevName, removeOverlay, nil
}

// mergeEphemeralSnapshot merges an ephemeral snapshot back into its base disk and removes it.
// On merge failure the overlay is kept attached as it still holds the guest's writes.
func (d *qemu) mergeEphemeralSnapshot(monitor *qmp.Monitor, overlayNode string) error {
	// Resume guest (this is needed as it will prevent merging the snapshot if paused).
	err := monitor.Start()
	if err != nil {
		d.logger.Warn("Failed resuming instance", logger.Ctx{"err": err})
	}

	// Merge the snapshot back into the source disk so we don't lose writes,
	// retrying as failures can be transient.
	for i := range 3 {
		if i > 0 {
			time.Sleep(time.Second)
		}

		err = monitor.BlockCommit(overlayNode, "", "")
		if err == nil {
			break
		}
	}

	if err != nil {
		// Keep the overlay attached, removing it would discard the guest's writes.
		d.logger.Error("Failed merging temporary storage snapshot, guest writes remain in the overlay", logger.Ctx{"overlay": overlayNode, "err": err})
		return fmt.Errorf("Failed merging temporary storage snapshot %q: %w", overlayNode, err)
	}

	err = monitor.RemoveBlockDevice(overlayNode)
	if err != nil {
		d.logger.Error("Failed removing temporary snapshot disk device", logger.Ctx{"err": err})
		return err
	}

	return nil
}

// removeEphemeralOverlay tears down a copy-before-write overlay, cancelling its backup job first.
func (d *qemu) removeEphemeralOverlay(monitor *qmp.Monitor, overlayNode string) error {
	// Cancel the copy-before-write job if it is still running.
	err := monitor.BlockJobCancelWait(overlayNode)
	if err != nil {
		d.logger.Debug("Failed cancelling overlay block job", logger.Ctx{"overlay": overlayNode, "err": err})
	}

	err = monitor.RemoveBlockDevice(overlayNode)
	if err != nil {
		return fmt.Errorf("Failed removing temporary snapshot overlay %q: %w", overlayNode, err)
	}

	return nil
}

// createEphemeralSnapshot creates a temporary snapshot of the disk that is intended for short-lived operations.
func (d *qemu) createEphemeralSnapshot(diskName string, diskSize int64) (func(), error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, err
	}

	snapshotDiskName, blockDevName, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, diskName, diskSize, false)
	if err != nil {
		return nil, err
	}

	// Take a snapshot of the disk and redirect writes to the snapshot disk.
	err = monitor.BlockDevSnapshot(blockDevName, snapshotDiskName)
	if err != nil {
		removeOverlay()
		return nil, fmt.Errorf("Failed taking temporary migration storage snapshot: %w", err)
	}

	cleanup := func() {
		_ = d.mergeEphemeralSnapshot(monitor, snapshotDiskName)
	}

	return cleanup, nil
}

// sendMigrationSnapshot transfers the snapshot to the target.
// If finalize is true, it performs cleanup after migration.
// Otherwise, it returns a finalize function that can be called later.
func (d *qemu) sendMigrationSnapshot(diskName string, filesystemConn io.ReadWriteCloser, finalize bool) (func() error, error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, err
	}

	reverter := revert.New()
	defer reverter.Fail()

	targetDiskName := migrationNBDTarget(diskName)
	snapshotDiskName := ephemeralSnapshotName(diskName)

	listener, err := net.Listen("unix", "")
	if err != nil {
		return nil, fmt.Errorf("Failed creating NBD unix listener: %w", err)
	}

	defer logger.WarnOnError(listener.Close, "Failed to close listener")

	g, _ := errgroup.WithContext(context.Background())

	g.Go(func() error {
		d.logger.Debug("NBD listener waiting for accept")
		nbdConn, err := listener.Accept()
		if err != nil {
			return fmt.Errorf("Failed accepting connection to NBD client unix listener: %w", err)
		}

		defer logger.WarnOnError(nbdConn.Close, "Failed to close connection")

		d.logger.Debug("NBD connection on source started")
		go func() { _, _ = util.SafeCopy(filesystemConn, nbdConn) }()

		_, _ = util.SafeCopy(nbdConn, filesystemConn)
		d.logger.Debug("NBD connection on source finished")

		return nil
	})

	// Connect to NBD migration target and add it the source instance as a disk device.
	d.logger.Debug("Connecting to migration NBD storage target", logger.Ctx{"targetDiskName": targetDiskName, "diskName": diskName})
	err = monitor.AddBlockDevice(map[string]any{
		"node-name": targetDiskName,
		"driver":    "raw",
		"file": map[string]any{
			"driver": "nbd",
			"export": diskName,
			"server": map[string]any{
				"type":     "unix",
				"abstract": true,
				"path":     strings.TrimPrefix(listener.Addr().String(), "@"),
			},
		},
	}, nil, false)
	if err != nil {
		return nil, fmt.Errorf("Failed adding NBD device: %w", err)
	}

	reverter.Add(func() {
		time.Sleep(time.Second) // Wait for it to be released.
		err := monitor.RemoveBlockDevice(targetDiskName)
		if err != nil {
			d.logger.Warn("Failed removing NBD storage target device", logger.Ctx{"err": err})
		}
	})

	d.logger.Debug("Connected to migration NBD storage target")

	// Begin transferring any writes that occurred during the storage migration by transferring the
	// contents of the (top) migration snapshot to the target disk to bring them into sync.
	// Once this has completed the guest OS will be paused.
	d.logger.Debug("Migration storage snapshot transfer started")
	err = monitor.BlockDevMirror(snapshotDiskName, targetDiskName)
	if err != nil {
		return nil, fmt.Errorf("Failed transferring migration storage snapshot: %w", err)
	}

	reverter.Add(func() {
		err = monitor.BlockJobCancel(snapshotDiskName)
		if err != nil {
			d.logger.Error("Failed cancelling block job", logger.Ctx{"err": err})
		}
	})

	d.logger.Debug("Migration storage snapshot transfer finished")

	finalizeFunc := func() error {
		filesystemConn.Close()
		_ = g.Wait()

		// Complete the migration snapshot sync process (the guest OS will remain paused).
		d.logger.Debug("Migration storage snapshot transfer commit started")
		err = monitor.BlockJobCancel(snapshotDiskName)
		if err != nil {
			return fmt.Errorf("Failed cancelling block job: %w", err)
		}

		d.logger.Debug("Migration storage snapshot transfer commit finished")

		time.Sleep(time.Second) // Wait for it to be released.

		// Remove the NBD client disk.
		err = monitor.RemoveBlockDevice(targetDiskName)
		if err != nil {
			d.logger.Warn("Failed removing NBD storage target device", logger.Ctx{"err": err})
		}

		d.logger.Debug("Removed NBD storage target device")

		// Merge snapshot back to the source disk so we don't lose the writes.
		err = monitor.BlockCommit(snapshotDiskName, "", "")
		if err != nil {
			return fmt.Errorf("Failed merging migration storage snapshot: %w", err)
		}

		err = monitor.RemoveBlockDevice(snapshotDiskName)
		if err != nil {
			return fmt.Errorf("Failed removing temporary snapshot disk device: %w", err)
		}

		return nil
	}

	if finalize {
		err = finalizeFunc()
		if err != nil {
			return nil, err
		}
	}

	reverter.Success()
	return finalizeFunc, nil
}

// migrateSendLive performs live migration send process.
func (d *qemu) migrateSendLive(ctx context.Context, pool storagePools.Pool, clusterMoveSourceName string, storagePool string, rootDiskSize int64, filesystemConn io.ReadWriteCloser, stateConn io.ReadWriteCloser, volSourceArgs *localMigration.VolumeSourceArgs) error {
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	// Get the root disk device config.
	rootDiskName, _, err := d.getRootDiskDevice()
	if err != nil {
		return err
	}

	rootDiskName = d.blockNodeName(linux.PathNameEncode(rootDiskName))

	// If we are performing an intra-cluster member move on a Ceph storage pool without storage change
	// then we can treat this as shared storage and avoid needing to sync the root disk.
	sameSharedStorage := clusterMoveSourceName != "" && pool.Driver().Info().Remote && storagePool == ""
	disksToMigrate := len(volSourceArgs.DependentVolumes) > 0

	dependentVolumeMove := clusterMoveSourceName != "" && disksToMigrate

	reverter := revert.New()

	// Non-shared storage snapshot setup.
	if !sameSharedStorage || dependentVolumeMove {
		// Setup migration capabilities.
		capabilities := map[string]bool{
			// Automatically throttle down the guest to speed up convergence of RAM migration.
			"auto-converge": true,

			// Allow the migration to be paused after the source qemu releases the block devices but
			// before the serialisation of the device state, to avoid a race condition between
			// migration and blockdev-mirror. This requires that the migration be continued after it
			// has reached the "pre-switchover" status.
			"pause-before-switchover": true,
		}

		err = monitor.MigrateSetCapabilities(capabilities)
		if err != nil {
			return fmt.Errorf("Failed setting migration capabilities: %w", err)
		}

		parameters := map[string]any{
			"cpu-throttle-initial":       50,
			"throttle-trigger-threshold": 20,
		}

		err = monitor.MigrateSetParameters(parameters)
		if err != nil {
			return fmt.Errorf("Failed setting migration parameters: %w", err)
		}

		if !sameSharedStorage {
			cleanup, err := d.createEphemeralSnapshot(rootDiskName, rootDiskSize)
			if err != nil {
				return fmt.Errorf("Failed creating migration snapshot: %w", err)
			}

			reverter.Add(cleanup)
		}

		for _, vol := range volSourceArgs.DependentVolumes {
			diskName := d.blockNodeName(linux.PathNameEncode(vol.DeviceName))

			d.logger.Debug("Create snapshot for dependent volume", logger.Ctx{"name": vol.Name, "size": vol.VolumeSize, "diskName": diskName})

			cleanup, err := d.createEphemeralSnapshot(diskName, vol.VolumeSize)
			if err != nil {
				return fmt.Errorf("Failed creating migration snapshot: %w", err)
			}

			reverter.Add(cleanup)
		}

		d.logger.Debug("Setup temporary migration storage snapshot")
	} else {
		// Still set some options for shared storage.
		capabilities := map[string]bool{
			// Automatically throttle down the guest to speed up convergence of RAM migration.
			"auto-converge": true,
		}

		err = monitor.MigrateSetCapabilities(capabilities)
		if err != nil {
			return fmt.Errorf("Failed setting migration capabilities: %w", err)
		}

		parameters := map[string]any{
			"cpu-throttle-initial":       50,
			"throttle-trigger-threshold": 20,
		}

		err = monitor.MigrateSetParameters(parameters)
		if err != nil {
			return fmt.Errorf("Failed setting migration parameters: %w", err)
		}
	}

	// Perform storage transfer while instance is still running.
	// For shared storage the storage driver will likely not do much here, but we still call it anyway for the
	// sense checks it performs.
	// We enable AllowInconsistent mode as this allows for transferring the VM storage whilst it is running
	// and the snapshot we took earlier is designed to provide consistency anyway.
	volSourceArgs.AllowInconsistent = true
	err = pool.MigrateInstance(d, filesystemConn, volSourceArgs, d.op)
	if err != nil {
		return err
	}

	// Derive the effective storage project name from the instance config's project.
	storageProjectName, err := project.StorageVolumeProject(d.state.DB.Cluster, d.project.Name, db.StoragePoolVolumeTypeCustom)
	if err != nil {
		return err
	}

	// Notify the shared disks that they're going to be accessed from another system,
	// but only when performing a move within the same storage pool.
	if storagePool == "" && clusterMoveSourceName != "" {
		for _, dev := range d.expandedDevices.Sorted() {
			if dev.Config["type"] != "disk" || dev.Config["path"] == "/" || dev.Config["pool"] == "" {
				continue
			}

			// Load the pool for the disk.
			diskPool, err := storagePools.LoadByName(d.state, dev.Config["pool"])
			if err != nil {
				return fmt.Errorf("Failed loading storage pool: %w", err)
			}

			// Check that we're on shared storage.
			if !diskPool.Driver().Info().Remote {
				continue
			}

			// Setup the volume entry.
			extraSourceArgs := &localMigration.VolumeSourceArgs{
				ClusterMove: true,
			}

			vol := diskPool.GetVolume(storageDrivers.VolumeTypeCustom, storageDrivers.ContentTypeBlock, project.StorageVolume(storageProjectName, dev.Config["source"]), nil)

			// Call MigrateVolume on the source.
			err = diskPool.Driver().MigrateVolume(vol, nil, extraSourceArgs, nil)
			if err != nil {
				return fmt.Errorf("Failed to prepare device %q for migration: %w", dev.Name, err)
			}
		}
	}

	var finalizeRootTransfer func() error
	if !sameSharedStorage {
		finalizeRootTransfer, err = d.sendMigrationSnapshot(rootDiskName, filesystemConn, false)
		if err != nil {
			return fmt.Errorf("Failed transferring snapshot disk: %w", err)
		}
	}

	d.logger.Debug("Stateful migration checkpoint send starting")

	// Send checkpoint to QEMU process on target. This will pause the guest OS (if not already paused).
	pipeRead, pipeWrite, err := os.Pipe()
	if err != nil {
		return err
	}

	defer func() {
		_ = pipeRead.Close()
		_ = pipeWrite.Close()
	}()

	go func() { _, _ = util.SafeCopy(stateConn, pipeRead) }()

	err = d.saveStateHandle(monitor, pipeWrite)
	if err != nil {
		return fmt.Errorf("Failed starting state transfer to target: %w", err)
	}

	// Start monitoring the migration progress.
	chMonitor := make(chan bool, 1)

	if d.op != nil {
		go func() {
			for {
				// Wait for next update.
				select {
				case <-chMonitor:
					return

				case <-time.After(time.Second):
				}

				// Get current migration progress.
				progress, err := monitor.QueryMigrate()
				if err != nil {
					// Stop monitoring on error.
					return
				}

				// Post update.
				percent := int64(float64(progress.RAM.Transferred) / float64(progress.RAM.Total) * float64(100))
				speed := int64(progress.RAM.MBps * 1024 * 1024 / 8)

				metadata := map[string]any{}
				metadata["progress"] = map[string]string{
					"stage":     "live_migrate_instance",
					"processed": strconv.FormatInt(progress.RAM.Transferred, 10),
					"percent":   strconv.FormatInt(percent, 10),
					"speed":     strconv.FormatInt(speed, 10),
				}

				metadata["live_migrate_instance_progress"] = fmt.Sprintf("Live migration: %s remaining (%s/s) (%d%% CPU throttle)", units.GetByteSizeString(progress.RAM.Remaining, 2), units.GetByteSizeString(speed, 2), progress.CPUThrottlePercentage)
				_ = d.op.UpdateMetadata(metadata)
			}
		}()
	}

	// Non-shared storage snapshot transfer finalization.
	if !sameSharedStorage || dependentVolumeMove {
		// Wait until state transfer has reached pre-switchover state (the guest OS will remain paused).
		err = monitor.MigrateWait(ctx, "pre-switchover")
		if err != nil {
			return fmt.Errorf("Failed waiting for state transfer to reach pre-switchover stage: %w", err)
		}

		d.logger.Debug("Stateful migration checkpoint reached pre-switchover phase")

		if finalizeRootTransfer != nil {
			err = finalizeRootTransfer()
			if err != nil {
				return fmt.Errorf("Failed transferring root snapshot disk: %w", err)
			}
		}

		for _, vol := range volSourceArgs.DependentVolumes {
			diskName := d.blockNodeName(linux.PathNameEncode(vol.DeviceName))

			_, err = d.sendMigrationSnapshot(diskName, filesystemConn, true)
			if err != nil {
				return fmt.Errorf("Failed transferring snapshot disk: %w", err)
			}
		}

		// Finalise the migration state transfer (the guest OS will remain paused).
		err = monitor.MigrateContinue("pre-switchover")
		if err != nil {
			return fmt.Errorf("Failed continuing state transfer: %w", err)
		}

		d.logger.Debug("Stateful migration checkpoint send continuing")
	}

	// Wait until the migration state transfer has completed (the guest OS will remain paused).
	err = monitor.MigrateWait(ctx, "completed")
	if err != nil {
		return fmt.Errorf("Failed waiting for state transfer to reach completed stage: %w", err)
	}

	close(chMonitor)

	d.logger.Debug("Stateful migration checkpoint send finished")

	if clusterMoveSourceName != "" {
		// If doing an intra-cluster member move then we will be deleting the instance on the source,
		// so lets just stop it after migration is completed.
		err = d.Stop(false)
		if err != nil {
			return fmt.Errorf("Failed stopping instance: %w", err)
		}
	} else {
		// Resume guest.
		err = monitor.Start()
		if err != nil {
			return fmt.Errorf("Failed resuming instance: %w", err)
		}

		d.logger.Debug("Resumed instance")
	}

	reverter.Success()

	return nil
}

// MigrateReceive receives an instance being migrated from a source.
func (d *qemu) MigrateReceive(args instance.MigrateReceiveArgs) error {
	d.logger.Debug("Migration receive starting")
	defer d.logger.Debug("Migration receive stopped")

	// Wait for essential migration connections before negotiation.
	connectionsCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	filesystemConn, err := args.FilesystemConn(connectionsCtx)
	if err != nil {
		return err
	}

	// Receive offer from source.
	d.logger.Debug("Waiting for migration offer from source")
	offerHeader := &migration.MigrationHeader{}
	err = args.ControlReceive(offerHeader, true)
	if err != nil {
		return fmt.Errorf("Failed receiving migration offer from source: %w", err)
	}

	// When doing a cluster same-name move we cannot load the storage pool using the instance's volume DB
	// record because it may be associated to the wrong cluster member. Instead we ascertain the pool to load
	// using the instance's root disk device.
	if args.ClusterMoveSourceName == d.name {
		if args.StoragePool != "" {
			d.storagePool, err = storagePools.LoadByName(d.state, args.StoragePool)
			if err != nil {
				return fmt.Errorf("Failed loading storage pool: %w", err)
			}
		} else {
			_, rootDiskDevice, err := d.getRootDiskDevice()
			if err != nil {
				return fmt.Errorf("Failed getting root disk: %w", err)
			}

			if rootDiskDevice["pool"] == "" {
				return errors.New("The instance's root device is missing the pool property")
			}

			// Initialize the storage pool cache.
			d.storagePool, err = storagePools.LoadByName(d.state, rootDiskDevice["pool"])
			if err != nil {
				return fmt.Errorf("Failed loading storage pool: %w", err)
			}
		}
	}

	pool, err := storagePools.LoadByInstance(d.state, d)
	if err != nil {
		return err
	}

	// The source will never set Refresh in the offer header.
	// However, to determine the correct migration type Refresh needs to be set.
	offerHeader.Refresh = &args.Refresh

	clusterMove := args.ClusterMoveSourceName != ""
	storageMove := args.StoragePool != ""

	// Extract the source's migration type and then match it against our pool's supported types and features.
	// If a match is found the combined features list will be sent back to requester.
	contentType := storagePools.InstanceContentType(d)
	respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, args.Refresh, args.Snapshots, clusterMove, storageMove))
	if err != nil {
		return err
	}

	// The migration header to be sent back to source with our target options.
	// Convert response type to response header and copy snapshot info into it.
	respHeader := localMigration.TypesToHeader(respTypes...)

	// Respond with our maximum supported header version if the requested version is higher than ours.
	// Otherwise just return the requested header version to the source.
	indexHeaderVersion := min(offerHeader.GetIndexHeaderVersion(), localMigration.IndexHeaderVersion)

	respHeader.IndexHeaderVersion = &indexHeaderVersion
	respHeader.SnapshotNames = offerHeader.SnapshotNames
	respHeader.Snapshots = offerHeader.Snapshots
	respHeader.Refresh = &args.Refresh

	localDevices := d.localDevices.CloneNative()
	volumesWithTypes, err := storagePools.DependentVolumesMatchMigrationType(d.state, offerHeader.DependentVolumes, args.Snapshots, localDevices, false)
	if err != nil {
		return fmt.Errorf("Failed to negotiate migration types for dependent volumes: %w", err)
	}

	dependentVolumes := []localMigration.DependentVolumeArgs{}
	for _, volWithType := range volumesWithTypes {
		respHeader.DependentVolumes = append(respHeader.DependentVolumes, volWithType.Volume)
		vol := localMigration.ProtobufToDependentVolume(volWithType.Volume, volWithType.VolumeTypes[0], localDevices[*volWithType.Volume.DeviceName])
		dependentVolumes = append(dependentVolumes, vol)
	}

	if args.Refresh {
		// Get the remote snapshots on the source.
		sourceSnapshots := offerHeader.GetSnapshots()
		sourceSnapshotComparable := make([]storagePools.ComparableSnapshot, 0, len(sourceSnapshots))
		for _, sourceSnap := range sourceSnapshots {
			sourceSnapshotComparable = append(sourceSnapshotComparable, storagePools.ComparableSnapshot{
				Name:         sourceSnap.GetName(),
				CreationDate: time.Unix(sourceSnap.GetCreationDate(), 0),
			})
		}

		// Get existing snapshots on the local target.
		targetSnapshots, err := d.Snapshots()
		if err != nil {
			return err
		}

		targetSnapshotsComparable := make([]storagePools.ComparableSnapshot, 0, len(targetSnapshots))
		for _, targetSnap := range targetSnapshots {
			_, targetSnapName, _ := api.GetParentAndSnapshotName(targetSnap.Name())

			targetSnapshotsComparable = append(targetSnapshotsComparable, storagePools.ComparableSnapshot{
				Name:         targetSnapName,
				CreationDate: targetSnap.CreationDate(),
			})
		}

		// Compare the two sets.
		syncSourceSnapshotIndexes, deleteTargetSnapshotIndexes := storagePools.CompareSnapshots(sourceSnapshotComparable, targetSnapshotsComparable, args.RefreshExcludeOlder)

		// Delete the extra local snapshots first.
		for _, deleteTargetSnapshotIndex := range deleteTargetSnapshotIndexes {
			err := targetSnapshots[deleteTargetSnapshotIndex].Delete(true, true)
			if err != nil {
				return err
			}
		}

		// Only request to send the snapshots that need updating.
		syncSnapshotNames := make([]string, 0, len(syncSourceSnapshotIndexes))
		syncSnapshots := make([]*migration.Snapshot, 0, len(syncSourceSnapshotIndexes))
		for _, syncSourceSnapshotIndex := range syncSourceSnapshotIndexes {
			syncSnapshotNames = append(syncSnapshotNames, sourceSnapshots[syncSourceSnapshotIndex].GetName())
			syncSnapshots = append(syncSnapshots, sourceSnapshots[syncSourceSnapshotIndex])
		}

		respHeader.Snapshots = syncSnapshots
		respHeader.SnapshotNames = syncSnapshotNames
		offerHeader.Snapshots = syncSnapshots
		offerHeader.SnapshotNames = syncSnapshotNames
	}

	// Negotiate support for QEMU to QEMU live state transfer.
	// If the request is for live migration, then respond that live QEMU to QEMU state transfer can proceed.
	// Otherwise we'll fallback to doing stateful stop, migrate, and then stateful start, which will still
	// fulfil the "live" part of the request, albeit with longer pause of the instance during the process.
	poolInfo := pool.Driver().Info()
	var useStateConn bool
	if args.Live && offerHeader.Criu != nil && *offerHeader.Criu == migration.CRIUType_VM_QEMU {
		respHeader.Criu = migration.CRIUType_VM_QEMU.Enum()
		useStateConn = true
	}

	// Send response to source.
	d.logger.Debug("Sending migration response to source")
	err = args.ControlSend(respHeader)
	if err != nil {
		return fmt.Errorf("Failed sending migration response to source: %w", err)
	}

	d.logger.Debug("Sent migration response to source")

	// Establish state transfer connection if needed.
	var stateConn io.ReadWriteCloser
	if args.Live && useStateConn {
		stateConn, err = args.StateConn(connectionsCtx)
		if err != nil {
			return err
		}
	}

	reverter := revert.New()
	defer reverter.Fail()

	g, ctx := errgroup.WithContext(context.Background())

	// Start control connection monitor.
	g.Go(func() error {
		d.logger.Debug("Migrate receive control monitor started")
		defer d.logger.Debug("Migrate receive control monitor finished")

		controlResult := make(chan error, 1) // Buffered to allow go routine to end if no readers.

		// This will read the result message from the source side and detect disconnections.
		go func() {
			resp := migration.MigrationControl{}
			err := args.ControlReceive(&resp, false)
			if err != nil {
				err = fmt.Errorf("Error reading migration control source: %w", err)
			} else if !resp.GetSuccess() {
				err = fmt.Errorf("Error from migration control source: %s", resp.GetMessage())
			}

			controlResult <- err
		}()

		// End as soon as we get control message/disconnection from the source side or a local error.
		select {
		case <-ctx.Done():
			err = ctx.Err()
		case err = <-controlResult:
		}

		return err
	})

	// Start error monitoring routine, this will detect when an error is returned from the other routines,
	// and if that happens it will disconnect the migration connections which will trigger the other routines
	// to finish.
	go func() {
		<-ctx.Done()
		args.Disconnect()
	}()

	// Start filesystem transfer routine and initialize a channel that is closed when the routine finishes.
	fsTransferDone := make(chan struct{})
	g.Go(func() error {
		defer close(fsTransferDone)

		d.logger.Debug("Migrate receive transfer started")
		defer d.logger.Debug("Migrate receive transfer finished")

		var err error

		snapshots := make([]*migration.Snapshot, 0)

		// Legacy: we only sent the snapshot names, so we just copy the instances's config over,
		// same as we used to do.
		if len(offerHeader.SnapshotNames) != len(offerHeader.Snapshots) {
			// Convert the instance to an api.InstanceSnapshot.

			profileNames := make([]string, 0, len(d.Profiles()))
			for _, p := range d.Profiles() {
				profileNames = append(profileNames, p.Name)
			}

			architectureName, _ := osarch.ArchitectureName(d.Architecture())
			apiInstSnap := &api.InstanceSnapshot{
				InstanceSnapshotPut: api.InstanceSnapshotPut{
					ExpiresAt: time.Time{},
				},
				Architecture: architectureName,
				CreatedAt:    d.CreationDate(),
				LastUsedAt:   d.LastUsedDate(),
				Config:       d.LocalConfig(),
				Description:  d.Description(),
				Devices:      d.LocalDevices().CloneNative(),
				Ephemeral:    d.IsEphemeral(),
				Stateful:     d.IsStateful(),
				Profiles:     profileNames,
			}

			for _, name := range offerHeader.SnapshotNames {
				base := instance.SnapshotToProtobuf(apiInstSnap)
				base.Name = &name
				snapshots = append(snapshots, base)
			}
		} else {
			snapshots = offerHeader.Snapshots
		}

		volTargetArgs := localMigration.VolumeTargetArgs{
			IndexHeaderVersion:    respHeader.GetIndexHeaderVersion(),
			Name:                  d.Name(),
			MigrationType:         respTypes[0],
			Refresh:               args.Refresh, // Indicate to receiver volume should exist.
			TrackProgress:         true,         // Use a progress tracker on receiver to get in-cluster progress information.
			Live:                  args.Live,
			VolumeSize:            offerHeader.GetVolumeSize(), // Block size setting override.
			VolumeOnly:            !args.Snapshots,
			ClusterMoveSourceName: args.ClusterMoveSourceName,
			StoragePool:           args.StoragePool,
			DependentVolumes:      dependentVolumes,
		}

		// At this point we have already figured out the parent instances's root
		// disk device so we can simply retrieve it from the expanded devices.
		parentStoragePool := ""
		parentExpandedDevices := d.ExpandedDevices()
		parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := internalInstance.GetRootDiskDevice(parentExpandedDevices.CloneNative())
		if parentLocalRootDiskDeviceKey != "" {
			parentStoragePool = parentLocalRootDiskDevice["pool"]
		}

		if parentStoragePool == "" {
			return errors.New("Instance's root device is missing the pool property")
		}

		// A zero length Snapshots slice indicates volume only migration in
		// VolumeTargetArgs. So if VolumeOnly was requested, do not populate them.
		if args.Snapshots {
			volTargetArgs.Snapshots = make([]*migration.Snapshot, 0, len(snapshots))
			for _, snap := range snapshots {
				migrationSnapshot := &migration.Snapshot{Name: snap.Name}
				migration.SetSnapshotConfigValue(migrationSnapshot, "size", migration.GetSnapshotConfigValue(snap, "size"))

				volTargetArgs.Snapshots = append(volTargetArgs.Snapshots, migrationSnapshot)

				// Only create snapshot instance DB records if not doing a cluster same-name move.
				// As otherwise the DB records will already exist.
				if args.ClusterMoveSourceName != d.name {
					snapArgs, err := instance.SnapshotProtobufToInstanceArgs(d.state, d, snap)
					if err != nil {
						return err
					}

					// The offerHeader, depending on the case, stores information about either an InstanceSnapshot
					// or a StorageVolumeSnapshot. In the Config, we pass information about the volume size,
					// but an InstanceSnapshot config cannot have a 'size' key. This key should be removed
					// before passing the data to the CreateInternal method.
					delete(snapArgs.Config, "size")

					// Ensure that snapshot and parent instance have the same storage pool in
					// their local root disk device. If the root disk device for the snapshot
					// comes from a profile on the new instance as well we don't need to do
					// anything.
					if snapArgs.Devices != nil {
						snapLocalRootDiskDeviceKey, _, _ := internalInstance.GetRootDiskDevice(snapArgs.Devices.CloneNative())
						if snapLocalRootDiskDeviceKey != "" {
							snapArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
						}
					}

					// Create the snapshot instance.
					_, snapInstOp, cleanup, err := instance.CreateInternal(d.state, *snapArgs, d.op, true, false, false)
					if err != nil {
						return fmt.Errorf("Failed creating instance snapshot record %q: %w", snapArgs.Name, err)
					}

					reverter.Add(cleanup)
					defer snapInstOp.Done(err)
				}
			}
		}

		err = pool.CreateInstanceFromMigration(d, filesystemConn, volTargetArgs, d.op)
		if err != nil {
			return fmt.Errorf("Failed creating instance on target: %w", err)
		}

		isRemoteClusterMove := clusterMove && poolInfo.Remote
		reverter.Add(func() {
			// Delete the instance unless it is moved within the same cluster on a shared pool.
			if (!isRemoteClusterMove && !storageMove) || storageMove {
				_ = pool.DeleteInstance(d, d.op)
			}
		})

		// Derive the effective storage project name from the instance config's project.
		storageProjectName, err := project.StorageVolumeProject(d.state.DB.Cluster, d.project.Name, db.StoragePoolVolumeTypeCustom)
		if err != nil {
			return err
		}

		// Notify the shared disks that they're going to be accessed from another system,
		// but only when performing a move within the same storage pool.
		if !storageMove && args.ClusterMoveSourceName != "" {
			for _, dev := range d.expandedDevices.Sorted() {
				if dev.Config["type"] != "disk" || dev.Config["path"] == "/" || dev.Config["pool"] == "" {
					continue
				}

				// Load the pool for the disk.
				diskPool, err := storagePools.LoadByName(d.state, dev.Config["pool"])
				if err != nil {
					return fmt.Errorf("Failed loading storage pool: %w", err)
				}

				// Check that we're on shared storage.
				if !diskPool.Driver().Info().Remote {
					continue
				}

				// Setup the volume entry.
				extraTargetArgs := localMigration.VolumeTargetArgs{
					ClusterMoveSourceName: args.ClusterMoveSourceName,
					StoragePool:           args.StoragePool,
				}

				vol := diskPool.GetVolume(storageDrivers.VolumeTypeCustom, storageDrivers.ContentTypeBlock, project.StorageVolume(storageProjectName, dev.Config["source"]), nil)

				// Call CreateVolumeFromMigration on the target.
				err = diskPool.Driver().CreateVolumeFromMigration(vol, nil, extraTargetArgs, nil, nil)
				if err != nil {
					return fmt.Errorf("Failed to prepare device %q for migration: %w", dev.Name, err)
				}
			}
		}

		// Only delete all instance volumes on error if the pool volume creation has succeeded to
		// avoid deleting an existing conflicting volume.
		if !volTargetArgs.Refresh && !isRemoteClusterMove {
			reverter.Add(func() {
				snapshots, _ := d.Snapshots()
				snapshotCount := len(snapshots)
				for k := range snapshots {
					// Delete the snapshots in reverse order.
					k = snapshotCount - 1 - k
					_ = pool.DeleteInstanceSnapshot(snapshots[k], nil)
				}

				_ = pool.DeleteInstance(d, nil)
			})
		}

		if args.ClusterMoveSourceName != d.name {
			err = d.DeferTemplateApply(instance.TemplateTriggerCopy)
			if err != nil {
				return err
			}
		}

		if args.Live {
			// Start live state transfer using state connection if supported.
			if stateConn != nil {
				d.migrationReceiveStateful = map[string]io.ReadWriteCloser{
					api.SecretNameState: stateConn,
				}

				d.disksToMigrate = append(d.disksToMigrate, dependentVolumes...)

				dependentVolumeMove := args.ClusterMoveSourceName != "" && len(d.disksToMigrate) > 0

				// Populate the filesystem connection handle if doing non-shared storage migration.
				sameSharedStorage := args.ClusterMoveSourceName != "" && poolInfo.Remote && args.StoragePool == ""
				if !sameSharedStorage || dependentVolumeMove {
					d.migrationReceiveStateful[api.SecretNameFilesystem] = filesystemConn
				}

				d.migrationRootDisk = !sameSharedStorage
				d.migrationClusterMove = args.ClusterMoveSourceName != ""
			}

			// Although the instance technically isn't considered stateful, we set this to allow
			// starting from the migrated state file or migration state connection.
			d.stateful = true

			err = d.start(true, args.InstanceOperation)
			if err != nil {
				return err
			}
		}

		return nil
	})

	{
		// Wait until the filesystem transfer routine has finished.
		<-fsTransferDone

		// If context is cancelled by this stage, then an error has occurred.
		// Wait for all routines to finish and collect the first error that occurred.
		if ctx.Err() != nil {
			err := g.Wait()

			// Send failure response to source.
			msg := migration.MigrationControl{
				Success: proto.Bool(err == nil),
			}

			if err != nil {
				msg.Message = proto.String(err.Error())
			}

			d.logger.Debug("Sending migration failure response to source", logger.Ctx{"err": err})
			sendErr := args.ControlSend(&msg)
			if sendErr != nil {
				d.logger.Warn("Failed sending migration failure to source", logger.Ctx{"err": sendErr})
			}

			return err
		}

		// Send success response to source to control as nothing has gone wrong so far.
		msg := migration.MigrationControl{
			Success: proto.Bool(true),
		}

		d.logger.Debug("Sending migration success response to source", logger.Ctx{"success": msg.GetSuccess()})
		err := args.ControlSend(&msg)
		if err != nil {
			d.logger.Warn("Failed sending migration success to source", logger.Ctx{"err": err})
			return fmt.Errorf("Failed sending migration success to source: %w", err)
		}

		// Wait for all routines to finish (in this case it will be the control monitor) but do
		// not collect the error, as it will just be a disconnect error from the source.
		_ = g.Wait()

		reverter.Success()

		return nil
	}
}

// CGroup is not implemented for VMs.
func (d *qemu) CGroup() (*cgroup.CGroup, error) {
	return nil, instance.ErrNotImplemented
}

// FileSFTPConn returns a connection to the agent SFTP endpoint.
func (d *qemu) FileSFTPConn() (net.Conn, error) {
	// VMs, unlike containers, cannot perform file operations if not running and using the agent.
	if !d.IsRunning() {
		return nil, errors.New("Instance is not running")
	}

	// Connect to the agent.
	client, err := d.getAgentClient()
	if err != nil {
		return nil, err
	}

	// Get the HTTP transport.
	httpTransport, ok := client.Transport.(*http.Transport)
	if !ok {
		return nil, errors.New("Agent client transport is not an *http.Transport")
	}

	// Send the upgrade request.
	u, err := url.Parse("https://custom.socket/1.0/sftp")
	if err != nil {
		return nil, err
	}

	req := &http.Request{
		Method:     http.MethodGet,
		URL:        u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       u.Host,
	}

	req.Header["Upgrade"] = []string{"sftp"}
	req.Header["Connection"] = []string{"Upgrade"}

	conn, err := httpTransport.DialContext(context.Background(), "tcp", "8443")
	if err != nil {
		return nil, err
	}

	tlsConn := tls.Client(conn, httpTransport.TLSClientConfig)
	err = tlsConn.Handshake()
	if err != nil {
		return nil, err
	}

	err = req.Write(tlsConn)
	if err != nil {
		return nil, err
	}

	resp, err := http.ReadResponse(bufio.NewReader(tlsConn), req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusSwitchingProtocols {
		return nil, fmt.Errorf("Dialing failed: expected status code 101 got %d", resp.StatusCode)
	}

	if resp.Header.Get("Upgrade") != "sftp" {
		return nil, errors.New("Missing or unexpected Upgrade header in response")
	}

	return tlsConn, nil
}

// FileSFTP returns an SFTP connection to the agent endpoint.
func (d *qemu) FileSFTP() (*sftp.Client, error) {
	// Connect to the forkfile daemon.
	conn, err := d.FileSFTPConn()
	if err != nil {
		return nil, err
	}

	// Get a SFTP client.
	client, err := sftp.NewClientPipe(conn, conn)
	if err != nil {
		_ = conn.Close()
		return nil, err
	}

	go func() {
		// Wait for the client to be done before closing the connection.
		_ = client.Wait()
		_ = conn.Close()
	}()

	return client, nil
}

// PortForwardConn connects to the given address and TCP port inside of the instance through the agent.
func (d *qemu) PortForwardConn(address string, port int) (net.Conn, error) {
	// VMs, unlike containers, cannot forward connections if not running and using the agent.
	if !d.IsRunning() {
		return nil, errors.New("Instance is not running")
	}

	// Connect to the agent.
	client, err := d.getAgentClient()
	if err != nil {
		return nil, err
	}

	// Get the HTTP transport.
	httpTransport, ok := client.Transport.(*http.Transport)
	if !ok {
		return nil, errors.New("Agent client transport is not an *http.Transport")
	}

	// Send the upgrade request.
	u, err := url.Parse("https://custom.socket/1.0/port-forward")
	if err != nil {
		return nil, err
	}

	body, err := json.Marshal(api.InstancePortForwardPost{Address: address, Port: port})
	if err != nil {
		return nil, err
	}

	req := &http.Request{
		Method:        http.MethodPost,
		URL:           u,
		Proto:         "HTTP/1.1",
		ProtoMajor:    1,
		ProtoMinor:    1,
		Header:        make(http.Header),
		Host:          u.Host,
		Body:          io.NopCloser(bytes.NewReader(body)),
		ContentLength: int64(len(body)),
	}

	req.Header["Upgrade"] = []string{"tcp"}
	req.Header["Connection"] = []string{"Upgrade"}
	req.Header.Set("Content-Type", "application/json")

	conn, err := httpTransport.DialContext(context.Background(), "tcp", "8443")
	if err != nil {
		return nil, err
	}

	tlsConn := tls.Client(conn, httpTransport.TLSClientConfig)
	err = tlsConn.Handshake()
	if err != nil {
		return nil, err
	}

	err = req.Write(tlsConn)
	if err != nil {
		return nil, err
	}

	resp, err := http.ReadResponse(bufio.NewReader(tlsConn), req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusSwitchingProtocols {
		// Try to surface the agent error.
		apiResp := api.Response{}

		err = json.NewDecoder(resp.Body).Decode(&apiResp)
		if err == nil && apiResp.Error != "" {
			return nil, errors.New(apiResp.Error)
		}

		return nil, fmt.Errorf("Dialing failed: expected status code 101 got %d", resp.StatusCode)
	}

	if resp.Header.Get("Upgrade") != "tcp" {
		return nil, errors.New("Missing or unexpected Upgrade header in response")
	}

	return tlsConn, nil
}

// Console gets access to the instance's console.
func (d *qemu) Console(protocol string) (*os.File, chan error, error) {
	var path string
	switch protocol {
	case instance.ConsoleTypeConsole:
		path = d.consolePath()
	case instance.ConsoleTypeVGA:
		info := DriverStatuses()[instancetype.VM].Info
		_, spiceSupported := info.Features["spice"]
		if !spiceSupported {
			return nil, nil, fmt.Errorf("SPICE is not supported by the host")
		}

		path = d.spicePath()
	default:
		return nil, nil, fmt.Errorf("Unknown protocol %q", protocol)
	}

	// When activating the text-based console, swap the backend to be a socket for an interactive connection.
	if protocol == instance.ConsoleTypeConsole {
		// Look for existing connections and reset.
		conn, err := linux.DialUnix(path)
		if err == nil {
			_ = d.consoleSwapSocketWithRB()
			_ = conn.Close()

			// Allow for cleanup to complete on the existing connection.
			time.Sleep(time.Second)
		}

		err = d.consoleSwapRBWithSocket()
		if err != nil {
			_ = d.consoleSwapSocketWithRB()
			return nil, nil, fmt.Errorf("Failed to swap console ring buffer with socket: %w", err)
		}
	}

	// Disconnection notification.
	chDisconnect := make(chan error, 1)

	// Open the console socket.
	conn, err := linux.DialUnix(path)
	if err != nil {
		if protocol == instance.ConsoleTypeConsole {
			_ = d.consoleSwapSocketWithRB()
		}

		return nil, nil, fmt.Errorf("Connect to console socket %q: %w", path, err)
	}

	file, err := conn.File()
	if err != nil {
		if protocol == instance.ConsoleTypeConsole {
			_ = d.consoleSwapSocketWithRB()
		}

		return nil, nil, fmt.Errorf("Get socket file: %w", err)
	}

	_ = conn.Close()

	// Handle disconnections.
	go func() {
		<-chDisconnect
		_ = d.consoleSwapSocketWithRB()
	}()

	// Only emit a lifecycle event for the text console here. SPICE clients open one socket per channel
	// (display, cursor, inputs, ...) and would otherwise produce a flurry of instance-console events
	// for a single user session; the VGA emit is handled once per session by the console request handler.
	if protocol == instance.ConsoleTypeConsole {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceConsole.Event(d, logger.Ctx{"type": protocol}))
	}

	return file, chDisconnect, nil
}

// Exec a command inside the instance.
func (d *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
	reverter := revert.New()
	defer reverter.Fail()

	client, err := d.getAgentClient()
	if err != nil {
		return nil, err
	}

	agent, err := incus.ConnectIncusHTTP(nil, client)
	if err != nil {
		d.logger.Error("Failed to connect to the agent", logger.Ctx{"err": err})
		return nil, errors.New("Failed to connect to the agent")
	}

	reverter.Add(agent.Disconnect)

	dataDone := make(chan bool)
	controlSendCh := make(chan api.InstanceExecControl)
	controlResCh := make(chan error)

	// This is the signal control handler, it receives signals from lxc CLI and forwards them to the VM agent.
	controlHandler := func(control *websocket.Conn) {
		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
		defer logger.WarnOnError(func() error { return control.WriteMessage(websocket.CloseMessage, closeMsg) }, "Failed to write close message")

		for {
			select {
			case cmd := <-controlSendCh:
				controlResCh <- control.WriteJSON(cmd)
			case <-dataDone:
				return
			}
		}
	}

	args := incus.InstanceExecArgs{
		Stdin:    stdin,
		Stdout:   stdout,
		Stderr:   stderr,
		DataDone: dataDone,
		Control:  controlHandler,
	}

	// Always needed for VM exec, as even for non-websocket requests from the client we need to connect the
	// websockets for control and for capturing output to a file on the server.
	req.WaitForWS = true

	// Similarly, output recording is performed on the host rather than in the guest, so clear that bit from the request.
	req.RecordOutput = false

	op, err := agent.ExecInstance("", req, &args)
	if err != nil {
		return nil, err
	}

	instCmd := &qemuCmd{
		cmd:              op,
		attachedChildPid: 0, // Process is not running on the host.
		dataDone:         args.DataDone,
		cleanupFunc:      reverter.Clone().Fail, // Pass revert function clone as clean up function.
		controlSendCh:    controlSendCh,
		controlResCh:     controlResCh,
	}

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceExec.Event(d, logger.Ctx{"command": req.Command}))

	reverter.Success()

	return instCmd, nil
}

// RenderWithUsage renders the API response including disk usage.
func (d *qemu) RenderWithUsage() (any, any, error) {
	resp, etag, err := d.Render()
	if err != nil {
		return nil, nil, err
	}

	// Currently only snapshot data needs usage added.
	snapResp, ok := resp.(*api.InstanceSnapshot)
	if !ok {
		return resp, etag, nil
	}

	pool, err := d.getStoragePool()
	if err != nil {
		return nil, nil, err
	}

	// It is important that the snapshot not be mounted here as mounting a snapshot can trigger a very
	// expensive filesystem UUID regeneration, so we rely on the driver implementation to get the info
	// we are requesting as cheaply as possible.
	volumeState, err := pool.GetInstanceUsage(d)
	if err != nil {
		return resp, etag, nil
	}

	// A negative usage means the driver couldn't determine it, so leave the size unset.
	if volumeState.Used >= 0 {
		snapResp.Size = volumeState.Used
	}

	return snapResp, etag, nil
}

// Render returns info about the instance.
func (d *qemu) Render() (any, any, error) {
	profileNames := make([]string, 0, len(d.profiles))
	for _, profile := range d.profiles {
		profileNames = append(profileNames, profile.Name)
	}

	if d.IsSnapshot() {
		// Prepare the response.
		snapState := api.InstanceSnapshot{
			CreatedAt:       d.creationDate,
			Description:     d.description,
			ExpandedConfig:  d.expandedConfig,
			ExpandedDevices: d.expandedDevices.CloneNative(),
			LastUsedAt:      d.lastUsedDate,
			Name:            strings.SplitN(d.name, "/", 2)[1],
			Stateful:        d.stateful,
			Size:            -1, // Default to uninitialized/error state (0 means no CoW usage).
		}

		snapState.Architecture = d.architectureName
		snapState.Config = d.localConfig
		snapState.Devices = d.localDevices.CloneNative()
		snapState.Ephemeral = d.ephemeral
		snapState.Profiles = profileNames
		snapState.ExpiresAt = d.expiryDate

		return &snapState, d.ETag(), nil
	}

	// Prepare the response.
	statusCode := d.statusCode()
	instState := api.Instance{
		ExpandedConfig:  d.expandedConfig,
		ExpandedDevices: d.expandedDevices.CloneNative(),
		Name:            d.name,
		Status:          statusCode.String(),
		StatusCode:      statusCode,
		Location:        d.node,
		Type:            d.Type().String(),
	}

	instState.Description = d.description
	instState.Architecture = d.architectureName
	instState.Config = d.localConfig
	instState.CreatedAt = d.creationDate
	instState.Devices = d.localDevices.CloneNative()
	instState.Ephemeral = d.ephemeral
	instState.LastUsedAt = d.lastUsedDate
	instState.Profiles = profileNames
	instState.Stateful = d.stateful
	instState.Project = d.project.Name

	return &instState, d.ETag(), nil
}

// RenderFull returns all info about the instance.
func (d *qemu) RenderFull(hostInterfaces []net.Interface) (*api.InstanceFull, any, error) {
	if d.IsSnapshot() {
		return nil, nil, errors.New("RenderFull doesn't work with snapshots")
	}

	// Get the Instance struct.
	base, etag, err := d.Render()
	if err != nil {
		return nil, nil, err
	}

	// Convert to InstanceFull.
	vmState := api.InstanceFull{Instance: *base.(*api.Instance)}

	// Add the InstanceState.
	vmState.State, err = d.renderState(vmState.StatusCode)
	if err != nil {
		return nil, nil, err
	}

	// Add the InstanceSnapshots.
	snaps, err := d.Snapshots()
	if err != nil {
		return nil, nil, err
	}

	for _, snap := range snaps {
		render, _, err := snap.Render()
		if err != nil {
			return nil, nil, err
		}

		if vmState.Snapshots == nil {
			vmState.Snapshots = []api.InstanceSnapshot{}
		}

		vmState.Snapshots = append(vmState.Snapshots, *render.(*api.InstanceSnapshot))
	}

	// Add the InstanceBackups.
	backups, err := d.Backups()
	if err != nil {
		return nil, nil, err
	}

	for _, backup := range backups {
		render := backup.Render()

		if vmState.Backups == nil {
			vmState.Backups = []api.InstanceBackup{}
		}

		vmState.Backups = append(vmState.Backups, *render)
	}

	return &vmState, etag, nil
}

// renderState returns just state info about the instance.
func (d *qemu) renderState(statusCode api.StatusCode) (*api.InstanceState, error) {
	// Initialize the return struct.
	status := &api.InstanceState{
		Processes:  -1,
		Status:     statusCode.String(),
		StatusCode: statusCode,
	}

	// If VM is stopped or errored, we're done here.
	if d.isErrorStatusCode(statusCode) || !d.isRunningStatusCode(statusCode) {
		diskState, err := d.diskState()
		if err != nil && !errors.Is(err, storageDrivers.ErrNotSupported) {
			d.logger.Warn("Error getting disk usage", logger.Ctx{"err": err})
		}

		status.Disk = diskState

		return status, nil
	}

	// If possible, get the metrics from the agent.
	if d.agentMetricsEnabled() {
		agentStatus, err := d.agentGetState()
		if err != nil {
			if !errors.Is(err, errQemuAgentOffline) {
				d.logger.Warn("Could not get VM state from agent", logger.Ctx{"err": err})
			} else {
				monitor, err := d.qmpConnect()
				if err == nil && monitor.AgenStarted() {
					agentStatus = monitor.GetInstanceState()
				}
			}
		}

		if agentStatus != nil {
			status = agentStatus
		}
	}

	// Override VM state back to QEMU state.
	status.Status = statusCode.String()
	status.StatusCode = statusCode

	// Add the network details if missing.
	if len(status.Network) == 0 {
		networkState, err := d.getNetworkState()
		if err != nil {
			return nil, err
		}

		status.Network = networkState
	}

	// Add the memory details if missing.
	if status.Memory.Usage <= 0 {
		monitor, err := d.qmpConnect()
		if err != nil {
			d.logger.Warn("Error getting QEMU monitor", logger.Ctx{"err": err})
		}

		memoryMetrics, err := d.getQemuMemoryMetrics(monitor)
		if err != nil {
			d.logger.Warn("Error getting memory metrics", logger.Ctx{"err": err})
		}

		status.Memory.Total = int64(memoryMetrics.MemTotalBytes)
		status.Memory.Usage = int64(memoryMetrics.MemTotalBytes - memoryMetrics.MemAvailableBytes)
	}

	// Populate the disk information.
	diskState, err := d.diskState()
	if err != nil && !errors.Is(err, storageDrivers.ErrNotSupported) {
		d.logger.Warn("Error getting disk usage", logger.Ctx{"err": err})
	}

	status.Disk = diskState

	// Populate the CPU time allocation.
	limitsCPU, ok := d.expandedConfig["limits.cpu"]
	if ok {
		cpuCount, err := strconv.ParseInt(limitsCPU, 10, 64)
		if err != nil {
			status.CPU.AllocatedTime = cpuCount * 1_000_000_000
		}
	} else {
		status.CPU.AllocatedTime = qemudefault.CPUCores * 1_000_000_000
	}

	// Populate host_name for network devices.
	for k, m := range d.ExpandedDevices() {
		// We only care about nics.
		if m["type"] != "nic" {
			continue
		}

		// Get hwaddr from static or volatile config.
		hwaddr := m["hwaddr"]
		if hwaddr == "" {
			hwaddr = d.localConfig[fmt.Sprintf("volatile.%s.hwaddr", k)]
		}

		// We have to match on hwaddr as device name can be different from the configured device
		// name when reported from the agent inside the VM (due to the guest OS choosing name).
		for netName, netStatus := range status.Network {
			if netStatus.Hwaddr == hwaddr {
				if netStatus.HostName == "" {
					netStatus.HostName = d.localConfig[fmt.Sprintf("volatile.%s.host_name", k)]
					status.Network[netName] = netStatus
				}
			}
		}
	}

	// Populate the process information.
	pid, _ := d.pid()
	status.Pid = int64(pid)
	status.StartedAt, err = d.processStartedAt(d.InitPID())
	if err != nil {
		return status, err
	}

	return status, nil
}

// RenderState returns just state info about the instance.
func (d *qemu) RenderState(hostInterfaces []net.Interface) (*api.InstanceState, error) {
	return d.renderState(d.statusCode())
}

// diskState gets disk usage info.
func (d *qemu) diskState() (map[string]api.InstanceStateDisk, error) {
	pool, err := d.getStoragePool()
	if err != nil {
		return nil, err
	}

	// Get the root disk device config.
	rootDiskName, _, err := d.getRootDiskDevice()
	if err != nil {
		return nil, err
	}

	usage, err := pool.GetInstanceUsage(d)
	if err != nil {
		return nil, err
	}

	disk := map[string]api.InstanceStateDisk{}
	disk[rootDiskName] = api.InstanceStateDisk{
		Usage: usage.Used,
		Total: usage.Total,
	}

	return disk, nil
}

// agentGetState connects to the agent inside of the VM and does
// an API call to get the current state.
func (d *qemu) agentGetState() (*api.InstanceState, error) {
	client, err := d.getAgentClient()
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	agent, err := incus.ConnectIncusHTTPWithContext(ctx, nil, client)
	if err != nil {
		return nil, fmt.Errorf("Failed connecting to agent: %w", err)
	}

	defer agent.Disconnect()

	status, _, err := agent.GetInstanceState("")
	if err != nil {
		return nil, err
	}

	return status, nil
}

// IsRunning returns whether or not the instance is running.
func (d *qemu) IsRunning() bool {
	return d.isRunningStatusCode(d.statusCode())
}

// IsFrozen returns whether the instance frozen or not.
func (d *qemu) IsFrozen() bool {
	return d.statusCode() == api.Frozen
}

// CanMigrate returns whether the instance can be migrated.
func (d *qemu) CanMigrate() string {
	return d.canMigrate(d)
}

// LockExclusive attempts to get exclusive access to the instance's root volume.
func (d *qemu) LockExclusive() (*operationlock.InstanceOperation, error) {
	if d.IsRunning() {
		return nil, errors.New("Instance is running")
	}

	// Prevent concurrent operations the instance.
	op, err := operationlock.Create(d.Project().Name, d.Name(), d.op, operationlock.ActionCreate, false, false)
	if err != nil {
		return nil, err
	}

	return op, err
}

// DeviceEventHandler handles events occurring on the instance's devices.
func (d *qemu) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {
	if !d.IsRunning() || runConf == nil {
		return nil
	}

	// Handle uevents.
	for _, uevent := range runConf.Uevents {
		for _, event := range uevent {
			fields := strings.SplitN(event, "=", 2)

			if fields[0] != "ACTION" {
				continue
			}

			switch fields[1] {
			case "add":
				for _, usbDev := range runConf.USBDevice {
					// This ensures that the device is actually removed from QEMU before adding it again.
					// In most cases the device will already be removed, but it is possible that the
					// device still exists in QEMU before trying to add it again.
					// If a USB device is physically detached from a running VM while the server
					// itself is stopped, QEMU in theory will not delete the device.
					err := d.deviceDetachUSB(usbDev)
					if err != nil {
						return err
					}

					err = d.deviceAttachUSB(usbDev)
					if err != nil {
						return err
					}
				}
			case "remove":
				for _, usbDev := range runConf.USBDevice {
					err := d.deviceDetachUSB(usbDev)
					if err != nil {
						return err
					}
				}
			}
		}
	}

	// Handle disk reconfiguration.
	for _, mount := range runConf.Mounts {
		if mount.Limits == nil && mount.Size == 0 {
			continue
		}

		// Get the QMP monitor.
		m, err := d.qmpConnect()
		if err != nil {
			return err
		}

		// Figure out the QEMU device ID.
		devID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, linux.PathNameEncode(mount.DevName))

		if mount.Limits != nil {
			// Apply the limits.
			err = m.SetBlockThrottle(devID, int(mount.Limits.ReadBytes), int(mount.Limits.WriteBytes), int(mount.Limits.ReadIOps), int(mount.Limits.WriteIOps))
			if err != nil {
				return fmt.Errorf("Failed applying limits for disk device %q: %w", mount.DevName, err)
			}
		}

		if mount.Size > 0 {
			// Update the size.
			err = m.UpdateBlockSize(strings.SplitN(devID, "-", 2)[1], mount.Size)
			if err != nil {
				return fmt.Errorf("Failed updating disk size %q: %w", mount.DevName, err)
			}
		}
	}

	// Handle NIC reconfiguration.
	var devName string
	var connected bool
	for _, dev := range runConf.NetworkInterface {
		switch dev.Key {
		case "devName":
			devName = dev.Value
		case "connected":
			connected = util.IsTrueOrEmpty(dev.Value)
		}
	}

	if devName != "" {
		// Get the QMP monitor.
		m, err := d.qmpConnect()
		if err != nil {
			return err
		}

		// Figure out the QEMU device ID.
		devID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, linux.PathNameEncode(devName))

		err = m.SetNICLink(devID, connected)
		if err != nil {
			return fmt.Errorf("Failed setting NIC device link status: %w", err)
		}
	}

	return nil
}

// reservedVsockID returns true if the given vsockID equals 0, 1 or 2.
// Those are reserved and we cannot use them.
func (d *qemu) reservedVsockID(vsockID uint32) bool {
	return vsockID <= 2
}

// getVsockID returns the vsock Context ID for the VM.
func (d *qemu) getVsockID() (uint32, error) {
	existingVsockID, ok := d.localConfig["volatile.vsock_id"]
	if !ok {
		return 0, errors.New("Context ID not set in volatile.vsock_id")
	}

	vsockID, err := strconv.ParseUint(existingVsockID, 10, 32)
	if err != nil {
		return 0, fmt.Errorf("Failed to parse volatile.vsock_id: %q: %w", existingVsockID, err)
	}

	if d.reservedVsockID(uint32(vsockID)) {
		return 0, fmt.Errorf("Failed to use reserved vsock Context ID: %d", vsockID)
	}

	return uint32(vsockID), nil
}

// acquireVsockID tries to occupy the given vsock Context ID.
// If the ID is free it returns the corresponding file handle.
func (d *qemu) acquireVsockID(vsockID uint32) (*os.File, error) {
	reverter := revert.New()
	defer reverter.Fail()

	vsockF, err := os.OpenFile("/dev/vhost-vsock", os.O_RDWR, 0)
	if err != nil {
		return nil, fmt.Errorf("Failed to open vhost socket: %w", err)
	}

	reverter.Add(func() { _ = vsockF.Close() })

	// The vsock Context ID cannot be supplied as type uint32.
	vsockIDInt := uint64(vsockID)

	// Call the ioctl to set the context ID.
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, vsockF.Fd(), linux.IoctlVhostVsockSetGuestCid, uintptr(unsafe.Pointer(&vsockIDInt)))
	if errno != 0 {
		if !errors.Is(errno, unix.EADDRINUSE) {
			return nil, fmt.Errorf("Failed ioctl syscall to vhost socket: %q", errno.Error())
		}

		// vsock Context ID is already in use.
		return nil, nil
	}

	reverter.Success()

	return vsockF, nil
}

// acquireExistingVsockID tries to acquire an already existing vsock Context ID from volatile.
// It returns both the acquired ID and opened vsock file handle for QEMU.
func (d *qemu) acquireExistingVsockID() (uint32, *os.File, error) {
	vsockID, err := d.getVsockID()
	if err != nil {
		return 0, nil, err
	}

	// Check if the vsockID from last VM start is still not acquired in case the VM was stopped.
	f, err := d.acquireVsockID(vsockID)
	if err != nil {
		return 0, nil, err
	}

	return vsockID, f, nil
}

// nextVsockID tries to acquire the next free vsock Context ID for the VM.
// It returns both the acquired ID and opened vsock file handle for QEMU.
func (d *qemu) nextVsockID() (uint32, *os.File, error) {
	// Check if vsock ID from last VM start is present in volatile, then use that.
	// This allows a running VM to be recovered after DB record deletion and that an agent connection still works
	// after the VM's instance ID has changed.
	// Continue in case of error since the caller requires a valid vsockID in any case.
	vsockID, vsockF, _ := d.acquireExistingVsockID()
	if vsockID != 0 && vsockF != nil {
		return vsockID, vsockF, nil
	}

	// Ignore the error from before and start to acquire a new Context ID.
	instanceUUID, err := uuid.Parse(d.localConfig["volatile.uuid"])
	if err != nil {
		return 0, nil, fmt.Errorf("Failed to parse instance UUID from volatile.uuid: %w", err)
	}

	r, err := localUtil.GetStableRandomGenerator(instanceUUID.String())
	if err != nil {
		return 0, nil, fmt.Errorf("Failed generating stable random seed from instance UUID %q: %w", instanceUUID, err)
	}

	timeout := time.Now().Add(5 * time.Second)

	// Try to find a new Context ID.
	for {
		if time.Now().After(timeout) {
			return 0, nil, errors.New("Timeout exceeded whilst trying to acquire the next vsock Context ID")
		}

		candidateVsockID := r.Uint32()

		if d.reservedVsockID(candidateVsockID) {
			continue
		}

		vsockF, err := d.acquireVsockID(candidateVsockID)
		if err != nil {
			return 0, nil, err
		}

		if vsockF != nil {
			return candidateVsockID, vsockF, nil
		}
	}
}

// InitPID returns the instance's current process ID.
func (d *qemu) InitPID() int {
	pid, _ := d.pid()
	return pid
}

func (d *qemu) statusCode() api.StatusCode {
	// Shortcut to avoid spamming QMP during ongoing operations.
	op := operationlock.Get(d.Project().Name, d.Name())
	if op != nil {
		if op.Action() == operationlock.ActionStart {
			return api.Stopped
		}

		if op.Action() == operationlock.ActionStop {
			if util.IsTrue(d.LocalConfig()["volatile.last_state.ready"]) {
				return api.Ready
			}

			return api.Running
		}
	}

	// Connect to the monitor.
	monitor, err := d.qmpConnect()
	if err != nil {
		// If cannot connect to monitor, but qemu process in pid file still exists, then likely qemu
		// is unresponsive and this instance is in an error state.
		pid, _ := d.pid()
		if pid > 0 {
			return api.Error
		}

		// If we fail to connect, chances are the VM isn't running.
		return api.Stopped
	}

	status, err := monitor.Status()
	if err != nil {
		if errors.Is(err, qmp.ErrMonitorDisconnect) {
			// If cannot connect to monitor, but qemu process in pid file still exists, then likely
			// qemu is unresponsive and this instance is in an error state.
			pid, _ := d.pid()
			if pid > 0 {
				return api.Error
			}

			return api.Stopped
		}

		return api.Error
	}

	switch status {
	case "prelaunch", "running":
		if status == "running" && util.IsTrue(d.LocalConfig()["volatile.last_state.ready"]) {
			return api.Ready
		}

		return api.Running
	case "inmigrate", "postmigrate", "finish-migrate", "save-vm", "suspended", "paused":
		return api.Frozen
	default:
		return api.Error
	}
}

// State returns the instance's state code.
func (d *qemu) State() string {
	return strings.ToUpper(d.statusCode().String())
}

// EarlyLogFilePath returns the instance's early log path.
func (d *qemu) EarlyLogFilePath() string {
	return filepath.Join(d.LogPath(), "qemu.early.log")
}

// LogFilePath returns the instance's log path.
func (d *qemu) LogFilePath() string {
	return filepath.Join(d.LogPath(), "qemu.log")
}

// QMPLogFilePath returns the instance's QMP log path.
func (d *qemu) QMPLogFilePath() string {
	return filepath.Join(d.LogPath(), "qemu.qmp.log")
}

// FillNetworkDevice takes a nic or infiniband device type and enriches it with automatically
// generated name and hwaddr properties if these are missing from the device.
func (d *qemu) FillNetworkDevice(name string, m deviceConfig.Device) (deviceConfig.Device, error) {
	var err error

	newDevice := m.Clone()

	nicType, err := nictype.NICType(d.state, d.Project().Name, m)
	if err != nil {
		return nil, err
	}

	isPhysicalWithBridge := device.IsPhysicalNICWithBridge(d.state, d.Project().Name, m)

	// Fill in the MAC address.
	if (!slices.Contains([]string{"physical", "ipvlan"}, nicType) || isPhysicalWithBridge) && m["hwaddr"] == "" {
		configKey := fmt.Sprintf("volatile.%s.hwaddr", name)
		volatileHwaddr := d.localConfig[configKey]
		if volatileHwaddr == "" {
			// Generate a new MAC address.
			volatileHwaddr, err = instance.DeviceNextInterfaceHWAddr(d.MACPattern())
			if err != nil || volatileHwaddr == "" {
				return nil, fmt.Errorf("Failed generating %q: %w", configKey, err)
			}

			// Update the database and update volatileHwaddr with stored value.
			volatileHwaddr, err = d.insertConfigkey(configKey, volatileHwaddr)
			if err != nil {
				return nil, fmt.Errorf("Failed storing generated config key %q: %w", configKey, err)
			}

			// Set stored value into current instance config.
			d.localConfig[configKey] = volatileHwaddr
			d.expandedConfig[configKey] = volatileHwaddr
		}

		if volatileHwaddr == "" {
			return nil, fmt.Errorf("Failed getting %q", configKey)
		}

		newDevice["hwaddr"] = volatileHwaddr
	}

	return newDevice, nil
}

// UpdateBackupFile writes the instance's backup.yaml file to storage.
func (d *qemu) UpdateBackupFile() error {
	// Prevent concurrent updates to the backup file.
	unlock, err := d.updateBackupFileLock(context.Background())
	if err != nil {
		return err
	}

	defer unlock()

	// Write the current instance state to backup file.
	pool, err := d.getStoragePool()
	if err != nil {
		return err
	}

	return pool.UpdateInstanceBackupFile(d, true, nil)
}

func (d *qemu) devIncusEventSend(eventType string, eventMessage map[string]any) error {
	event := jmap.Map{}
	event["type"] = eventType
	event["timestamp"] = time.Now()
	event["metadata"] = eventMessage

	client, err := d.getAgentClient()
	if err != nil {
		// Don't fail if the VM simply doesn't have an agent.
		if errors.Is(err, errQemuAgentOffline) {
			return nil
		}

		return err
	}

	agentArgs := &incus.ConnectionArgs{
		SkipGetEvents: true,
		SkipGetServer: true,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	agent, err := incus.ConnectIncusHTTPWithContext(ctx, agentArgs, client)
	if err != nil {
		d.logger.Error("Failed to connect to the agent", logger.Ctx{"err": err})
		return errors.New("Failed to connect to the agent")
	}

	defer agent.Disconnect()

	_, _, err = agent.RawQuery("POST", "/1.0/events", &event, "")
	if err != nil {
		return err
	}

	return nil
}

// Info returns "qemu" and the currently loaded qemu version.
func (d *qemu) Info() instance.Info {
	data := instance.Info{
		Name:     "qemu",
		Features: make(map[string]any),
		Type:     instancetype.VM,
		Error:    errors.New("Unknown error"),
	}

	if !util.PathExists("/dev/kvm") {
		data.Error = errors.New("KVM support is missing (no /dev/kvm)")
		return data
	}

	err := linux.LoadModule("vhost_vsock")
	if err != nil {
		data.Error = errors.New("vhost_vsock kernel module not loaded")
		return data
	}

	if !util.PathExists("/dev/vsock") {
		data.Error = errors.New("Vsock support is missing (no /dev/vsock)")
		return data
	}

	hostArch, err := osarch.ArchitectureGetLocalID()
	if err != nil {
		logger.Errorf("Failed getting CPU architecture during QEMU initialization: %v", err)
		data.Error = errors.New("Failed getting CPU architecture")
		return data
	}

	qemuPath, _, err := d.qemuArchConfig(hostArch)
	if err != nil {
		data.Error = fmt.Errorf("QEMU command not available: %v", err)
		return data
	}

	out, err := exec.Command(qemuPath, "--version").Output()
	if err != nil {
		logger.Errorf("Failed getting version during QEMU initialization: %v", err)
		data.Error = errors.New("Failed getting QEMU version")
		return data
	}

	qemuOutput := strings.Fields(string(out))
	if len(qemuOutput) >= 4 {
		qemuVersion := strings.Fields(string(out))[3]
		data.Version = qemuVersion
	} else {
		data.Version = "unknown" // Not necessarily an error that should prevent us using driver.
	}

	data.Features, err = d.checkFeatures(hostArch, qemuPath)
	if err != nil {
		logger.Errorf("Unable to run feature checks during QEMU initialization: %v", err)
		data.Error = errors.New("QEMU failed to run feature checks")
		return data
	}

	data.Error = nil

	return data
}

func (d *qemu) checkFeatures(hostArch int, qemuPath string) (map[string]any, error) {
	monitorPath, err := os.CreateTemp("", "")
	if err != nil {
		return nil, err
	}

	defer logger.WarnOnError(func() error { return os.Remove(monitorPath.Name()) }, "Failed to remove monitor file")

	qemuArgs := []string{
		qemuPath,
		"-S", // Do not start virtualisation.
		"-nographic",
		"-nodefaults",
		"-no-user-config",
		"-chardev", fmt.Sprintf("socket,id=monitor,path=%s,server=on,wait=off", qemuEscapeCmdline(monitorPath.Name())),
		"-mon", "chardev=monitor,mode=control",
		"-machine", qemuMachineType(hostArch),
	}

	if hostArch == osarch.ARCH_64BIT_INTEL_X86 {
		// On Intel, use KVM acceleration as it's needed for SEV detection.
		// This also happens to be less resource intensive but can't
		// trivially be performed on all architectures without extra care about the
		// machine type.
		qemuArgs = append(qemuArgs, "-accel", "kvm")
	}

	if d.architectureSupportsUEFI(hostArch) {
		// Try to locate a UEFI firmware.
		var efiPath string

		firmwares, err := edk2.GetArchitectureFirmwarePairsForUsage(hostArch, edk2.GENERIC)
		if err != nil {
			return nil, err
		}

		for _, firmwarePair := range firmwares {
			if util.PathExists(firmwarePair.Code) {
				efiPath = firmwarePair.Code
				break
			}
		}

		if efiPath == "" {
			return nil, errors.New("Unable to locate a UEFI firmware")
		}

		qemuArgs = append(qemuArgs, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", qemuEscapeCmdline(efiPath)))
	}

	var stderr bytes.Buffer

	checkFeature := exec.Cmd{
		Path:   qemuPath,
		Args:   qemuArgs,
		Stderr: &stderr,
	}

	err = checkFeature.Start()
	if err != nil {
		// QEMU not operational. VM support missing.
		return nil, fmt.Errorf("Failed starting QEMU: %w", err)
	}

	defer logger.WarnOnError(checkFeature.Process.Kill, "Failed to kill QEMU process")

	// Start go routine that waits for QEMU to exit and captures the exit error (if any).
	errWaitCh := make(chan error, 1)
	go func() {
		errWaitCh <- checkFeature.Wait()
	}()

	// Start go routine that tries to connect to QEMU's QMP socket in a loop (giving QEMU a chance to open it).
	ctx, cancelMonitorConnect := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancelMonitorConnect()

	errMonitorCh := make(chan error, 1)
	var monitor *qmp.Monitor
	go func() {
		var err error

		// Try and connect to QMP socket until cancelled.
		for {
			monitor, err = qmp.Connect(monitorPath.Name(), qemuSerialChardevName, nil, "", d.detachDisk)
			// QMP successfully connected or we have been cancelled.
			if err == nil || ctx.Err() != nil {
				break
			}

			time.Sleep(50 * time.Millisecond)
		}

		// Return last QMP connection error.
		errMonitorCh <- err
	}()

	// Wait for premature QEMU exit or QMP to connect or timeout.
	select {
	case errMonitor := <-errMonitorCh:
		// A non-nil error here means that QMP failed to connect before timing out.
		// The last connection error is returned.
		// A nil error means QMP successfully connected and we can continue.
		if errMonitor != nil {
			return nil, fmt.Errorf("QEMU monitor connect error: %w", errMonitor)
		}

	case errWait := <-errWaitCh:
		// Any sort of premature exit, even a non-error one is problematic here, and should not occur.
		return nil, fmt.Errorf("QEMU premature exit: %w (%v)", errWait, strings.TrimSpace(stderr.String()))
	}

	defer monitor.Disconnect()

	features := make(map[string]any)

	blockDevPath, err := os.CreateTemp("", "")
	if err != nil {
		return nil, err
	}

	defer logger.WarnOnError(func() error { return os.Remove(blockDevPath.Name()) }, "Failed to remove block device file")

	// Check io_uring feature.
	blockDev := map[string]any{
		"node-name": d.blockNodeName("feature-check"),
		"driver":    "file",
		"filename":  blockDevPath.Name(),
		"aio":       "io_uring",
	}

	err = monitor.AddBlockDevice(blockDev, nil, false)
	if err != nil {
		logger.Debug("Failed adding block device during VM feature check", logger.Ctx{"err": err})
	} else {
		features["io_uring"] = struct{}{}
	}

	// Check CPU hotplug feature.
	_, err = monitor.QueryHotpluggableCPUs()
	if err != nil {
		logger.Debug("Failed querying hotpluggable CPUs during VM feature check", logger.Ctx{"err": err})
	} else {
		features["cpu_hotplug"] = struct{}{}
	}

	// Check AMD SEV features (only for x86 architecture)
	if hostArch == osarch.ARCH_64BIT_INTEL_X86 {
		cmdline, err := os.ReadFile("/proc/cmdline")
		if err != nil {
			return nil, err
		}

		parts := strings.Split(string(cmdline), " ")

		// Check if SME is enabled in the kernel command line.  // codespell:ignore sme
		if slices.Contains(parts, "mem_encrypt=on") || util.PathExists("/dev/sev") {
			features["sme"] = struct{}{} // codespell:ignore sme
		}

		// Check if SEV/SEV-ES are enabled
		sev, err := os.ReadFile("/sys/module/kvm_amd/parameters/sev")
		if err != nil && !errors.Is(err, fs.ErrNotExist) {
			return nil, err
		} else if strings.TrimSpace(string(sev)) == "Y" {
			// Host supports SEV, check if QEMU supports it as well.
			capabilities, err := monitor.SEVCapabilities()
			if err != nil {
				logger.Debug("Failed querying SEV capability during VM feature check", logger.Ctx{"err": err})
			} else {
				features["sev"] = capabilities

				// If SEV is enabled on host and supported by QEMU,
				// check if the SEV-ES extension is enabled.
				sevES, err := os.ReadFile("/sys/module/kvm_amd/parameters/sev_es")
				if err != nil {
					logger.Debug("Failed querying SEV-ES capability during VM feature check", logger.Ctx{"err": err})
				} else if strings.TrimSpace(string(sevES)) == "Y" {
					features["sev-es"] = struct{}{}
				}
			}
		}
	}

	// Check if vhost-net accelerator (for NIC CPU offloading) is available.
	if util.PathExists("/dev/vhost-net") {
		features["vhost_net"] = struct{}{}
	}

	// Check if SPICE is compiled into QEMU.
	err = monitor.QuerySpice()
	if err != nil {
		logger.Debug("Failed querying SPICE during VM feature check", logger.Ctx{"err": err})
	} else {
		features["spice"] = struct{}{}
	}

	// Check if virtio-9p-pci is compiled into QEMU.
	err = monitor.Query9pDevice()
	if err != nil {
		logger.Debug("Failed querying virtio-9p-pci during VM feature check", logger.Ctx{"err": err})
	} else {
		features["plan9"] = struct{}{}
	}

	// Check if virtio-sound-pci is compiled into QEMU.
	err = monitor.QueryVirtioSoundDevice()
	if err != nil {
		logger.Debug("Failed querying virtio-sound-pci during VM feature check", logger.Ctx{"err": err})
	} else {
		features["virtio-sound"] = struct{}{}
	}

	// Check if virtio-vga is compiled into QEMU.
	err = monitor.QueryVirtioVGADevice()
	if err != nil {
		logger.Debug("Failed querying virtio-vga during VM feature check", logger.Ctx{"err": err})
	} else {
		features["virtio-vga"] = struct{}{}
	}

	// Check if running nested.
	cpus, err := resources.GetCPU()
	if err != nil {
		return nil, err
	}

	nested := false
	for _, socket := range cpus.Sockets {
		for _, core := range socket.Cores {
			if slices.Contains(core.Flags, "hypervisor") {
				nested = true
			}
		}
	}

	if nested {
		features["nested"] = struct{}{}
	}

	// Get the host CPU model (x86_64 only for now).
	if hostArch == osarch.ARCH_64BIT_INTEL_X86 {
		model, err := monitor.QueryCPUModel("kvm64")
		if err != nil {
			// Fallback to qemu64 if kvm64 is missing (RHEL).
			model, err = monitor.QueryCPUModel("qemu64")
			if err != nil {
				return nil, err
			}
		}

		cpuFlags := map[string]bool{}
		for k, v := range model.Flags {
			value, ok := v.(bool)
			if !ok {
				continue
			}

			cpuFlags[k] = value
		}

		features["flags"] = cpuFlags
	}

	return features, nil
}

// version returns the QEMU version.
func (d *qemu) version() (*version.DottedVersion, error) {
	info := DriverStatuses()[instancetype.VM].Info
	qemuVer, err := version.NewDottedVersion(info.Version)
	if err != nil {
		return nil, fmt.Errorf("Failed parsing QEMU version: %w", err)
	}

	return qemuVer, nil
}

// Metrics returns the metrics set for the instance.
func (d *qemu) Metrics(hostInterfaces []net.Interface) (*metrics.MetricSet, error) {
	if !d.IsRunning() {
		return nil, ErrInstanceIsStopped
	}

	if d.agentMetricsEnabled() {
		agentMetrics, err := d.getAgentMetrics()
		if err != nil {
			if !errors.Is(err, errQemuAgentOffline) {
				d.logger.Warn("Could not get VM metrics from agent", logger.Ctx{"err": err})
			}

			// Fallback data if agent is not reachable.
			return d.getQemuMetrics()
		}

		return agentMetrics, nil
	}

	return d.getQemuMetrics()
}

func (d *qemu) getAgentMetrics() (*metrics.MetricSet, error) {
	client, err := d.getAgentClient()
	if err != nil {
		return nil, err
	}

	agentArgs := &incus.ConnectionArgs{
		SkipGetEvents: true,
		SkipGetServer: true,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	agent, err := incus.ConnectIncusHTTPWithContext(ctx, agentArgs, client)
	if err != nil {
		d.logger.Error("Failed to connect to the agent", logger.Ctx{"project": d.Project().Name, "instance": d.Name(), "err": err})
		return nil, errors.New("Failed to connect to the agent")
	}

	defer agent.Disconnect()

	resp, _, err := agent.RawQuery("GET", "/1.0/metrics", nil, "")
	if err != nil {
		return nil, err
	}

	var m metrics.Metrics

	err = json.Unmarshal(resp.Metadata, &m)
	if err != nil {
		return nil, err
	}

	metricSet, err := metrics.MetricSetFromAPI(&m, map[string]string{"project": d.project.Name, "name": d.name, "type": instancetype.VM.String()})
	if err != nil {
		return nil, err
	}

	return metricSet, nil
}

func (d *qemu) getNetworkState() (map[string]api.InstanceStateNetwork, error) {
	networks := map[string]api.InstanceStateNetwork{}
	for k, m := range d.ExpandedDevices() {
		if m["type"] != "nic" {
			continue
		}

		dev, err := d.deviceLoad(d, k, m, false)
		if err != nil {
			if errors.Is(err, device.ErrUnsupportedDevType) {
				continue // Skip unsupported device (allows for mixed instance type profiles).
			}

			d.logger.Warn("Failed state validation for device", logger.Ctx{"device": k, "err": err})
			continue
		}

		// Only some NIC types support fallback state mechanisms when there is no agent.
		nic, ok := dev.(device.NICState)
		if !ok {
			continue
		}

		nicState, err := nic.State()
		if err != nil {
			return nil, fmt.Errorf("Failed getting NIC state for %q: %w", k, err)
		}

		if nicState != nil {
			networks[k] = *nicState
		}
	}

	return networks, nil
}

func (d *qemu) agentMetricsEnabled() bool {
	return util.IsTrueOrEmpty(d.expandedConfig["security.agent.metrics"])
}

func (d *qemu) deviceAttachUSB(usbConf deviceConfig.USBDeviceItem) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	monHook, err := d.addUSBDeviceConfig(usbConf)
	if err != nil {
		return err
	}

	err = monHook(monitor)
	if err != nil {
		return err
	}

	return nil
}

func (d *qemu) deviceDetachUSB(usbDev deviceConfig.USBDeviceItem) error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, usbDev.DeviceName)

	err = monitor.RemoveDevice(deviceID)
	if err != nil && !api.StatusErrorCheck(err, http.StatusNotFound) {
		return fmt.Errorf("Failed removing device: %w", err)
	}

	err = monitor.RemoveFDFromFDSet(deviceID)
	if err != nil {
		return fmt.Errorf("Failed removing FD set: %w", err)
	}

	return nil
}

// Block node names may only be up to 31 characters long, so use a hash if longer.
func (d *qemu) blockNodeName(name string) string {
	// Apply the prefix.
	return fmt.Sprintf("%s%s", qemuBlockDevIDPrefix, hashValue(name, 25))
}

// Mount tag names may only be up to 31 or 36 characters long, so use a hash if longer.
func (d *qemu) mountTagName(name string, maxLength int) string {
	// Apply the prefix.
	return fmt.Sprintf("%s%s", qemuMountTagPrefix, hashValue(name, maxLength))
}

func (d *qemu) setCPUs(monitor *qmp.Monitor, count int) error {
	if count == 0 {
		return nil
	}

	// Check if the agent is running.
	if monitor == nil {
		var err error

		monitor, err = d.qmpConnect()
		if err != nil {
			return err
		}
	}

	cpus, err := monitor.QueryHotpluggableCPUs()
	if err != nil {
		return fmt.Errorf("Failed to query hotpluggable CPUs: %w", err)
	}

	var availableCPUs []qmp.HotpluggableCPU
	var hotpluggedCPUs []qmp.HotpluggableCPU

	// Count the available and hotplugged CPUs.
	for _, cpu := range cpus {
		// If qom-path is unset, the CPU is available.
		if cpu.QOMPath == "" {
			availableCPUs = append(availableCPUs, cpu)
		} else if strings.HasPrefix(cpu.QOMPath, "/machine/peripheral") {
			hotpluggedCPUs = append(hotpluggedCPUs, cpu)
		}
	}

	// The reserved CPUs includes both the hotplugged CPUs as well as the fixed one.
	totalReservedCPUs := len(hotpluggedCPUs) + 1

	// Nothing to do as the count matches the already reserved CPUs.
	if count == totalReservedCPUs {
		return nil
	}

	reverter := revert.New()
	defer reverter.Fail()

	// More CPUs requested.
	if count > totalReservedCPUs {
		// Cannot allocate more CPUs than the system provides.
		if count > len(cpus) {
			return fmt.Errorf("Requested CPU count of %d exceeds instance current maximum of %d, restart required", count, len(cpus))
		}

		// This shouldn't trigger, but if it does, don't panic.
		if count-totalReservedCPUs > len(availableCPUs) {
			return errors.New("Unable to allocate more CPUs, not enough hotpluggable CPUs available")
		}

		// Only allocate the difference in CPUs.
		for i := range count - totalReservedCPUs {
			cpu := availableCPUs[i]

			devID := fmt.Sprintf("cpu%d%d%d", cpu.Props.SocketID, cpu.Props.CoreID, cpu.Props.ThreadID)

			qemuDev := map[string]any{
				"id":      devID,
				"driver":  cpu.Type,
				"core-id": cpu.Props.CoreID,
			}

			// No such thing as sockets and threads on s390x.
			if d.architecture != osarch.ARCH_64BIT_S390_BIG_ENDIAN {
				qemuDev["socket-id"] = cpu.Props.SocketID
				qemuDev["thread-id"] = cpu.Props.ThreadID
			}

			err := monitor.AddDevice(qemuDev)
			if err != nil {
				return fmt.Errorf("Failed to add device: %w", err)
			}

			reverter.Add(func() {
				err := monitor.RemoveDevice(devID)
				d.logger.Warn("Failed to remove CPU device", logger.Ctx{"err": err})
			})
		}
	} else {
		if totalReservedCPUs-count > len(hotpluggedCPUs) {
			// This shouldn't trigger, but if it does, don't panic.
			return errors.New("Unable to remove CPUs, not enough hotpluggable CPUs available")
		}

		// Less CPUs requested.
		for i := range totalReservedCPUs - count {
			cpu := hotpluggedCPUs[i]

			fields := strings.Split(cpu.QOMPath, "/")
			devID := fields[len(fields)-1]

			err := monitor.RemoveDevice(devID)
			if err != nil {
				return fmt.Errorf("Failed to remove CPU: %w", err)
			}

			reverter.Add(func() {
				err := monitor.AddDevice(map[string]any{
					"id":        devID,
					"driver":    cpu.Type,
					"socket-id": cpu.Props.SocketID,
					"core-id":   cpu.Props.CoreID,
					"thread-id": cpu.Props.ThreadID,
				})
				d.logger.Warn("Failed to add CPU device", logger.Ctx{"err": err})
			})
		}

		// QEMU doesn't immediately remove the thread from the vCPU list.
		// Wait a second to allow the thread to fully exit and disappear from the vCPU list.
		time.Sleep(time.Second)
	}

	reverter.Success()

	// Run post-hotplug tasks.
	err = d.postCPUHotplug(monitor)
	if err != nil {
		return err
	}

	return nil
}

// resetSupportThreadsAffinity resets the CPU affinity of all non-vCPU QEMU threads,
// releasing them from the confinement applied during startup.
func (d *qemu) resetSupportThreadsAffinity(monitor *qmp.Monitor, pid int) error {
	// Get the vCPU PID list.
	vcpuPIDs, err := monitor.GetCPUs()
	if err != nil {
		return err
	}

	// Use our own affinity as the reset target.
	set := unix.CPUSet{}
	err = unix.SchedGetaffinity(0, &set)
	if err != nil {
		return err
	}

	// Reset every non-vCPU thread.
	entries, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
	if err != nil {
		return err
	}

	for _, entry := range entries {
		tid, err := strconv.Atoi(entry.Name())
		if err != nil {
			continue
		}

		if slices.Contains(vcpuPIDs, tid) {
			continue
		}

		err = unix.SchedSetaffinity(tid, &set)
		if err != nil && !errors.Is(err, unix.ESRCH) {
			return err
		}
	}

	return nil
}

func (d *qemu) architectureSupportsCPUHotplug() bool {
	// Check supported features.
	info := DriverStatuses()[instancetype.VM].Info
	_, found := info.Features["cpu_hotplug"]
	return found
}

func (d *qemu) postCPUHotplug(monitor *qmp.Monitor) error {
	// Get the vCPU PID list.
	pids, err := monitor.GetCPUs()
	if err != nil {
		return err
	}

	// Handle NUMA node restrictions.
	numaNodes := d.expandedConfig["limits.cpu.nodes"]
	if numaNodes != "" {
		if numaNodes == "balanced" {
			numaNodes = d.expandedConfig["volatile.cpu.nodes"]
		}

		// Parse the NUMA restriction.
		numaNodeSet, err := resources.ParseNumaNodeSet(numaNodes)
		if err != nil {
			return err
		}

		// Get the CPU topology.
		cpusTopology, err := resources.GetCPU()
		if err != nil {
			return err
		}

		// Get the isolated CPU ids.
		isolatedCpusInt := resources.GetCPUIsolated()

		// Build a map of NUMA node to CPU threads.
		numaNodeToCPU := make(map[int64][]int64)
		for _, cpu := range cpusTopology.Sockets {
			for _, core := range cpu.Cores {
				for _, thread := range core.Threads {
					// Skip any isolated CPU thread.
					if slices.Contains(isolatedCpusInt, thread.ID) {
						continue
					}

					numaNodeToCPU[int64(thread.NUMANode)] = append(numaNodeToCPU[int64(thread.NUMANode)], thread.ID)
				}
			}
		}

		// Figure out the list of CPU threads for the NUMA node(s).
		set := unix.CPUSet{}
		for _, numaNode := range numaNodeSet {
			for _, id := range numaNodeToCPU[numaNode] {
				set.Set(int(id))
			}
		}

		// Apply the restriction.
		for _, pid := range pids {
			// Apply the pin.
			err := unix.SchedSetaffinity(pid, &set)
			if err != nil {
				return err
			}
		}
	}

	// Create a core scheduling group.
	err = d.setCoreSched(pids)
	if err != nil {
		return fmt.Errorf("Failed to allocate new core scheduling domain for vCPU threads: %w", err)
	}

	return nil
}

// ConsoleLog returns all output sent to the instance's console's ring buffer since startup.
func (d *qemu) ConsoleLog() (string, error) {
	// Setup a new operation.
	op, err := operationlock.CreateWaitGet(d.Project().Name, d.Name(), d.op, operationlock.ActionConsoleRetrieve, []operationlock.Action{operationlock.ActionRestart, operationlock.ActionRestore, operationlock.ActionMigrate}, false, true)
	if err != nil {
		return "", err
	}

	// Only mark the operation as done if only processing the console retrieval.
	if op.Action() == operationlock.ActionConsoleRetrieve {
		defer op.Done(nil)
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return "", err
	}

	logString, err := monitor.RingbufRead("console")
	if err != nil {
		// If a VM was started by an older version of Incus which was then upgraded, its
		// console device won't be a ring buffer. We don't want to cause an error in this
		// case, so just return an empty string.
		if errors.Is(err, qmp.ErrNotARingbuf) {
			return "", nil
		}

		return "", err
	}

	// If we got data back, append it to the log file for this instance.
	if logString != "" {
		logFile, err := os.OpenFile(d.ConsoleBufferLogPath(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
		if err != nil {
			return "", err
		}

		defer logger.WarnOnError(logFile.Close, "Failed to close log file")

		_, err = logFile.WriteString(logString)
		if err != nil {
			return "", err
		}
	}

	// Read and return the complete log for this instance.
	fullLog, err := os.ReadFile(d.ConsoleBufferLogPath())
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			// If there's no log file yet, such as right at VM creation, return an empty string.
			return "", nil
		}

		return "", err
	}

	return string(fullLog), nil
}

// consoleSwapRBWithSocket swaps the qemu backend for the instance's console to a unix socket.
func (d *qemu) consoleSwapRBWithSocket() error {
	// This will wipe out anything in the existing ring buffer; save any buffered data to log file first.
	_, err := d.ConsoleLog()
	if err != nil {
		return err
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	// Create the unix socket here, which will be passed via file descriptor to qemu.
	d.consoleSocket, err = linux.ListenUnix(d.consolePath())
	if err != nil {
		return err
	}

	d.consoleSocketFile, err = d.consoleSocket.File()
	if err != nil {
		_ = d.consoleSocket.Close()
		_ = os.Remove(d.consolePath())
		return err
	}

	return monitor.ChardevChange("console", qmp.ChardevChangeInfo{Type: "socket", FDName: "consoleSocket", File: d.consoleSocketFile})
}

// consoleSwapSocketWithRB swaps the qemu backend for the instance's console to a ring buffer.
func (d *qemu) consoleSwapSocketWithRB() error {
	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	defer func() {
		// Clean up the old socket.
		_ = d.consoleSocketFile.Close()
		_ = d.consoleSocket.Close()
		_ = os.Remove(d.consolePath())
	}()

	return monitor.ChardevChange("console", qmp.ChardevChangeInfo{Type: "ringbuf"})
}

// ConsoleScreenshot returns a screenshot of the current VGA console in PNG format.
func (d *qemu) ConsoleScreenshot(screenshotFile *os.File) error {
	if !d.IsRunning() {
		return errors.New("Instance is not running")
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	err = screenshotFile.Chown(int(d.state.OS.UnprivUID), -1)
	if err != nil {
		return fmt.Errorf("Failed to chown screenshot path: %w", err)
	}

	// Take the screenshot.
	err = monitor.Screendump(screenshotFile.Name())
	if err != nil {
		return fmt.Errorf("Failed taking screenshot: %w", err)
	}

	return nil
}

// ReloadDevice triggers an empty Update call to the underlying device.
func (d *qemu) ReloadDevice(devName string) error {
	dev, err := d.deviceLoad(d, devName, d.expandedDevices[devName], false)
	if err != nil {
		return err
	}

	return dev.Update(d.expandedDevices, true)
}

// DumpGuestMemory dumps the guest memory to a file in the specified format.
func (d *qemu) DumpGuestMemory(w *os.File, format string) error {
	if !d.IsRunning() {
		return errors.New("Instance is not running")
	}

	// Check if the agent is running.
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	defer monitor.Disconnect()

	// Dump the guest memory.
	err = monitor.SendFile("memory-dump", w)
	if err != nil {
		return err
	}

	err = monitor.DumpGuestMemory("memory-dump", format)
	if err != nil {
		return err
	}

	// Close the writer.
	err = w.Close()
	if err != nil {
		return err
	}

	return nil
}

// CanLiveMigrate returns whether the VM is live-migratable.
func (d *qemu) CanLiveMigrate() bool {
	// Refuse migration if not enabled.
	if !util.IsTrue(d.expandedConfig["migration.stateful"]) {
		return false
	}

	// Additional checks when the VM is running.
	if d.IsRunning() {
		// Check if it may have been enabled through a later profile (and so still not supported).
		bs, err := d.getBootState()
		if err != nil {
			return false
		}

		// Ideally we'd just check for Version > 0 but that would prevent
		// all migrations from older Incus versions that predate the introduction
		// of the boot state recording. So instead rely on the machine type
		// recording which is going to be present on those older versions too.
		if bs.MachineType == "" {
			return false
		}
	}

	return true
}

// GuestOS returns the guest OS. In this driver, we consider anything unknown to be Linux.
func (d *qemu) GuestOS() osinfo.OSType {
	osType, _ := osinfo.DetermineOS(strings.ToLower(d.expandedConfig["image.os"]))

	return osType
}

// CreateQcow2Snapshot creates a qcow2 snapshot for a running instance.
func (d *qemu) CreateQcow2Snapshot(devPath string, devName string, snapshotName string, backingFilename string, stateful bool) error {
	reverter := revert.New()
	defer reverter.Fail()

	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	f, err := os.OpenFile(devPath, unix.O_RDWR, 0)
	if err != nil {
		return fmt.Errorf("Failed opening file descriptor for disk device %s: %w", devPath, err)
	}

	defer logger.WarnOnError(f.Close, "Failed to close file")

	devName = d.blockNodeName(linux.PathNameEncode(devName))

	// Select all block devices related to a qcow2 backing chain.
	blockDevs, err := d.fetchBlockDeviceChain(monitor, devName)
	if err != nil {
		return err
	}

	// Fetch the current maximum overlay index.
	overlayNodeIndex := currentQcow2OverlayIndex(blockDevs, devName)
	nextOverlayName := fmt.Sprintf("%s_overlay%d", devName, overlayNodeIndex+1)

	currentRootNode := blockDevs[len(blockDevs)-1]

	info, err := monitor.SendFileWithFDSet(nextOverlayName, f, false)
	if err != nil {
		return fmt.Errorf("Failed sending file descriptor of %q for disk device: %w", f.Name(), err)
	}

	reverter.Add(func() { _ = monitor.RemoveFDFromFDSet(nextOverlayName) })

	blockDev := map[string]any{
		"driver":    "qcow2",
		"discard":   "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file.
		"node-name": nextOverlayName,
		"read-only": false,
		"file": map[string]any{
			"driver":   "host_device",
			"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
		},
	}

	// Add overlay block dev.
	err = monitor.AddBlockDevice(blockDev, nil, false)
	if err != nil {
		return fmt.Errorf("Fail to add block device: %w", err)
	}

	reverter.Add(func() { _ = monitor.RemoveBlockDevice(nextOverlayName) })

	// Take a snapshot of the root disk and redirect writes to the snapshot disk.
	err = monitor.BlockDevSnapshot(currentRootNode, nextOverlayName)
	if err != nil {
		return fmt.Errorf("Failed taking storage snapshot: %w", err)
	}

	reverter.Add(func() { _ = monitor.BlockCommit(nextOverlayName, "", "") })

	// Update metadata of the backing file.
	// Use the Qcow2Rebase method when performing stateful snapshots.
	// Using QMP to modify a volume that was added while the VM is paused can cause QEMU to crash.
	if stateful {
		err = storageDrivers.Qcow2Rebase(devPath, backingFilename)
		if err != nil {
			return err
		}
	} else {
		err = monitor.ChangeBackingFile(nextOverlayName, nextOverlayName, backingFilename)
		if err != nil {
			return fmt.Errorf("Failed changing backing file: %w", err)
		}
	}

	reverter.Success()
	return nil
}

// fetchBlockDeviceChain returns the ordered list of block device names
// required to load a volume, including any dependent layers.
func (d *qemu) fetchBlockDeviceChain(m *qmp.Monitor, blockDevName string) ([]string, error) {
	// Fetch information about block devices.
	blockdevNames, err := m.QueryNamedBlockNodes()
	if err != nil {
		return nil, fmt.Errorf("Failed fetching block nodes names: %w", err)
	}

	return filterAndSortQcow2Blockdevs(blockdevNames, blockDevName), nil
}

// DeleteQcow2Snapshot deletes a qcow2 snapshot for a running instance.
func (d *qemu) DeleteQcow2Snapshot(devName string, snapshotIndex int, backingFilename string) error {
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	devName = d.blockNodeName(linux.PathNameEncode(devName))

	// Select all block devices related to a qcow2 backing chain.
	blockDevs, err := d.fetchBlockDeviceChain(monitor, devName)
	if err != nil {
		return err
	}

	d.logger.Debug("QCOW2 blockdev chain:", logger.Ctx{"blockdev": blockDevs, "snapshotIndex": snapshotIndex, "devName": devName})

	if snapshotIndex < 0 || (snapshotIndex+1) >= len(blockDevs) {
		return fmt.Errorf("Incorrect snapshot index: %d", snapshotIndex)
	}

	rootDevName := blockDevs[len(blockDevs)-1]
	snapChildDevName := blockDevs[snapshotIndex+1]
	snapDevName := blockDevs[snapshotIndex]

	err = monitor.BlockCommit(rootDevName, snapChildDevName, snapDevName)
	if err != nil {
		return err
	}

	err = monitor.RemoveBlockDevice(snapChildDevName)
	if err != nil {
		d.logger.Error("Remove block device error", logger.Ctx{"err": err})
		return err
	}

	err = monitor.RemoveFDFromFDSet(snapChildDevName)
	if err != nil {
		d.logger.Error("Remove fd from fd set", logger.Ctx{"err": err})
		return err
	}

	if backingFilename == "" {
		return nil
	}

	if snapshotIndex+2 >= len(blockDevs) {
		return fmt.Errorf("Incorrect snapshot index for backing file update: %d", snapshotIndex)
	}

	// Update metadata of the backing file.
	err = monitor.ChangeBackingFile(rootDevName, blockDevs[snapshotIndex+2], backingFilename)
	if err != nil {
		return fmt.Errorf("Failed changing backing file: %w", err)
	}

	return nil
}

// ExportQcow2Block exports a qcow2 block device by exposing it through a QEMU NBD server.
func (d *qemu) ExportQcow2Block(diskName string, blockIndex int) (func(), string, error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, "", err
	}

	socketPath := d.migrateSockPath()

	// Cleanup any leftover sockets.
	err = os.Remove(socketPath)
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		return nil, "", fmt.Errorf("Failed to remove stale migration socket %q: %w", socketPath, err)
	}

	// Reference the socket through a short /proc path to handle run paths that
	// exceed the unix socket path limit. The directory is kept open until cleanup
	// so qemu-nbd can connect through the same reference.
	runDir, err := os.OpenFile(d.RunPath(), unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
	if err != nil {
		return nil, "", err
	}

	reverter := revert.New()
	defer reverter.Fail()

	reverter.Add(func() { _ = runDir.Close() })

	shortSocketPath := fmt.Sprintf("/proc/%d/fd/%d/%s", os.Getpid(), runDir.Fd(), filepath.Base(socketPath))

	addr, err := net.ResolveUnixAddr("unix", shortSocketPath)
	if err != nil {
		return nil, "", err
	}

	migrationSock, err := net.ListenUnix("unix", addr)
	if err != nil {
		return nil, "", fmt.Errorf("Error connecting to migration socket %q: %w", socketPath, err)
	}

	migrationFile, err := migrationSock.File()
	if err != nil {
		return nil, "", fmt.Errorf("Error opening migration socket %q: %w", socketPath, err)
	}

	err = monitor.SendFile(socketPath, migrationFile)
	if err != nil {
		return nil, "", fmt.Errorf("Failed to send migration file descriptor: %w", err)
	}

	err = monitor.NBDUnixServerStart(socketPath)
	if err != nil {
		return nil, "", fmt.Errorf("Failed starting NBD server: %w", err)
	}

	escapedDeviceName := linux.PathNameEncode(diskName)
	nodeName := d.blockNodeName(escapedDeviceName)

	// Selects all block devices related to this instance (backing, root disk, overlays).
	blockDevs, err := d.fetchBlockDeviceChain(monitor, nodeName)
	if err != nil {
		return nil, "", err
	}

	d.logger.Debug("Instance block devices:", logger.Ctx{"blockdev": blockDevs, "blockIndex": blockIndex})

	if blockIndex < 0 || blockIndex >= len(blockDevs) {
		return nil, "", fmt.Errorf("Incorrect block device index: %d", blockIndex)
	}

	exportBlockName := blockDevs[blockIndex]

	exportDiskPath := fmt.Sprintf("nbd+unix:///%s?socket=%s", exportBlockName, shortSocketPath)

	err = monitor.NBDBlockExportAdd(exportBlockName, exportBlockName, false, "", nil)
	if err != nil {
		return nil, "", fmt.Errorf("Failed adding disk to NBD server: %w", err)
	}

	reverter.Success()

	return func() {
		_ = monitor.NBDServerStop()
		_ = migrationSock.Close()
		_ = runDir.Close()
	}, exportDiskPath, nil
}

func (d *qemu) isQCOW2(devPath string) (bool, error) {
	imgInfo, err := storageDrivers.Qcow2Info(devPath)
	if err != nil {
		return false, err
	}

	return imgInfo.Format == storageDrivers.BlockVolumeTypeQcow2, nil
}

func (d *qemu) qcow2BlockDev(m *qmp.Monitor, nodeName string, aioMode string, directCache bool, noFlushCache bool, permissions int, readonly bool, backingPaths []string, iter int) (string, error) {
	devName := backingPaths[0]
	backingNodeName := fmt.Sprintf("%s_backing%d", nodeName, iter)

	f, err := os.OpenFile(devName, permissions, 0)
	if err != nil {
		return "", fmt.Errorf("Failed opening file descriptor for disk device %q: %w", devName, err)
	}

	defer logger.WarnOnError(f.Close, "Failed to close file")

	info, err := m.SendFileWithFDSet(backingNodeName, f, readonly)
	if err != nil {
		return "", fmt.Errorf("Failed sending file descriptor of %q for disk device %q: %w", f.Name(), devName, err)
	}

	blockDev := map[string]any{
		"driver":    "qcow2",
		"discard":   "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file.
		"node-name": backingNodeName,
		"read-only": false,
		"file": map[string]any{
			"driver":   "host_device",
			"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
			"aio":      aioMode,
			"cache": map[string]any{
				"direct":   directCache,
				"no-flush": noFlushCache,
			},
		},
	}

	// If there are any children, load block information about them.
	if len(backingPaths) > 1 {
		parentNodeName, err := d.qcow2BlockDev(m, nodeName, aioMode, directCache, noFlushCache, permissions, readonly, backingPaths[1:], iter+1)
		if err != nil {
			return "", err
		}

		blockDev["backing"] = parentNodeName
	}

	err = m.AddBlockDevice(blockDev, nil, false)
	if err != nil {
		return "", err
	}

	return backingNodeName, nil
}

// currentQcow2OverlayIndex returns the current maximum overlay index.
func currentQcow2OverlayIndex(names []string, prefix string) int {
	re := regexp.MustCompile(fmt.Sprintf(`^%s_overlay(\d+)$`, prefix))

	maxIndex := -1

	for _, name := range names {
		m := re.FindStringSubmatch(name)
		if len(m) == 2 {
			n, err := strconv.Atoi(m[1])
			if err == nil && n > maxIndex {
				maxIndex = n
			}
		}
	}

	return maxIndex
}

func (d *qemu) needsFullRestart() bool {
	// Check if we have a pending change.
	if d.localConfig["volatile.vm.needs_reset"] != "" {
		return true
	}

	// Check if the QEMU binary has changed.
	pid, _ := d.pid()
	if pid <= 0 {
		return true
	}

	exePath, _, err := d.qemuArchConfig(d.architecture)
	if err != nil {
		return true
	}

	var curExe unix.Stat_t
	err = unix.Stat(fmt.Sprintf("/proc/%d/exe", pid), &curExe)
	if err != nil {
		return true
	}

	var nextExe unix.Stat_t
	err = unix.Stat(exePath, &nextExe)
	if err != nil {
		return true
	}

	if curExe.Size != nextExe.Size || curExe.Ino != nextExe.Ino || curExe.Dev != nextExe.Dev {
		return true
	}

	// Full restart isn't required.
	return false
}

// ConnectNBD exports a disk over NBD. Not supported by containers.
func (d *qemu) ConnectNBD(diskName string, volSize int64, writable bool) (net.Conn, func(), error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, nil, err
	}

	// Check for existing NBD block exports to detect if another operation is in progress.
	blocks, err := monitor.QueryNBDBlockExports()
	if err == nil && len(blocks) > 0 {
		return nil, nil, fmt.Errorf("Another NBD operation is already in progress for: %s", blocks[0].NodeName)
	}

	nbdConn, err := monitor.NBDServerStart("", 1)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed starting NBD server: %w", err)
	}

	d.logger.Debug("User requested NBD server started")

	reverter := revert.New()
	defer reverter.Fail()

	overlayNode := ""

	disconnect := func() {
		d.logger.Debug("User requested NBD server stopped")
		_ = nbdConn.Close()
		_ = monitor.NBDServerStop()

		if overlayNode != "" {
			err := d.removeEphemeralOverlay(monitor, overlayNode)
			if err != nil {
				d.logger.Error("Failed removing temporary snapshot overlay", logger.Ctx{"overlay": overlayNode, "err": err})
			}
		}
	}

	reverter.Add(disconnect)

	bitmaps, err := d.GetBitmaps(diskName)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed fetching bitmaps for %q: %w", diskName, err)
	}

	bitmapNames := []string{}
	for _, b := range bitmaps {
		if b.Inconsistent {
			continue
		}

		bitmapNames = append(bitmapNames, b.Name)
	}

	escapedDeviceName := linux.PathNameEncode(diskName)
	nodeName := d.blockNodeName(escapedDeviceName)

	blockDevs, err := d.fetchBlockDeviceChain(monitor, nodeName)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed fetching disk chain: %w", err)
	}

	blockExport := blockDevs[len(blockDevs)-1]
	exportNode := blockExport

	if !writable {
		// Expose a frozen view of the disk through a copy-before-write overlay
		// (see ConnectNBDAllDisks).
		snapNode, baseNode, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, blockExport, volSize, true)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed creating temporary snapshot: %w", err)
		}

		err = monitor.BlockDevBackupTransaction([]qmp.BlockDevBackupTarget{{Device: baseNode, Target: snapNode, Sync: "none", JobID: snapNode}})
		if err != nil {
			removeOverlay()
			return nil, nil, fmt.Errorf("Failed creating temporary snapshot: %w", err)
		}

		overlayNode = snapNode
		exportNode = snapNode
	}

	err = monitor.NBDBlockExportAdd(exportNode, "", writable, blockExport, bitmapNames)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed adding disk to NBD server: %w", err)
	}

	cleanup := reverter.Clone().Fail
	reverter.Success()
	return nbdConn, cleanup, nil
}

// nbdSession tracks a running NBD session and its active connections.
type nbdSession struct {
	connections int
	stop        func()
}

// nbdSessions tracks the active NBD sessions by instance ID.
var (
	nbdSessionsMu sync.Mutex
	nbdSessions   = map[int]*nbdSession{}
)

// releaseNBDSession drops one connection from the instance's NBD session, stopping the session
// when no connections are left.
func (d *qemu) releaseNBDSession() {
	nbdSessionsMu.Lock()
	defer nbdSessionsMu.Unlock()

	session := nbdSessions[d.id]
	if session == nil {
		return
	}

	session.connections--
	if session.connections > 0 {
		return
	}

	delete(nbdSessions, d.id)
	session.stop()
}

// ConnectNBDAllDisks exports all of the instance's block disks read-only over a single NBD server.
// When reuse is true, an additional connection to an already running NBD server is returned instead.
// The NBD server is stopped once all of its connections are closed.
func (d *qemu) ConnectNBDAllDisks(reuse bool) (net.Conn, func(), error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, nil, err
	}

	// Connect to an already running NBD server when requested.
	if reuse {
		nbdSessionsMu.Lock()

		session := nbdSessions[d.id]
		if session == nil {
			nbdSessionsMu.Unlock()
			return nil, nil, errors.New("No NBD session is currently active")
		}

		conn, err := net.Dial("unix", d.nbdPath())
		if err != nil {
			nbdSessionsMu.Unlock()
			return nil, nil, fmt.Errorf("Failed connecting to NBD server: %w", err)
		}

		session.connections++
		nbdSessionsMu.Unlock()

		cleanup := func() {
			_ = conn.Close()
			d.releaseNBDSession()
		}

		return conn, cleanup, nil
	}

	// Check for existing NBD block exports to detect if another operation is in progress.
	blocks, err := monitor.QueryNBDBlockExports()
	if err == nil && len(blocks) > 0 {
		return nil, nil, fmt.Errorf("Another NBD operation is already in progress for: %s", blocks[0].NodeName)
	}

	// Determine the set of block-backed disks that can be exported.
	namedNodes, err := monitor.QueryNamedBlockNodes()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed fetching block nodes names: %w", err)
	}

	nodeSet := make(map[string]struct{}, len(namedNodes))
	for _, n := range namedNodes {
		nodeSet[n] = struct{}{}
	}

	deviceNames := []string{}
	for devName, devConf := range d.ExpandedDevices() {
		if devConf["type"] != "disk" {
			continue
		}

		// Only block-backed disks have a matching QEMU block node, this filters out filesystem
		// shares (e.g. virtiofs) which can't be exported over NBD.
		nodeName := d.blockNodeName(linux.PathNameEncode(devName))
		_, ok := nodeSet[nodeName]
		if !ok {
			continue
		}

		deviceNames = append(deviceNames, devName)
	}

	if len(deviceNames) == 0 {
		return nil, nil, errors.New("Instance has no exportable disks")
	}

	// Export the disks in a stable order.
	sort.Strings(deviceNames)

	// Recover any overlay left behind by a previously failed teardown so the
	// disks can be snapshotted and exported again.
	for _, devName := range deviceNames {
		overlayNode := ephemeralSnapshotName(d.blockNodeName(linux.PathNameEncode(devName)))
		_, ok := nodeSet[overlayNode]
		if !ok {
			continue
		}

		err = d.removeEphemeralOverlay(monitor, overlayNode)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed recovering disk %q from an earlier failed teardown: %w", devName, err)
		}
	}

	nbdConn, err := monitor.NBDServerStart(d.nbdPath(), 0)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed starting NBD server: %w", err)
	}

	d.logger.Debug("User requested NBD server started")

	reverter := revert.New()
	defer reverter.Fail()

	reverter.Add(func() {
		d.logger.Debug("User requested NBD server stopped")
		_ = nbdConn.Close()

		err := monitor.NBDServerStop()
		if err != nil {
			d.logger.Error("Failed stopping NBD server", logger.Ctx{"err": err})
		}

		_ = os.Remove(d.nbdPath())
	})

	type exportTarget struct {
		deviceName string
		exportNode string
		bitmapNode string
		bitmaps    []string
	}

	targets := make([]exportTarget, 0, len(deviceNames))
	backups := make([]qmp.BlockDevBackupTarget, 0, len(deviceNames))
	overlays := make([]string, 0, len(deviceNames))

	// Prepare a copy-before-write overlay for each disk, exposing a frozen view of it while the
	// guest keeps writing to the disk itself. Snapshotting the disk instead would reopen it and
	// its persistent dirty bitmaps read-only, making the eventual merge of the snapshot fail.
	for _, devName := range deviceNames {
		nodeName := d.blockNodeName(linux.PathNameEncode(devName))

		bitmaps, err := d.GetBitmaps(devName)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed fetching bitmaps for %q: %w", devName, err)
		}

		bitmapNames := []string{}
		for _, b := range bitmaps {
			if b.Inconsistent {
				continue
			}

			bitmapNames = append(bitmapNames, b.Name)
		}

		diskSize, err := monitor.BlockNodeSize(nodeName)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed fetching size for %q: %w", devName, err)
		}

		overlayNode, baseNode, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, nodeName, diskSize, true)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed creating temporary snapshot for %q: %w", devName, err)
		}

		reverter.Add(removeOverlay)

		backups = append(backups, qmp.BlockDevBackupTarget{Device: baseNode, Target: overlayNode, Sync: "none", JobID: overlayNode})
		targets = append(targets, exportTarget{deviceName: devName, exportNode: overlayNode, bitmapNode: baseNode, bitmaps: bitmapNames})
		overlays = append(overlays, overlayNode)
	}

	// Start all copy-before-write jobs atomically so the exported disks share a consistent
	// point in time.
	err = monitor.BlockDevBackupTransaction(backups)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed creating consistent storage snapshot: %w", err)
	}

	// The overlays never hold guest writes, teardown cancels the jobs and drops them.
	reverter.Success()

	stop := func() {
		d.logger.Debug("User requested NBD server stopped")
		_ = nbdConn.Close()

		err := monitor.NBDServerStop()
		if err != nil {
			d.logger.Error("Failed stopping NBD server", logger.Ctx{"err": err})
		}

		_ = os.Remove(d.nbdPath())

		for _, overlayNode := range overlays {
			err := d.removeEphemeralOverlay(monitor, overlayNode)
			if err != nil {
				d.logger.Error("Failed removing temporary snapshot overlay", logger.Ctx{"overlay": overlayNode, "err": err})
			}
		}
	}

	// Add an NBD export per disk, using the Incus device name as the export name.
	for _, target := range targets {
		err = monitor.NBDBlockExportAdd(target.exportNode, target.deviceName, false, target.bitmapNode, target.bitmaps)
		if err != nil {
			stop()
			return nil, nil, fmt.Errorf("Failed adding disk %q to NBD server: %w", target.deviceName, err)
		}
	}

	// Register the session, it's stopped once its last connection is released.
	nbdSessionsMu.Lock()
	nbdSessions[d.id] = &nbdSession{connections: 1, stop: stop}
	nbdSessionsMu.Unlock()

	return nbdConn, d.releaseNBDSession, nil
}

// CreateBitmap creates a dirty bitmap.
func (d *qemu) CreateBitmap(deviceNames []string, data api.StorageVolumeBitmapsPost) error {
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	blockNames := []string{}
	for _, devName := range deviceNames {
		escapedDeviceName := linux.PathNameEncode(devName)
		nodeName := d.blockNodeName(escapedDeviceName)

		blockDevs, err := d.fetchBlockDeviceChain(monitor, nodeName)
		if err != nil {
			return fmt.Errorf("Failed fetching disk chain: %w", err)
		}

		blockNames = append(blockNames, blockDevs[len(blockDevs)-1])
	}

	err = monitor.AddDirtyBitmap(blockNames, data.Name, data.Granularity, data.Persistent, data.Disabled)
	if err != nil {
		return err
	}

	return nil
}

// DeleteBitmap deletes a dirty bitmap.
func (d *qemu) DeleteBitmap(deviceName string, bitmapName string) error {
	monitor, err := d.qmpConnect()
	if err != nil {
		return err
	}

	escapedDeviceName := linux.PathNameEncode(deviceName)
	nodeName := d.blockNodeName(escapedDeviceName)

	blockDevs, err := d.fetchBlockDeviceChain(monitor, nodeName)
	if err != nil {
		return fmt.Errorf("Failed fetching disk chain: %w", err)
	}

	blockName := blockDevs[len(blockDevs)-1]

	err = monitor.RemoveDirtyBitmap(blockName, bitmapName)
	if err != nil {
		return err
	}

	return nil
}

// GetBitmaps fetches dirty bitmaps.
func (d *qemu) GetBitmaps(deviceName string) ([]api.StorageVolumeBitmap, error) {
	monitor, err := d.qmpConnect()
	if err != nil {
		return nil, err
	}

	escapedDeviceName := linux.PathNameEncode(deviceName)
	nodeName := d.blockNodeName(escapedDeviceName)

	blockDevs, err := d.fetchBlockDeviceChain(monitor, nodeName)
	if err != nil {
		return nil, fmt.Errorf("Failed fetching disk chain: %w", err)
	}

	blockName := blockDevs[len(blockDevs)-1]

	blocks, err := monitor.QueryBlock()
	if err != nil {
		return nil, err
	}

	result := []api.StorageVolumeBitmap{}
	for _, block := range blocks {
		if block.Inserted.NodeName == blockName {
			for _, bitmap := range block.Inserted.DirtyBitmaps {
				result = append(result, api.StorageVolumeBitmap{
					Name:         bitmap.Name,
					Count:        bitmap.Count,
					Granularity:  bitmap.Granularity,
					Recording:    bitmap.Recording,
					Busy:         bitmap.Busy,
					Persistent:   bitmap.Persistent,
					Inconsistent: bitmap.Inconsistent,
				})
			}

			return result, nil
		}
	}

	return nil, fmt.Errorf("Requested device not found")
}

// selinuxEnsureContext generates and persists the SELinux context for this instance.
func (d *qemu) selinuxEnsureContext() (bool, error) {
	if !d.state.OS.SELinuxEnabled {
		return false, nil
	}

	previousCtx := d.localConfig["volatile.selinux.context"]

	allocLevel := func() (string, func(), error) {
		used, err := d.selinuxCollectUsedLevels()
		if err != nil {
			return "", nil, err
		}

		return selinux.AllocateLevel(used)
	}

	ctx, needsPersist, release, err := selinux.InstanceContext(d.state.OS, instancetype.VM, d.localConfig, d.expandedConfig, allocLevel)
	if err != nil {
		return false, err
	}

	defer release()

	if ctx == "" {
		return false, nil
	}

	if needsPersist {
		err = d.VolatileSet(map[string]string{"volatile.selinux.context": ctx})
		if err != nil {
			return false, fmt.Errorf("Failed to persist SELinux context: %w", err)
		}
	}

	// Return true if this is the first time a context was generated.
	return previousCtx == "", nil
}

// selinuxLabelFiles applies SELinux file labels to the VM instance directory.
func (d *qemu) selinuxLabelFiles(contextIsNew bool) error {
	if !d.state.OS.SELinuxEnabled {
		return nil
	}

	ctx := d.localConfig["volatile.selinux.context"]
	if ctx == "" {
		return nil
	}

	if !contextIsNew {
		if d.localConfig["security.selinux.level"] != "" {
			return nil
		}
	}

	fileCtx := selinux.InstanceFileContext(ctx, instancetype.VM, d.expandedConfig)
	if fileCtx == "" {
		return fmt.Errorf("Failed to derive file context from %q", ctx)
	}

	return selinux.LabelTree(d.Path(), fileCtx, "")
}

// buildDataFileInfo builds the "data-file" field for block device options.
func buildDataFileInfo(nodeName string, m *qmp.Monitor, driveConf deviceConfig.MountEntryItem, permissions int, readonly bool, aioMode string, directCache bool, noFlushCache bool) (map[string]any, error) {
	if driveConf.DataFilePath == "" {
		return nil, nil
	}

	if !util.PathExists(driveConf.DataFilePath) {
		return nil, fmt.Errorf("qcow2 data-file %q does not exist", driveConf.DataFilePath)
	}

	df, err := os.OpenFile(driveConf.DataFilePath, permissions, 0)
	if err != nil {
		return nil, fmt.Errorf("Failed opening qcow2 data-file %q: %w", driveConf.DataFilePath, err)
	}

	defer func() { _ = df.Close() }()

	dataInfo, err := m.SendFileWithFDSet(nodeName+"_raw", df, readonly)
	if err != nil {
		return nil, fmt.Errorf("Failed sending qcow2 data file descriptor %q: %w", driveConf.DataFilePath, err)
	}

	reverter := revert.New()
	defer reverter.Fail()

	reverter.Add(func() {
		_ = m.RemoveFDFromFDSet(nodeName + "_raw")
	})

	dataDev := map[string]any{
		"filename": fmt.Sprintf("/dev/fdset/%d", dataInfo.ID),
		"aio":      aioMode,
		"cache": map[string]any{
			"direct":   directCache,
			"no-flush": noFlushCache,
		},
	}

	dataFilePathInfo, err := os.Stat(driveConf.DataFilePath)
	if err != nil {
		return nil, fmt.Errorf("Invalid data-file path %q: %w", driveConf.DataFilePath, err)
	}

	if linux.IsBlockdev(dataFilePathInfo.Mode()) {
		dataDev["driver"] = "host_device"
	} else {
		dataDev["driver"] = "file"
	}

	reverter.Success()
	return dataDev, nil
}

// GetNVRAM gets the NVRAM.
func (d *qemu) GetNVRAM() (*uefi.Store, error) {
	if !d.IsRunning() {
		// Mount the instance's config volume.
		_, err := d.mount()
		if err != nil {
			return nil, err
		}

		defer logger.WarnOnError(d.unmount, "Failed to unmount instance")

		_, err = os.Stat(d.nvramPath())
		if errors.Is(err, os.ErrNotExist) {
			// The NVRAM hasn’t been initialized yet.
			err = d.setupNvram()
			if err != nil {
				return nil, err
			}
		}
	}

	nvRAM, err := os.ReadFile(d.nvramPath())
	if err != nil {
		return nil, fmt.Errorf("Failed opening NVRAM file: %w", err)
	}

	return uefi.ParseNVRAM(nvRAM)
}

// SetNVRAM sets the NVRAM.
func (d *qemu) SetNVRAM(store *uefi.Store) error {
	// Mount the instance's config volume.
	_, err := d.mount()
	if err != nil {
		return err
	}

	defer logger.WarnOnError(d.unmount, "Failed to unmount instance")

	_, err = os.Stat(d.nvramPath())
	if errors.Is(err, os.ErrNotExist) {
		// The NVRAM hasn’t been initialized yet.
		err = d.setupNvram()
		if err != nil {
			return err
		}
	}

	f, err := os.Create(d.nvramPath())
	if err != nil {
		return fmt.Errorf("Failed opening NVRAM file: %w", err)
	}

	b, err := store.Bytes()
	if err != nil {
		return err
	}

	_, err = f.Write(b)
	return err
}

// ResetNVRAM resets the NVRAM.
func (d *qemu) ResetNVRAM() error {
	// Mount the instance's config volume.
	_, err := d.mount()
	if err != nil {
		return err
	}

	defer logger.WarnOnError(d.unmount, "Failed to unmount instance")
	return d.setupNvram()
}
