package drivers

import (
	"bufio"
	"bytes"
	"context"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"maps"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"slices"
	"sort"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/checkpoint-restore/go-criu/v8/crit"
	"github.com/flosch/pongo2/v6"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/kballard/go-shellquote"
	liblxc "github.com/lxc/go-lxc"
	ociSpecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/sftp"
	"golang.org/x/sync/errgroup"
	"golang.org/x/sys/unix"
	"google.golang.org/protobuf/proto"
	yaml "gopkg.in/yaml.v2"

	internalInstance "github.com/lxc/incus/v6/internal/instance"
	"github.com/lxc/incus/v6/internal/instancewriter"
	internalIO "github.com/lxc/incus/v6/internal/io"
	"github.com/lxc/incus/v6/internal/jmap"
	"github.com/lxc/incus/v6/internal/linux"
	"github.com/lxc/incus/v6/internal/migration"
	"github.com/lxc/incus/v6/internal/netutils"
	"github.com/lxc/incus/v6/internal/rsync"
	"github.com/lxc/incus/v6/internal/server/apparmor"
	"github.com/lxc/incus/v6/internal/server/cgroup"
	"github.com/lxc/incus/v6/internal/server/daemon"
	"github.com/lxc/incus/v6/internal/server/db"
	"github.com/lxc/incus/v6/internal/server/db/cluster"
	"github.com/lxc/incus/v6/internal/server/db/operationtype"
	"github.com/lxc/incus/v6/internal/server/device"
	deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
	"github.com/lxc/incus/v6/internal/server/device/nictype"
	"github.com/lxc/incus/v6/internal/server/instance"
	"github.com/lxc/incus/v6/internal/server/instance/instancetype"
	"github.com/lxc/incus/v6/internal/server/instance/operationlock"
	"github.com/lxc/incus/v6/internal/server/ip"
	"github.com/lxc/incus/v6/internal/server/lifecycle"
	"github.com/lxc/incus/v6/internal/server/locking"
	"github.com/lxc/incus/v6/internal/server/metrics"
	localMigration "github.com/lxc/incus/v6/internal/server/migration"
	"github.com/lxc/incus/v6/internal/server/network"
	"github.com/lxc/incus/v6/internal/server/operations"
	"github.com/lxc/incus/v6/internal/server/project"
	"github.com/lxc/incus/v6/internal/server/response"
	"github.com/lxc/incus/v6/internal/server/seccomp"
	"github.com/lxc/incus/v6/internal/server/state"
	storagePools "github.com/lxc/incus/v6/internal/server/storage"
	storageDrivers "github.com/lxc/incus/v6/internal/server/storage/drivers"
	localUtil "github.com/lxc/incus/v6/internal/server/util"
	internalUtil "github.com/lxc/incus/v6/internal/util"
	"github.com/lxc/incus/v6/internal/version"
	"github.com/lxc/incus/v6/shared/api"
	"github.com/lxc/incus/v6/shared/idmap"
	"github.com/lxc/incus/v6/shared/ioprogress"
	"github.com/lxc/incus/v6/shared/logger"
	"github.com/lxc/incus/v6/shared/osarch"
	"github.com/lxc/incus/v6/shared/revert"
	"github.com/lxc/incus/v6/shared/subprocess"
	"github.com/lxc/incus/v6/shared/termios"
	"github.com/lxc/incus/v6/shared/units"
	"github.com/lxc/incus/v6/shared/util"
	"github.com/lxc/incus/v6/shared/ws"
)

// Helper functions.
func lxcSetConfigItem(c *liblxc.Container, key string, value string) error {
	if c == nil {
		return errors.New("Uninitialized go-lxc struct")
	}

	if !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 1, 0) {
		switch key {
		case "lxc.uts.name":
			key = "lxc.utsname"
		case "lxc.pty.max":
			key = "lxc.pts"
		case "lxc.tty.dir":
			key = "lxc.devttydir"
		case "lxc.tty.max":
			key = "lxc.tty"
		case "lxc.apparmor.profile":
			key = "lxc.aa_profile"
		case "lxc.apparmor.allow_incomplete":
			key = "lxc.aa_allow_incomplete"
		case "lxc.selinux.context":
			key = "lxc.se_context"
		case "lxc.mount.fstab":
			key = "lxc.mount"
		case "lxc.console.path":
			key = "lxc.console"
		case "lxc.seccomp.profile":
			key = "lxc.seccomp"
		case "lxc.signal.halt":
			key = "lxc.haltsignal"
		case "lxc.signal.reboot":
			key = "lxc.rebootsignal"
		case "lxc.signal.stop":
			key = "lxc.stopsignal"
		case "lxc.log.syslog":
			key = "lxc.syslog"
		case "lxc.log.level":
			key = "lxc.loglevel"
		case "lxc.log.file":
			key = "lxc.logfile"
		case "lxc.init.cmd":
			key = "lxc.init_cmd"
		case "lxc.init.uid":
			key = "lxc.init_uid"
		case "lxc.init.gid":
			key = "lxc.init_gid"
		case "lxc.idmap":
			key = "lxc.id_map"
		}
	}

	if strings.HasPrefix(key, "lxc.prlimit.") {
		if !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 1, 0) {
			return errors.New(`Process limits require liblxc >= 2.1`)
		}
	}

	err := c.SetConfigItem(key, value)
	if err != nil {
		return fmt.Errorf("Failed to set LXC config: %s=%s", key, value)
	}

	return nil
}

func lxcStatusCode(state liblxc.State) api.StatusCode {
	return map[int]api.StatusCode{
		1: api.Stopped,
		2: api.Starting,
		3: api.Running,
		4: api.Stopping,
		5: api.Aborting,
		6: api.Freezing,
		7: api.Frozen,
		8: api.Thawed,
		9: api.Error,
	}[int(state)]
}

// lxcCreate creates the DB storage records and sets up instance devices.
// Returns a revert fail function that can be used to undo this function if a subsequent step fails.
func lxcCreate(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 container struct
	d := &lxc{
		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,
		},
	}

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

	_, 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")
	}

	// Setup the initial idmap config.
	var idmapSet *idmap.Set
	base := int64(0)
	if !d.IsPrivileged() {
		idmapSet, base, err = d.findIdmap()
		if err != nil {
			return nil, nil, err
		}
	}

	idmapSetJSON, err := idmapSet.ToJSON()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to encode ID map: %w", err)
	}

	v := map[string]string{
		"volatile.idmap.next": idmapSetJSON,
		"volatile.idmap.base": fmt.Sprintf("%v", base),
	}

	// Invalidate the idmap cache.
	d.idmapset = nil

	// Set last_state if not currently set.
	if d.localConfig["volatile.last_state.idmap"] == "" {
		v["volatile.last_state.idmap"] = "[]"
	}

	err = d.VolatileSet(v)
	if err != nil {
		return nil, nil, err
	}

	// Re-run init to update the idmap.
	err = d.init()
	if err != nil {
		return nil, nil, err
	}

	if !d.IsSnapshot() {
		// Add devices to container.
		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 {
		// Add instance to authorizer.
		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{"instanceName": d.Name(), "projectName": 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.InstanceTypeContainer,
			"storage-pool": d.storagePool.Name(),
			"location":     d.Location(),
		}))
	}

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

	return d, cleanup, err
}

func lxcLoad(s *state.State, args db.InstanceArgs, p api.Project) (instance.Instance, error) {
	// Create the container struct
	d := lxcInstantiate(s, args, nil, p)

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

	return d, nil
}

// Unload is called by the garbage collector.
func lxcUnload(d *lxc) {
	d.release()
}

// release releases any internal reference to a liblxc container, invalidating the go-lxc cache.
func (d *lxc) release() {
	d.cMu.Lock()
	defer d.cMu.Unlock()

	if d.c != nil {
		_ = d.c.Release()
		d.c = nil
	}
}

// Create a container struct without initializing it.
func lxcInstantiate(s *state.State, args db.InstanceArgs, expandedDevices deviceConfig.Devices, p api.Project) instance.Instance {
	d := &lxc{
		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,
		},
	}

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

// The LXC container driver.
type lxc struct {
	common

	// Config handling.
	fromHook bool

	cMu        sync.Mutex
	cFinalizer sync.Once

	// Cached handles.
	// Do not use these variables directly, instead use their associated get functions so they
	// will be initialized on demand.
	c *liblxc.Container // Use d.initLXC() instead of accessing this directly.

	cConfig  bool
	idmapset *idmap.Set
}

var idmapLock sync.Mutex

func (d *lxc) findIdmap() (*idmap.Set, int64, error) {
	if d.state.OS.IdmapSet == nil {
		return nil, 0, errors.New("System doesn't have a functional idmap setup")
	}

	idmapSize := func(size string) (int64, error) {
		var idMapSize int64
		if size == "" || size == "auto" {
			if util.IsTrue(d.expandedConfig["security.idmap.isolated"]) {
				idMapSize = 65536
			} else {
				if len(d.state.OS.IdmapSet.Entries) != 2 {
					return 0, fmt.Errorf("Bad initial idmap: %v", d.state.OS.IdmapSet)
				}

				idMapSize = d.state.OS.IdmapSet.Entries[0].MapRange
			}
		} else {
			size, err := strconv.ParseInt(size, 10, 64)
			if err != nil {
				return 0, err
			}

			idMapSize = size
		}

		return idMapSize, nil
	}

	rawMaps, err := idmap.NewSetFromIncusIDMap(d.expandedConfig["raw.idmap"])
	if err != nil {
		return nil, 0, err
	}

	mkIdmap := func(offset int64, size int64) (*idmap.Set, error) {
		set := &idmap.Set{Entries: []idmap.Entry{
			{IsUID: true, NSID: 0, HostID: offset, MapRange: size},
			{IsGID: true, NSID: 0, HostID: offset, MapRange: size},
		}}

		for _, ent := range rawMaps.Entries {
			err := set.AddSafe(ent)
			if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
				return nil, err
			}
		}

		return set, nil
	}

	if !util.IsTrue(d.expandedConfig["security.idmap.isolated"]) {
		// Create a new set based from the global one.
		newIdmapset := idmap.Set{Entries: make([]idmap.Entry, len(d.state.OS.IdmapSet.Entries))}
		copy(newIdmapset.Entries, d.state.OS.IdmapSet.Entries)

		// Restrict the range sizes if specified.
		if d.expandedConfig["security.idmap.size"] != "" {
			size, err := idmapSize(d.expandedConfig["security.idmap.size"])
			if err != nil {
				return nil, 0, err
			}

			for k, ent := range newIdmapset.Entries {
				if ent.MapRange < size {
					continue
				}

				newIdmapset.Entries[k].MapRange = size
			}
		}

		// Apply the raw idmap entries.
		for _, ent := range rawMaps.Entries {
			err := newIdmapset.AddSafe(ent)
			if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
				return nil, 0, err
			}
		}

		return &newIdmapset, 0, nil
	}

	size, err := idmapSize(d.expandedConfig["security.idmap.size"])
	if err != nil {
		return nil, 0, err
	}

	if d.expandedConfig["security.idmap.base"] != "" {
		offset, err := strconv.ParseInt(d.expandedConfig["security.idmap.base"], 10, 64)
		if err != nil {
			return nil, 0, err
		}

		set, err := mkIdmap(offset, size)
		if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
			return nil, 0, err
		}

		return set, offset, nil
	}

	idmapLock.Lock()
	defer idmapLock.Unlock()

	cts, err := instance.LoadNodeAll(d.state, instancetype.Container)
	if err != nil {
		return nil, 0, err
	}

	offset := d.state.OS.IdmapSet.Entries[0].HostID + 65536

	mapentries := idmap.ByHostID{}
	for _, container := range cts {
		if container.Type() != instancetype.Container {
			continue
		}

		/* Don't change our map Just Because. */
		if container.ID() == d.id {
			continue
		}

		if container.IsPrivileged() {
			continue
		}

		if util.IsFalseOrEmpty(container.ExpandedConfig()["security.idmap.isolated"]) {
			continue
		}

		if container.ExpandedConfig()["volatile.idmap.base"] == "" {
			continue
		}

		cBase, err := strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 64)
		if err != nil {
			return nil, 0, err
		}

		cSize, err := idmapSize(container.ExpandedConfig()["security.idmap.size"])
		if err != nil {
			return nil, 0, err
		}

		mapentries.Entries = append(mapentries.Entries, idmap.Entry{HostID: int64(cBase), MapRange: cSize})
	}

	sort.Sort(mapentries)

	for i := range mapentries.Entries {
		if i == 0 {
			if mapentries.Entries[0].HostID < offset+size {
				offset = mapentries.Entries[0].HostID + mapentries.Entries[0].MapRange
				continue
			}

			set, err := mkIdmap(offset, size)
			if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
				return nil, 0, err
			}

			return set, offset, nil
		}

		if mapentries.Entries[i-1].HostID+mapentries.Entries[i-1].MapRange > offset {
			offset = mapentries.Entries[i-1].HostID + mapentries.Entries[i-1].MapRange
			continue
		}

		offset = mapentries.Entries[i-1].HostID + mapentries.Entries[i-1].MapRange
		if offset+size < mapentries.Entries[i].HostID {
			set, err := mkIdmap(offset, size)
			if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
				return nil, 0, err
			}

			return set, offset, nil
		}

		offset = mapentries.Entries[i].HostID + mapentries.Entries[i].MapRange
	}

	if offset+size <= d.state.OS.IdmapSet.Entries[0].HostID+d.state.OS.IdmapSet.Entries[0].MapRange {
		set, err := mkIdmap(offset, size)
		if err != nil && errors.Is(err, idmap.ErrHostIDIsSubID) {
			return nil, 0, err
		}

		return set, offset, nil
	}

	return nil, 0, errors.New("Not enough uid/gid available for the container")
}

func (d *lxc) init() error {
	// Compute the expanded config and device list
	err := d.expandConfig()
	if err != nil {
		return err
	}

	return nil
}

func (d *lxc) initLXC(config bool) (*liblxc.Container, error) {
	d.cMu.Lock()
	defer d.cMu.Unlock()

	// No need to go through all that for snapshots
	if d.IsSnapshot() {
		return nil, nil
	}

	// Check if being called from a hook
	if d.fromHook {
		return nil, errors.New("You can't use go-lxc from inside a LXC hook")
	}

	// Check if already initialized
	if d.c != nil && (!config || d.cConfig) {
		return d.c, nil
	}

	// As we are now going to be initialising a liblxc.Container reference, set the finalizer so that it is
	// cleaned up (if needed) when the garbage collector destroys this instance struct.
	d.cFinalizer.Do(func() { runtime.SetFinalizer(d, lxcUnload) })

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

	// Load the go-lxc struct
	cname := project.Instance(d.Project().Name, d.Name())
	cc, err := liblxc.NewContainer(cname, d.state.OS.LxcPath)
	if err != nil {
		return nil, err
	}

	reverter.Add(func() {
		_ = cc.Release()
	})

	// Load cgroup abstraction
	cg, err := d.cgroup(cc, false)
	if err != nil {
		return nil, err
	}

	// Setup logging
	logfile := d.LogFilePath()
	err = lxcSetConfigItem(cc, "lxc.log.file", logfile)
	if err != nil {
		return nil, err
	}

	logLevel := "warn"
	if daemon.Debug {
		logLevel = "trace"
	} else if daemon.Verbose {
		logLevel = "info"
	}

	err = lxcSetConfigItem(cc, "lxc.log.level", logLevel)
	if err != nil {
		return nil, err
	}

	if liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 3, 0, 0) {
		// Default size log buffer
		err = lxcSetConfigItem(cc, "lxc.console.buffer.size", "auto")
		if err != nil {
			return nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.console.size", "auto")
		if err != nil {
			return nil, err
		}

		// File to dump ringbuffer contents to when requested or
		// container shutdown.
		consoleBufferLogFile := d.ConsoleBufferLogPath()
		err = lxcSetConfigItem(cc, "lxc.console.logfile", consoleBufferLogFile)
		if err != nil {
			return nil, err
		}
	}

	if d.state.OS.ContainerCoreScheduling {
		err = lxcSetConfigItem(cc, "lxc.sched.core", "1")
		if err != nil {
			return nil, err
		}
	} else if d.state.OS.CoreScheduling {
		err = lxcSetConfigItem(cc, "lxc.hook.start-host", fmt.Sprintf("/proc/%d/exe forkcoresched 1", os.Getpid()))
		if err != nil {
			return nil, err
		}
	}

	// Allow for lightweight init
	d.cConfig = config
	if !config {
		if d.c != nil {
			_ = d.c.Release()
		}

		d.c = cc

		reverter.Success()
		return cc, err
	}

	if d.IsPrivileged() {
		// Base config
		toDrop := "sys_time sys_module sys_rawio"
		if !d.state.OS.AppArmorStacking || d.state.OS.AppArmorStacked {
			toDrop = toDrop + " mac_admin mac_override"
		}

		err = lxcSetConfigItem(cc, "lxc.cap.drop", toDrop)
		if err != nil {
			return nil, err
		}
	}

	// Set an appropriate /proc, /sys/ and /sys/fs/cgroup
	mounts := []string{}
	if d.IsPrivileged() && !d.state.OS.RunningInUserNS {
		mounts = append(mounts, "proc:mixed")
		mounts = append(mounts, "sys:mixed")
	} else {
		mounts = append(mounts, "proc:rw")
		mounts = append(mounts, "sys:rw")
	}

	cgInfo := cgroup.GetInfo()
	if cgInfo.Namespacing {
		if cgInfo.Layout == cgroup.CgroupsUnified {
			mounts = append(mounts, "cgroup:rw:force")
		} else {
			mounts = append(mounts, "cgroup:mixed")
		}
	} else {
		mounts = append(mounts, "cgroup:mixed")
	}

	err = lxcSetConfigItem(cc, "lxc.mount.auto", strings.Join(mounts, " "))
	if err != nil {
		return nil, err
	}

	err = lxcSetConfigItem(cc, "lxc.autodev", "1")
	if err != nil {
		return nil, err
	}

	err = lxcSetConfigItem(cc, "lxc.pty.max", "1024")
	if err != nil {
		return nil, err
	}

	bindMounts := []string{
		"/dev/fuse",
		"/dev/net/tun",
		"/sys/firmware/efi/efivars",
		"/sys/fs/fuse/connections",
		"/sys/fs/pstore",
		"/sys/kernel/config",
		"/sys/kernel/debug",
		"/sys/kernel/security",
		"/sys/kernel/tracing",
	}

	// Handle unprivileged binfmt_misc.
	if d.IsPrivileged() || !d.state.OS.UnprivBinfmt {
		bindMounts = append(bindMounts, "/proc/sys/fs/binfmt_misc")
	}

	// Pass in /dev/zfs to the container if delegation is supported on the system.
	// This is only done for unprivileged containers as delegation is tied to the user namespace.
	if !d.IsPrivileged() && storageDrivers.ZFSSupportsDelegation() && util.PathExists("/dev/zfs") {
		bindMounts = append(bindMounts, "/dev/zfs")
	}

	if d.IsPrivileged() && !d.state.OS.RunningInUserNS {
		err = lxcSetConfigItem(cc, "lxc.mount.entry", "mqueue dev/mqueue mqueue rw,relatime,create=dir,optional 0 0")
		if err != nil {
			return nil, err
		}
	} else {
		bindMounts = append(bindMounts, "/dev/mqueue")
	}

	for _, mnt := range bindMounts {
		if !util.PathExists(mnt) {
			continue
		}

		if internalUtil.IsDir(mnt) {
			err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s none rbind,create=dir,optional 0 0", mnt, strings.TrimPrefix(mnt, "/")))
			if err != nil {
				return nil, err
			}
		} else {
			err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s none bind,create=file,optional 0 0", mnt, strings.TrimPrefix(mnt, "/")))
			if err != nil {
				return nil, err
			}
		}
	}

	// For lxcfs
	templateConfDir := os.Getenv("INCUS_LXC_TEMPLATE_CONFIG")
	if templateConfDir == "" {
		templateConfDir = "/usr/share/lxc/config"
	}

	if util.PathExists(fmt.Sprintf("%s/common.conf.d/", templateConfDir)) {
		err = lxcSetConfigItem(cc, "lxc.include", fmt.Sprintf("%s/common.conf.d/", templateConfDir))
		if err != nil {
			return nil, err
		}
	}

	// Configure devices cgroup
	if d.IsPrivileged() && !d.state.OS.RunningInUserNS && d.state.OS.CGInfo.Supports(cgroup.Devices, cg) {
		if d.state.OS.CGInfo.Layout == cgroup.CgroupsUnified {
			err = lxcSetConfigItem(cc, "lxc.cgroup2.devices.deny", "a")
		} else {
			err = lxcSetConfigItem(cc, "lxc.cgroup.devices.deny", "a")
		}

		if err != nil {
			return nil, err
		}

		devices := []string{
			"b *:* m",      // Allow mknod of block devices
			"c *:* m",      // Allow mknod of char devices
			"c 136:* rwm",  // /dev/pts devices
			"c 1:3 rwm",    // /dev/null
			"c 1:5 rwm",    // /dev/zero
			"c 1:7 rwm",    // /dev/full
			"c 1:8 rwm",    // /dev/random
			"c 1:9 rwm",    // /dev/urandom
			"c 5:0 rwm",    // /dev/tty
			"c 5:1 rwm",    // /dev/console
			"c 5:2 rwm",    // /dev/ptmx
			"c 10:229 rwm", // /dev/fuse
			"c 10:200 rwm", // /dev/net/tun
		}

		if storageDrivers.ZFSSupportsDelegation() {
			devices = append(devices, "c 10:249 rwm")
		}

		for _, dev := range devices {
			if d.state.OS.CGInfo.Layout == cgroup.CgroupsUnified {
				err = lxcSetConfigItem(cc, "lxc.cgroup2.devices.allow", dev)
			} else {
				err = lxcSetConfigItem(cc, "lxc.cgroup.devices.allow", dev)
			}

			if err != nil {
				return nil, err
			}
		}
	}

	if d.IsNesting() {
		/*
		 * mount extra /proc and /sys to work around kernel
		 * restrictions on remounting them when covered
		 */
		err = lxcSetConfigItem(cc, "lxc.mount.entry", "proc dev/.lxc/proc proc create=dir,optional 0 0")
		if err != nil {
			return nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.mount.entry", "sys dev/.lxc/sys sysfs create=dir,optional 0 0")
		if err != nil {
			return nil, err
		}
	}

	// Setup architecture
	personality, err := osarch.ArchitecturePersonality(d.architecture)
	if err != nil {
		personality, err = osarch.ArchitecturePersonality(d.state.OS.Architectures[0])
		if err != nil {
			return nil, err
		}
	}

	err = lxcSetConfigItem(cc, "lxc.arch", personality)
	if err != nil {
		return nil, err
	}

	// Setup the hooks
	err = lxcSetConfigItem(cc, "lxc.hook.version", "1")
	if err != nil {
		return nil, err
	}

	// Call the onstart hook on start.
	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("/proc/%d/exe callhook %s %s %s start", os.Getpid(), internalUtil.VarPath(""), util.SingleQuote(d.Project().Name), util.SingleQuote(d.Name())))
	if err != nil {
		return nil, err
	}

	// Call the onstopns hook on stop but before namespaces are unmounted.
	err = lxcSetConfigItem(cc, "lxc.hook.stop", fmt.Sprintf("%s callhook %s %s %s stopns", d.state.OS.ExecPath, internalUtil.VarPath(""), util.SingleQuote(d.Project().Name), util.SingleQuote(d.Name())))
	if err != nil {
		return nil, err
	}

	// Call the onstop hook on stop.
	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %s %s stop", d.state.OS.ExecPath, internalUtil.VarPath(""), util.SingleQuote(d.Project().Name), util.SingleQuote(d.Name())))
	if err != nil {
		return nil, err
	}

	// Setup the console
	err = lxcSetConfigItem(cc, "lxc.tty.max", "0")
	if err != nil {
		return nil, err
	}

	// Setup the hostname
	err = lxcSetConfigItem(cc, "lxc.uts.name", d.Name())
	if err != nil {
		return nil, err
	}

	// Setup devIncus
	if util.IsTrueOrEmpty(d.expandedConfig["security.guestapi"]) {
		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s dev/incus none bind,create=dir 0 0", internalUtil.VarPath("guestapi")))
		if err != nil {
			return nil, err
		}
	}

	// Setup AppArmor
	if d.state.OS.AppArmorAvailable {
		if d.state.OS.AppArmorConfined || !d.state.OS.AppArmorAdmin {
			// If confined but otherwise able to use AppArmor, use our own profile
			curProfile := localUtil.AppArmorProfile()
			curProfile = strings.TrimSuffix(curProfile, " (enforce)")
			err := lxcSetConfigItem(cc, "lxc.apparmor.profile", curProfile)
			if err != nil {
				return nil, err
			}
		} else {
			// If not currently confined, use the container's profile
			profile := apparmor.InstanceProfileName(d)

			/* In the nesting case, we want to enable the inside
			 * daemon to load its profile. Unprivileged containers can
			 * load profiles, but privileged containers cannot, so
			 * let's not use a namespace so they can fall back to
			 * the old way of nesting, i.e. using the parent's
			 * profile.
			 */
			if d.state.OS.AppArmorStacking && !d.state.OS.AppArmorStacked {
				profile = fmt.Sprintf("%s//&:%s:", profile, apparmor.InstanceNamespaceName(d))
			}

			err := lxcSetConfigItem(cc, "lxc.apparmor.profile", profile)
			if err != nil {
				return nil, err
			}
		}
	} else {
		// Make sure that LXC won't try to apply an apparmor profile.
		// This may fail on liblxc compiled without apparmor, so ignore errors.
		_ = lxcSetConfigItem(cc, "lxc.apparmor.profile", "unconfined")
	}

	// Setup SELinux.
	if d.state.OS.SELinuxAvailable && d.state.OS.SELinuxContextInstanceLXC != "" {
		seContext, err := d.selinuxContext(d.state.OS.SELinuxContextInstanceLXC)
		if err != nil {
			return nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.selinux.context", seContext)
		if err != nil {
			return nil, err
		}
	}

	// Setup Seccomp if necessary
	if seccomp.InstanceNeedsPolicy(d) {
		err = lxcSetConfigItem(cc, "lxc.seccomp.profile", seccomp.ProfilePath(d))
		if err != nil {
			return nil, err
		}

		// Setup notification socket
		// System requirement errors are handled during policy generation instead of here
		ok, err := seccomp.InstanceNeedsIntercept(d.state, d)
		if err == nil && ok {
			err = lxcSetConfigItem(cc, "lxc.seccomp.notify.proxy", fmt.Sprintf("unix:%s", internalUtil.RunPath("seccomp.socket")))
			if err != nil {
				return nil, err
			}
		}
	}

	// Setup idmap
	idmapset, err := d.NextIdmap()
	if err != nil {
		return nil, err
	}

	if idmapset != nil {
		lines := idmapset.ToLXCString()
		for _, line := range lines {
			err := lxcSetConfigItem(cc, "lxc.idmap", line)
			if err != nil {
				return nil, err
			}
		}
	}

	// Setup environment
	for k, v := range d.expandedConfig {
		// gendoc:generate(entity=instance, group=miscellaneous, key=environment.*)
		// The specified key/value environment variables are exported to the instance and set for `incus exec`.

		// ---
		//  type: string
		//  liveupdate: yes (exec)
		//  shortdesc: Environment variables to export
		after, ok := strings.CutPrefix(k, "environment.")
		if ok {
			err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("%s=%s", after, v))
			if err != nil {
				return nil, err
			}
		}
	}

	err = lxcSetConfigItem(cc, "lxc.environment", "CREDENTIALS_DIRECTORY=/dev/.incus-systemd-credentials")
	if err != nil {
		return nil, err
	}

	err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s dev/.incus-systemd-credentials none bind,ro,create=dir 0 0", filepath.Join(d.Path(), "credentials")))
	if err != nil {
		return nil, err
	}

	// Setup NVIDIA runtime
	if util.IsTrue(d.expandedConfig["nvidia.runtime"]) {
		hookDir := os.Getenv("INCUS_LXC_HOOK")
		if hookDir == "" {
			hookDir = "/usr/share/lxc/hooks"
		}

		hookPath := filepath.Join(hookDir, "nvidia")
		if !util.PathExists(hookPath) {
			return nil, errors.New("The NVIDIA LXC hook couldn't be found")
		}

		_, err := exec.LookPath("nvidia-container-cli")
		if err != nil {
			return nil, errors.New("The NVIDIA container tools couldn't be found")
		}

		err = lxcSetConfigItem(cc, "lxc.environment", "NVIDIA_VISIBLE_DEVICES=none")
		if err != nil {
			return nil, err
		}

		nvidiaDriver := d.expandedConfig["nvidia.driver.capabilities"]
		if nvidiaDriver == "" {
			err = lxcSetConfigItem(cc, "lxc.environment", "NVIDIA_DRIVER_CAPABILITIES=compute,utility")
			if err != nil {
				return nil, err
			}
		} else {
			err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("NVIDIA_DRIVER_CAPABILITIES=%s", nvidiaDriver))
			if err != nil {
				return nil, err
			}
		}

		nvidiaRequireCuda := d.expandedConfig["nvidia.require.cuda"]
		if nvidiaRequireCuda == "" {
			err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("NVIDIA_REQUIRE_CUDA=%s", nvidiaRequireCuda))
			if err != nil {
				return nil, err
			}
		}

		nvidiaRequireDriver := d.expandedConfig["nvidia.require.driver"]
		if nvidiaRequireDriver == "" {
			err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("NVIDIA_REQUIRE_DRIVER=%s", nvidiaRequireDriver))
			if err != nil {
				return nil, err
			}
		}

		err = lxcSetConfigItem(cc, "lxc.hook.mount", hookPath)
		if err != nil {
			return nil, err
		}
	}

	// Memory limits
	if d.state.OS.CGInfo.Supports(cgroup.Memory, cg) {
		memory := d.expandedConfig["limits.memory"]
		memoryEnforce := d.expandedConfig["limits.memory.enforce"]
		memorySwap := d.expandedConfig["limits.memory.swap"]
		memorySwapPriority := d.expandedConfig["limits.memory.swap.priority"]

		// Configure the memory limits
		if memory != "" {
			valueInt, err := ParseMemoryStr(memory)
			if err != nil {
				return nil, err
			}

			if memoryEnforce == "soft" {
				err = cg.SetMemorySoftLimit(valueInt)
				if err != nil {
					return nil, err
				}
			} else {
				err = cg.SetMemoryLimit(valueInt)
				if err != nil {
					return nil, err
				}

				if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) {
					if util.IsTrueOrEmpty(memorySwap) || util.IsFalse(memorySwap) {
						err = cg.SetMemorySwapLimit(0)
						if err != nil {
							return nil, err
						}
					} else {
						// Additional memory as swap.
						swapInt, err := units.ParseByteSizeString(memorySwap)
						if err != nil {
							return nil, err
						}

						err = cg.SetMemorySwapLimit(swapInt)
						if err != nil {
							return nil, err
						}
					}
				}

				// If on CGroup1, set soft limit to value 10% less than hard limit.
				if slices.Contains([]cgroup.Layout{cgroup.CgroupsLegacy, cgroup.CgroupsHybrid}, d.state.OS.CGInfo.Layout) {
					err = cg.SetMemorySoftLimit(int64(float64(valueInt) * 0.9))
					if err != nil {
						return nil, err
					}
				}
			}
		}

		if d.state.OS.CGInfo.Supports(cgroup.MemorySwappiness, cg) {
			// Configure the swappiness
			if util.IsFalse(memorySwap) {
				err = cg.SetMemorySwappiness(0)
				if err != nil {
					return nil, err
				}
			} else if memorySwapPriority != "" {
				priority, err := strconv.Atoi(memorySwapPriority)
				if err != nil {
					return nil, err
				}

				// Maximum priority (10) should be default swappiness (60).
				err = cg.SetMemorySwappiness(int64(70 - priority))
				if err != nil {
					return nil, err
				}
			}
		}
	}

	// CPU limits
	cpuPriority := d.expandedConfig["limits.cpu.priority"]
	cpuAllowance := d.expandedConfig["limits.cpu.allowance"]

	if (cpuPriority != "" || cpuAllowance != "") && d.state.OS.CGInfo.Supports(cgroup.CPU, cg) {
		cpuShares, cpuCfsQuota, cpuCfsPeriod, err := cgroup.ParseCPU(cpuAllowance, cpuPriority)
		if err != nil {
			return nil, err
		}

		if cpuShares != 1024 {
			err = cg.SetCPUShare(cpuShares)
			if err != nil {
				return nil, err
			}
		}

		if cpuCfsPeriod != -1 && cpuCfsQuota != -1 {
			err = cg.SetCPUCfsLimit(cpuCfsPeriod, cpuCfsQuota)
			if err != nil {
				return nil, err
			}
		}
	}

	// Disk priority limits.
	diskPriority := d.ExpandedConfig()["limits.disk.priority"]
	if diskPriority != "" {
		if d.state.OS.CGInfo.Supports(cgroup.BlkioWeight, nil) {
			priorityInt, err := strconv.Atoi(diskPriority)
			if err != nil {
				return nil, err
			}

			priority := priorityInt * 100

			// Minimum valid value is 10
			if priority == 0 {
				priority = 10
			}

			err = cg.SetBlkioWeight(int64(priority))
			if err != nil {
				return nil, err
			}
		} else {
			return nil, errors.New("Cannot apply limits.disk.priority as blkio.weight cgroup controller is missing")
		}
	}

	// Processes
	if d.state.OS.CGInfo.Supports(cgroup.Pids, cg) {
		processes := d.expandedConfig["limits.processes"]
		if processes != "" {
			valueInt, err := strconv.ParseInt(processes, 10, 64)
			if err != nil {
				return nil, err
			}

			err = cg.SetMaxProcesses(valueInt)
			if err != nil {
				return nil, err
			}
		}
	}

	// Hugepages
	if d.state.OS.CGInfo.Supports(cgroup.Hugetlb, cg) {
		for i, key := range internalInstance.HugePageSizeKeys {
			value := d.expandedConfig[key]
			if value != "" {
				value, err := units.ParseByteSizeString(value)
				if err != nil {
					return nil, err
				}

				err = cg.SetHugepagesLimit(internalInstance.HugePageSizeSuffix[i], value)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	// Setup process limits
	for k, v := range d.expandedConfig {
		after, ok := strings.CutPrefix(k, "limits.kernel.")
		if ok {
			prlimitSuffix := after
			prlimitKey := fmt.Sprintf("lxc.prlimit.%s", prlimitSuffix)
			err = lxcSetConfigItem(cc, prlimitKey, v)
			if err != nil {
				return nil, err
			}
		}
	}

	// Setup sysctls
	for k, v := range d.expandedConfig {
		// gendoc:generate(entity=instance, group=miscellaneous, key=linux.sysctl.*)
		//
		// ---
		//  type: string
		//  liveupdate: no
		//  condition: container
		//  shortdesc: Override for the corresponding `sysctl` setting in the container
		after, ok := strings.CutPrefix(k, "linux.sysctl.")
		if ok {
			sysctlSuffix := after
			sysctlKey := fmt.Sprintf("lxc.sysctl.%s", sysctlSuffix)
			err = lxcSetConfigItem(cc, sysctlKey, v)
			if err != nil {
				return nil, err
			}
		}
	}

	// Setup shmounts
	if d.state.OS.LXCFeatures["mount_injection_file"] {
		err = lxcSetConfigItem(cc, "lxc.mount.auto", fmt.Sprintf("shmounts:%s:/dev/.incus-mounts", d.ShmountsPath()))
	} else {
		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s dev/.incus-mounts none bind,create=dir 0 0", d.ShmountsPath()))
	}

	if err != nil {
		return nil, err
	}

	if d.c != nil {
		_ = d.c.Release()
	}

	d.c = cc
	reverter.Success()

	return cc, err
}

var (
	idmappedStorageMap       map[unix.Fsid]idmap.StorageType = map[unix.Fsid]idmap.StorageType{}
	idmappedStorageMapString map[string]idmap.StorageType    = map[string]idmap.StorageType{}
	idmappedStorageMapLock   sync.Mutex
)

// IdmappedStorage determines if the container can use idmapped mounts.
func (d *lxc) IdmappedStorage(fspath string, fstype string) idmap.StorageType {
	var mode idmap.StorageType = idmap.StorageTypeNone
	var bindMount bool = fstype == "none" || fstype == ""

	if !d.state.OS.LXCFeatures["idmapped_mounts_v2"] || !d.state.OS.IdmappedMounts {
		return mode
	}

	buf := &unix.Statfs_t{}

	if bindMount {
		err := unix.Statfs(fspath, buf)
		if err != nil {
			d.logger.Error("Failed to statfs", logger.Ctx{"path": fspath, "err": err})
			return mode
		}
	}

	idmappedStorageMapLock.Lock()
	defer idmappedStorageMapLock.Unlock()

	if bindMount {
		val, ok := idmappedStorageMap[buf.Fsid]
		if ok {
			// Return recorded idmapping type.
			return val
		}
	} else {
		val, ok := idmappedStorageMapString[fstype]
		if ok {
			// Return recorded idmapping type.
			return val
		}
	}

	if idmap.CanIdmapMount(fspath, fstype) {
		// Use idmapped mounts.
		mode = idmap.StorageTypeIdmapped
	}

	if bindMount {
		idmappedStorageMap[buf.Fsid] = mode
	} else {
		idmappedStorageMapString[fstype] = mode
	}

	return mode
}

func (d *lxc) devIncusEventSend(eventType string, eventMessage map[string]any) error {
	event := jmap.Map{}
	event["type"] = eventType
	event["timestamp"] = time.Now()
	event["metadata"] = eventMessage

	return d.state.DevIncusEvents.Send(d.ID(), eventType, eventMessage)
}

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

// deviceStart loads a new device and calls its Start() function.
func (d *lxc) 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 container specific setup of device.
	if runConf != nil {
		// Shift device file ownership if needed before mounting into container.
		// This needs to be done whether or not container is running.
		if len(runConf.Mounts) > 0 {
			err := d.deviceStaticShiftMounts(runConf.Mounts)
			if err != nil {
				return nil, err
			}
		}

		// If container is running and then live attach device.
		if instanceRunning {
			// Attach mounts if requested.
			if len(runConf.Mounts) > 0 {
				err = d.deviceHandleMounts(runConf.Mounts)
				if err != nil {
					return nil, err
				}
			}

			// Add cgroup rules if requested.
			if len(runConf.CGroups) > 0 {
				err = d.deviceAddCgroupRules(runConf.CGroups)
				if err != nil {
					return nil, err
				}
			}

			// Attach network interface if requested.
			if len(runConf.NetworkInterface) > 0 {
				err = d.deviceAttachNIC(dev.Name(), configCopy, runConf.NetworkInterface)
				if err != nil {
					return nil, err
				}
			}

			// If running, run post start hooks now (if not running, 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
}

// deviceStaticShiftMounts statically shift device mount files ownership to active idmap if needed.
func (d *lxc) deviceStaticShiftMounts(mounts []deviceConfig.MountEntryItem) error {
	idmapSet, err := d.CurrentIdmap()
	if err != nil {
		return fmt.Errorf("Failed to get idmap for device: %s", err)
	}

	// If there is an idmap being applied and the daemon is not running in a user namespace then shift the
	// device files before they are mounted.
	if idmapSet != nil && !d.state.OS.RunningInUserNS {
		for _, mount := range mounts {
			// Skip UID/GID shifting if OwnerShift mode is not static, or the host-side
			// DevPath is empty (meaning an unmount request that doesn't need shifting).
			if mount.OwnerShift != deviceConfig.MountOwnerShiftStatic || mount.DevPath == "" {
				continue
			}

			err := idmapSet.ShiftPath(mount.DevPath, nil)
			if err != nil {
				// uidshift failing is weird, but not a big problem. Log and proceed.
				d.logger.Debug("Failed to uidshift device", logger.Ctx{"mountDevPath": mount.DevPath, "err": err})
			}
		}
	}

	return nil
}

// deviceAddCgroupRules live adds cgroup rules to a container.
func (d *lxc) deviceAddCgroupRules(cgroups []deviceConfig.RunConfigItem) error {
	cc, err := d.initLXC(false)
	if err != nil {
		return err
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return err
	}

	for _, rule := range cgroups {
		// Only apply devices cgroup rules if container is running privileged and host has devices cgroup controller.
		if strings.HasPrefix(rule.Key, "devices.") && (!d.isCurrentlyPrivileged() || d.state.OS.RunningInUserNS || !d.state.OS.CGInfo.Supports(cgroup.Devices, cg)) {
			continue
		}

		// Add the new device cgroup rule.
		err := d.CGroupSet(rule.Key, rule.Value)
		if err != nil {
			return fmt.Errorf("Failed to add cgroup rule for device: %w", err)
		}
	}

	return nil
}

// deviceAttachNIC live attaches a NIC device to a container.
func (d *lxc) deviceAttachNIC(devName string, configCopy map[string]string, netIF []deviceConfig.RunConfigItem) error {
	ctDevName := ""
	connected := true
	for _, dev := range netIF {
		if dev.Key == "link" {
			ctDevName = dev.Value
		} else if dev.Key == "connected" {
			connected = util.IsTrueOrEmpty(dev.Value)
		}
	}

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

	// Load the go-lxc struct.
	cc, err := d.initLXC(false)
	if err != nil {
		return err
	}

	// Add the interface to the container.
	err = cc.AttachInterface(ctDevName, configCopy["name"])
	if err != nil {
		return fmt.Errorf("Failed to attach interface: %s to %s: %w", ctDevName, configCopy["name"], err)
	}

	return d.setNICLink(devName, connected, true)
}

// deviceStop loads a new device and calls its Stop() function.
// Accepts a stopHookNetnsPath argument which is required when run from the onStopNS hook before the
// container's network namespace is unmounted (which is required for NIC device cleanup).
func (d *lxc) deviceStop(dev device.Device, instanceRunning bool, stopHookNetnsPath 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")
	}

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

	if runConf != nil {
		// If network interface settings returned, then detach NIC from container.
		if len(runConf.NetworkInterface) > 0 {
			err = d.deviceDetachNIC(configCopy, runConf.NetworkInterface, instanceRunning, stopHookNetnsPath)
			if err != nil {
				return err
			}
		}

		// Add cgroup rules if requested and container is running.
		if len(runConf.CGroups) > 0 && instanceRunning {
			err = d.deviceAddCgroupRules(runConf.CGroups)
			if err != nil {
				return err
			}
		}

		// Detach mounts if requested and container is running.
		if len(runConf.Mounts) > 0 && instanceRunning {
			err = d.deviceHandleMounts(runConf.Mounts)
			if err != nil {
				return err
			}
		}

		// 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 container.
// Accepts a stopHookNetnsPath argument which is required when run from the onStopNS hook before the
// container's network namespace is unmounted (which is required for NIC device cleanup).
func (d *lxc) deviceDetachNIC(configCopy map[string]string, netIF []deviceConfig.RunConfigItem, instanceRunning bool, stopHookNetnsPath string) error {
	// Get requested device name to detach interface back to on the host.
	devName := ""
	for _, dev := range netIF {
		if dev.Key == "link" {
			devName = dev.Value
			break
		}
	}

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

	// If container is running, perform live detach of interface back to host.
	if instanceRunning {
		// For some reason, having network config confuses detach, so get our own go-lxc struct.
		cname := project.Instance(d.Project().Name, d.Name())
		cc, err := liblxc.NewContainer(cname, d.state.OS.LxcPath)
		if err != nil {
			return err
		}

		defer func() { _ = cc.Release() }()

		// Get interfaces inside container.
		ifaces, err := cc.Interfaces()
		if err != nil {
			return fmt.Errorf("Failed to list network interfaces: %w", err)
		}

		// If interface doesn't exist inside container, cannot proceed.
		if !slices.Contains(ifaces, configCopy["name"]) {
			return nil
		}

		err = cc.DetachInterfaceRename(configCopy["name"], devName)
		if err != nil {
			return fmt.Errorf("Failed to detach interface: %q to %q: %w", configCopy["name"], devName, err)
		}
	} else {
		// Currently liblxc does not move devices back to the host on stop that were added
		// after the container was started. For this reason we utilise the lxc.hook.stop
		// hook so that we can capture the netns path, enter the namespace and move the nics
		// back to the host and rename them if liblxc hasn't already done it.
		// We can only move back devices that have an expected host_name record and where
		// that device doesn't already exist on the host as if a device exists on the host
		// we can't know whether that is because liblxc has moved it back already or whether
		// it is a conflicting device.
		if !util.PathExists(fmt.Sprintf("/sys/class/net/%s", devName)) {
			if stopHookNetnsPath == "" {
				return fmt.Errorf("Cannot detach NIC device %q without stopHookNetnsPath being provided", devName)
			}

			err := d.detachInterfaceRename(stopHookNetnsPath, configCopy["name"], devName)
			if err != nil {
				return fmt.Errorf("Failed to detach interface: %q to %q: %w", configCopy["name"], devName, err)
			}

			d.logger.Debug("Detached NIC device interface", logger.Ctx{"name": configCopy["name"], "device": devName})
		}
	}

	return nil
}

// deviceHandleMounts live attaches or detaches mounts on a container.
// If the mount DevPath is empty the mount action is treated as unmount.
func (d *lxc) deviceHandleMounts(mounts []deviceConfig.MountEntryItem) error {
	for _, mount := range mounts {
		if mount.DevPath != "" {
			flags := 0

			// Convert options into flags.
			for _, opt := range mount.Opts {
				if opt == "bind" {
					flags |= unix.MS_BIND
				} else if opt == "rbind" {
					flags |= unix.MS_BIND | unix.MS_REC
				} else if opt == "ro" {
					flags |= unix.MS_RDONLY
				}
			}

			var idmapType idmap.StorageType = idmap.StorageTypeNone
			if !d.IsPrivileged() && mount.OwnerShift == deviceConfig.MountOwnerShiftDynamic {
				idmapType = d.IdmappedStorage(mount.DevPath, mount.FSType)
				if idmapType == idmap.StorageTypeNone {
					return errors.New("Required idmapping abilities not available")
				}
			}

			// Mount it into the container.
			err := d.insertMount(mount.DevPath, mount.TargetPath, mount.FSType, flags, idmapType)
			if err != nil {
				return fmt.Errorf("Failed to add mount for device inside container: %s", err)
			}
		} else {
			relativeTargetPath := strings.TrimPrefix(mount.TargetPath, "/")

			// Connect to files API.
			files, err := d.FileSFTP()
			if err != nil {
				return err
			}

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

			_, err = files.Lstat(relativeTargetPath)
			if err == nil {
				err := d.removeMount(mount.TargetPath)
				if err != nil {
					return fmt.Errorf("Error unmounting the device path inside container: %s", err)
				}

				// Only remove mountpoints created in /dev.
				if strings.HasPrefix(mount.TargetPath, "dev/") {
					err := files.Remove(relativeTargetPath)
					if err != nil {
						return err
					}
				}
			}
		}
	}

	return nil
}

// DeviceEventHandler actions the results of a RunConfig after an event has occurred on a device.
func (d *lxc) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {
	// Device events can only be processed when the container is running.
	// We use InitPID here rather than IsRunning because this task can be triggered during the
	// container startup process, which is during the time that the start lock is held, which causes
	// IsRunning to return false (because the container hasn't fully started yet).
	if d.InitPID() <= 0 {
		return nil
	}

	if runConf == nil {
		return nil
	}

	// Shift device file ownership if needed before mounting devices into container.
	if len(runConf.Mounts) > 0 {
		err := d.deviceStaticShiftMounts(runConf.Mounts)
		if err != nil {
			return err
		}

		err = d.deviceHandleMounts(runConf.Mounts)
		if err != nil {
			return err
		}
	}

	// Add cgroup rules if requested.
	if len(runConf.CGroups) > 0 {
		err := d.deviceAddCgroupRules(runConf.CGroups)
		if err != nil {
			return 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 != "" {
		err := d.setNICLink(devName, connected, false)
		if err != nil {
			return err
		}
	}

	// Run any post hooks requested.
	err := d.runHooks(runConf.PostHooks)
	if err != nil {
		return err
	}

	// Generate uevent inside container if requested.
	if len(runConf.Uevents) > 0 {
		pidFd := d.inheritInitPidFd()
		pidFdNr := "-1"
		if pidFd != nil {
			defer func() { _ = pidFd.Close() }()
			pidFdNr = "3"
		}

		for _, eventParts := range runConf.Uevents {
			length := 0
			for _, part := range eventParts {
				length = length + len(part) + 1
			}

			args := []string{
				"forkuevent",
				"inject",
				"--",
				fmt.Sprintf("%d", d.InitPID()),
				pidFdNr,
				fmt.Sprintf("%d", length),
			}

			args = append(args, eventParts...)

			_, _, err := subprocess.RunCommandSplit(
				context.TODO(),
				nil,
				[]*os.File{pidFd},
				d.state.OS.ExecPath,
				args...)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *lxc) handleIdmappedStorage() (idmap.StorageType, *idmap.Set, error) {
	diskIdmap, err := d.DiskIdmap()
	if err != nil {
		return idmap.StorageTypeNone, nil, fmt.Errorf("Set last ID map: %w", err)
	}

	nextIdmap, err := d.NextIdmap()
	if err != nil {
		return idmap.StorageTypeNone, nil, fmt.Errorf("Set ID map: %w", err)
	}

	// Identical on-disk idmaps so no changes required.
	if nextIdmap.Equals(diskIdmap) {
		return idmap.StorageTypeNone, nextIdmap, nil
	}

	// There's no on-disk idmap applied and the container can use idmapped
	// storage.
	idmapType := d.IdmappedStorage(d.RootfsPath(), "none")
	if diskIdmap == nil && idmapType != idmap.StorageTypeNone {
		return idmapType, nextIdmap, nil
	}

	// We need to change the on-disk idmap but the container is protected
	// against idmap changes.
	if util.IsTrue(d.expandedConfig["security.protection.shift"]) {
		return idmap.StorageTypeNone, nil, errors.New("Container is protected against filesystem shifting")
	}

	d.logger.Debug("Container idmap changed, remapping")
	d.updateProgress("Remapping container filesystem")

	storageType, err := d.getStorageType()
	if err != nil {
		return idmap.StorageTypeNone, nil, fmt.Errorf("Storage type: %w", err)
	}

	// Revert the currently applied on-disk idmap.
	if diskIdmap != nil {
		if storageType == "zfs" {
			err = diskIdmap.UnshiftPath(d.RootfsPath(), storageDrivers.ShiftZFSSkipper)
		} else if storageType == "btrfs" {
			err = storageDrivers.UnshiftBtrfsRootfs(d.RootfsPath(), diskIdmap)
		} else {
			err = diskIdmap.UnshiftPath(d.RootfsPath(), nil)
		}

		if err != nil {
			return idmap.StorageTypeNone, nil, err
		}
	}

	jsonDiskIdmap := "[]"

	// If the container can't use idmapped storage apply the new on-disk
	// idmap of the container now. Otherwise we will later instruct LXC to
	// make use of idmapped storage.
	if nextIdmap != nil && idmapType == idmap.StorageTypeNone {
		if storageType == "zfs" {
			err = nextIdmap.ShiftPath(d.RootfsPath(), storageDrivers.ShiftZFSSkipper)
		} else if storageType == "btrfs" {
			err = storageDrivers.ShiftBtrfsRootfs(d.RootfsPath(), nextIdmap)
		} else {
			err = nextIdmap.ShiftPath(d.RootfsPath(), nil)
		}

		if err != nil {
			return idmap.StorageTypeNone, nil, err
		}

		idmapJSON, err := nextIdmap.ToJSON()
		if err != nil {
			return idmap.StorageTypeNone, nil, err
		}

		jsonDiskIdmap = idmapJSON
	}

	err = d.VolatileSet(map[string]string{"volatile.last_state.idmap": jsonDiskIdmap})
	if err != nil {
		return idmap.StorageTypeNone, nextIdmap, fmt.Errorf("Set volatile.last_state.idmap config key on container %q (id %d): %w", d.name, d.id, err)
	}

	d.updateProgress("")
	return idmapType, nextIdmap, nil
}

// Start functions.
func (d *lxc) startCommon() (string, []func() error, error) {
	postStartHooks := []func() error{}

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

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

	// Check if idmap needs changing.
	if !d.IsPrivileged() {
		nextMap, err := d.NextIdmap()
		if err != nil {
			return "", nil, err
		}

		// Check if we need to change idmap.
		if nextMap != nil && d.state.OS.IdmapSet != nil && !d.state.OS.IdmapSet.Includes(nextMap) {
			// Update the idmap.
			idmapSet, base, err := d.findIdmap()
			if err != nil {
				return "", nil, fmt.Errorf("Failed to get ID map: %w", err)
			}

			idmapSetJSON, err := idmapSet.ToJSON()
			if err != nil {
				return "", nil, fmt.Errorf("Failed to encode ID map: %w", err)
			}

			err = d.VolatileSet(map[string]string{
				"volatile.idmap.next": idmapSetJSON,
				"volatile.idmap.base": fmt.Sprintf("%v", base),
			})
			if err != nil {
				return "", nil, fmt.Errorf("Failed to update volatile idmap: %w", err)
			}

			// Invalidate the idmap cache.
			d.idmapset = nil
		}
	}

	// Load the go-lxc struct
	cc, err := d.initLXC(true)
	if err != nil {
		return "", nil, fmt.Errorf("Load go-lxc struct: %w", err)
	}

	// gendoc:generate(entity=image, group=requirements, key=requirements.cgroup)
	//
	// ---
	//  type: string
	//  shortdesc: If set to `v1`, indicates that the image requires the host to run cgroup v1.
	//
	// Ensure cgroup v1 configuration is set appropriately with the image using systemd
	if d.localConfig["image.requirements.cgroup"] == "v1" && !util.PathExists("/sys/fs/cgroup/systemd") {
		return "", nil, errors.New("The image used by this instance requires a CGroupV1 host system")
	}

	// gendoc:generate(entity=image, group=requirements, key=requirements.privileged)
	//
	// ---
	//  type: bool
	//  shortdesc: If set to `false`, indicates that the image cannot work as a privileged container.
	//
	// Ensure privileged is turned off for images that cannot work privileged
	if util.IsFalse(d.localConfig["image.requirements.privileged"]) && util.IsTrue(d.expandedConfig["security.privileged"]) {
		return "", nil, errors.New("The image used by this instance is incompatible with privileged containers. Please unset security.privileged on the instance")
	}

	// Load any required kernel modules
	kernelModules := d.expandedConfig["linux.kernel_modules"]
	if kernelModules != "" {
		for _, module := range strings.Split(kernelModules, ",") {
			module = strings.TrimPrefix(module, " ")
			err := linux.LoadModule(module)
			if err != nil {
				return "", nil, fmt.Errorf("Failed to load kernel module '%s': %w", module, err)
			}
		}
	}

	// Rotate the log file.
	logfile := d.LogFilePath()
	if util.PathExists(logfile) {
		_ = os.Remove(logfile + ".old")
		err := os.Rename(logfile, logfile+".old")
		if err != nil && !errors.Is(err, fs.ErrNotExist) {
			return "", nil, err
		}
	}

	// Wait for any file operations to complete.
	// This is to avoid having an active mount by forkfile and so all file operations
	// from this point will use the container's namespace rather than a chroot.
	d.stopForkfile(false)

	// Mount instance root volume.
	mountInfo, err := d.mount()
	if err != nil {
		return "", nil, err
	}

	// Handle post hooks.
	postStartHooks = append(postStartHooks, func() error {
		for _, hook := range mountInfo.PostHooks {
			err := hook(d)
			if err != nil {
				return err
			}
		}

		return nil
	})

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

	idmapType, nextIdmap, err := d.handleIdmappedStorage()
	if err != nil {
		return "", nil, fmt.Errorf("Failed to handle idmapped storage: %w", err)
	}

	nextIdmapJSON, err := nextIdmap.ToJSON()
	if err != nil {
		return "", nil, fmt.Errorf("Failed to encode ID map: %w", err)
	}

	if d.localConfig["volatile.idmap.current"] != nextIdmapJSON {
		err = d.VolatileSet(map[string]string{"volatile.idmap.current": nextIdmapJSON})
		if err != nil {
			return "", nil, fmt.Errorf("Set volatile.idmap.current config key on container %q (id %d): %w", d.name, d.id, err)
		}
	}

	// Generate the Seccomp profile
	err = seccomp.CreateProfile(d.state, d)
	if err != nil {
		return "", nil, err
	}

	// Cleanup any existing leftover devices
	_ = d.removeUnixDevices()
	_ = d.removeDiskDevices()

	// Create any missing directories.
	err = os.MkdirAll(d.LogPath(), 0o700)
	if err != nil {
		return "", nil, err
	}

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

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

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

	volatileSet := make(map[string]string)

	// 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 container instance, we must also set the generation UUID.
	genUUID := d.localConfig["volatile.uuid.generation"]
	if genUUID == "" {
		genUUID = instUUID
		volatileSet["volatile.uuid.generation"] = genUUID
	}

	// Create the devices
	nicID := -1
	nvidiaDevices := []string{}

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

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

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

		// Stop device on failure to setup container.
		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)
		}

		// Process rootfs setup.
		if runConf.RootFS.Path != "" {
			if !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 1, 0) {
				// Set the rootfs backend type if supported (must happen before any other lxc.rootfs)
				err := lxcSetConfigItem(cc, "lxc.rootfs.backend", "dir")
				if err == nil {
					value := cc.ConfigItem("lxc.rootfs.backend")
					if len(value) == 0 || value[0] != "dir" {
						_ = lxcSetConfigItem(cc, "lxc.rootfs.backend", "")
					}
				}
			}

			// Get an absolute path for the rootfs (avoid constantly traversing the symlink).
			absoluteRootfs, err := filepath.EvalSymlinks(runConf.RootFS.Path)
			if err != nil {
				return "", nil, fmt.Errorf("Unable to resolve container rootfs: %w", err)
			}

			if liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 1, 0) {
				rootfsPath := fmt.Sprintf("dir:%s", absoluteRootfs)
				err = lxcSetConfigItem(cc, "lxc.rootfs.path", rootfsPath)
			} else {
				err = lxcSetConfigItem(cc, "lxc.rootfs", absoluteRootfs)
			}

			if err != nil {
				return "", nil, fmt.Errorf("Failed to setup device rootfs %q: %w", dev.Name(), err)
			}

			if len(runConf.RootFS.Opts) > 0 {
				err = lxcSetConfigItem(cc, "lxc.rootfs.options", strings.Join(runConf.RootFS.Opts, ","))
				if err != nil {
					return "", nil, fmt.Errorf("Failed to setup device rootfs %q: %w", dev.Name(), err)
				}
			}

			if !d.IsPrivileged() && idmapType == idmap.StorageTypeIdmapped {
				err = lxcSetConfigItem(cc, "lxc.rootfs.options", "idmap=container")
				if err != nil {
					return "", nil, fmt.Errorf("Failed to set \"idmap=container\" rootfs option: %w", err)
				}
			}
		}

		// Pass any cgroups rules into LXC.
		if len(runConf.CGroups) > 0 {
			for _, rule := range runConf.CGroups {
				if strings.HasPrefix(rule.Key, "devices.") && (!d.isCurrentlyPrivileged() || d.state.OS.RunningInUserNS) {
					continue
				}

				if d.state.OS.CGInfo.Layout == cgroup.CgroupsUnified {
					err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.cgroup2.%s", rule.Key), rule.Value)
				} else {
					err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.cgroup.%s", rule.Key), rule.Value)
				}

				if err != nil {
					return "", nil, fmt.Errorf("Failed to setup device cgroup %q: %w", dev.Name(), err)
				}
			}
		}

		// Pass any mounts into LXC.
		if len(runConf.Mounts) > 0 {
			escapePathFstab := func(path string) string {
				r := strings.NewReplacer(
					" ", "\\040",
					"\t", "\\011",
					"\n", "\\012",
					"\\", "\\\\")
				return r.Replace(path)
			}

			for _, mount := range runConf.Mounts {
				if slices.Contains(mount.Opts, "propagation") && !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 3, 0, 0) {
					return "", nil, fmt.Errorf("Failed to setup device mount %q: %w", dev.Name(), errors.New("liblxc 3.0 is required for mount propagation configuration"))
				}

				mntOptions := strings.Join(mount.Opts, ",")

				if !d.IsPrivileged() && mount.OwnerShift == deviceConfig.MountOwnerShiftDynamic {
					switch d.IdmappedStorage(mount.DevPath, mount.FSType) {
					case idmap.StorageTypeIdmapped:
						mntOptions = strings.Join([]string{mntOptions, "idmap=container"}, ",")
					case idmap.StorageTypeNone:
						return "", nil, fmt.Errorf("Failed to setup device mount %q: %w", dev.Name(), errors.New("idmapping abilities are required but aren't supported on system"))
					}
				}

				mntVal := fmt.Sprintf("%s %s %s %s %d %d", escapePathFstab(mount.DevPath), escapePathFstab(mount.TargetPath), mount.FSType, mntOptions, mount.Freq, mount.PassNo)
				err = lxcSetConfigItem(cc, "lxc.mount.entry", mntVal)
				if err != nil {
					return "", nil, fmt.Errorf("Failed to setup device mount %q: %w", dev.Name(), err)
				}
			}
		}

		// Pass any network setup config into LXC.
		if len(runConf.NetworkInterface) > 0 {
			// Increment nicID so that LXC network index is unique per device.
			nicID++

			networkKeyPrefix := "lxc.net"
			if !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 1, 0) {
				networkKeyPrefix = "lxc.network"
			}

			for _, nicItem := range runConf.NetworkInterface {
				// The connected state is not a LXC configuration key; we defer its handling to a post hook.
				if nicItem.Key == "connected" {
					runConf.PostHooks = append(runConf.PostHooks, func() error {
						return d.setNICLink(dev.Name(), util.IsTrueOrEmpty(nicItem.Value), true)
					})

					continue
				}

				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.%s", networkKeyPrefix, nicID, nicItem.Key), nicItem.Value)
				if err != nil {
					return "", nil, fmt.Errorf("Failed to setup device network interface %q: %w", dev.Name(), err)
				}
			}
		}

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

		// Build list of NVIDIA GPUs (used for MIG).
		if len(runConf.GPUDevice) > 0 {
			for _, entry := range runConf.GPUDevice {
				if entry.Key == device.GPUNvidiaDeviceKey {
					nvidiaDevices = append(nvidiaDevices, entry.Value)
				}
			}
		}
	}

	// Initialize the credentials directory.
	err = d.setupCredentials(false)
	if err != nil {
		return "", nil, err
	}

	// Override NVIDIA_VISIBLE_DEVICES if we have devices that need it.
	if len(nvidiaDevices) > 0 {
		err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("NVIDIA_VISIBLE_DEVICES=%s", strings.Join(nvidiaDevices, ",")))
		if err != nil {
			return "", nil, fmt.Errorf("Unable to set NVIDIA_VISIBLE_DEVICES in LXC environment: %w", err)
		}
	}

	// Handle application containers.
	if util.PathExists(filepath.Join(d.Path(), "config.json")) {
		// Parse the OCI config.
		data, err := os.ReadFile(filepath.Join(d.Path(), "config.json"))
		if err != nil {
			return "", nil, err
		}

		var config ociSpecs.Spec
		err = json.Unmarshal([]byte(data), &config)
		if err != nil {
			return "", nil, err
		}

		// Mark the container as an OCI container if not already set.
		if !util.IsTrue(d.expandedConfig["volatile.container.oci"]) {
			volatileSet["volatile.container.oci"] = "true"
		}

		// Allow unprivileged users to use ping (requires a 6.6 kernel at least).
		minVer, _ := version.NewDottedVersion("6.6.0")
		if !d.state.OS.RunningInUserNS && d.state.OS.KernelVersion.Compare(minVer) >= 0 {
			maxGid := int64(4294967294)

			if !d.IsPrivileged() {
				maxGid = 0
				idMap, err := d.CurrentIdmap()
				if err != nil {
					return "", nil, err
				}

				for _, entry := range idMap.Entries {
					if entry.NSID+entry.MapRange-1 > maxGid {
						maxGid = entry.NSID + entry.MapRange - 1
					}
				}
			}

			err = lxcSetConfigItem(cc, "lxc.sysctl.net.ipv4.ping_group_range", fmt.Sprintf("0 %d", maxGid))
			if err != nil {
				return "", nil, err
			}
		}

		// Allow unprivileged users to use low ports.
		err = lxcSetConfigItem(cc, "lxc.sysctl.net.ipv4.ip_unprivileged_port_start", "0")
		if err != nil {
			return "", nil, err
		}

		// Configure the entry point.
		entrypoint := config.Process.Args
		if d.expandedConfig["oci.entrypoint"] != "" {
			entrypoint, err = shellquote.Split(d.expandedConfig["oci.entrypoint"])
			if err != nil {
				return "", nil, err
			}
		}

		// Compute the entrypoint string.
		initCmd := shellquote.Join(entrypoint...)

		// As we feed this to execve and not to a real shell, un-escape some sequences.
		initCmd = strings.ReplaceAll(initCmd, "\\(", "(")
		initCmd = strings.ReplaceAll(initCmd, "\\)", ")")

		if len(entrypoint) > 0 && slices.Contains([]string{"/init", "/sbin/init", "/s6-init", "/usr/bin/init"}, entrypoint[0]) {
			// For regular init systems, call them directly as PID1.
			err = lxcSetConfigItem(cc, "lxc.init.cmd", initCmd)
			if err != nil {
				return "", nil, err
			}
		} else {
			// For anything else, run them under our own PID1.
			err = lxcSetConfigItem(cc, "lxc.execute.cmd", initCmd)
			if err != nil {
				return "", nil, err
			}
		}

		// Configure the cwd.
		if d.expandedConfig["oci.cwd"] != "" {
			err = lxcSetConfigItem(cc, "lxc.init.cwd", d.expandedConfig["oci.cwd"])
			if err != nil {
				return "", nil, err
			}
		} else {
			err = lxcSetConfigItem(cc, "lxc.init.cwd", config.Process.Cwd)
			if err != nil {
				return "", nil, err
			}
		}

		// Configure the UID
		if d.expandedConfig["oci.uid"] != "" {
			err = lxcSetConfigItem(cc, "lxc.init.uid", d.expandedConfig["oci.uid"])
			if err != nil {
				return "", nil, err
			}
		} else {
			err = lxcSetConfigItem(cc, "lxc.init.uid", fmt.Sprintf("%d", config.Process.User.UID))
			if err != nil {
				return "", nil, err
			}
		}

		// Configure the GID
		if d.expandedConfig["oci.gid"] != "" {
			err = lxcSetConfigItem(cc, "lxc.init.gid", d.expandedConfig["oci.gid"])
			if err != nil {
				return "", nil, err
			}
		} else {
			err = lxcSetConfigItem(cc, "lxc.init.gid", fmt.Sprintf("%d", config.Process.User.GID))
			if err != nil {
				return "", nil, err
			}
		}

		// Get all mounts so far.
		lxcMounts := []string{"/dev", "/proc", "/sys", "/sys/fs/cgroup"}
		for _, mount := range cc.ConfigItem("lxc.mount.entry") {
			fields := strings.Split(mount, " ")
			if len(fields) < 2 || fields[1][0] == '/' {
				continue
			}

			lxcMounts = append(lxcMounts, filepath.Clean(fmt.Sprintf("/%s", fields[1])))
		}

		// Configure mounts.
		for _, mount := range config.Mounts {
			// We only support simple tmpfs at this stage.
			if len(mount.UIDMappings) > 0 || len(mount.GIDMappings) > 0 || mount.Type != "tmpfs" {
				continue
			}

			// Skip all our own mounts.
			if slices.Contains(lxcMounts, filepath.Clean(mount.Destination)) {
				continue
			}

			err := lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s %s %s 0 0", mount.Source, strings.TrimLeft(mount.Destination, "/"), mount.Type, strings.Join(append(mount.Options, "create=dir"), ",")))
			if err != nil {
				return "", nil, err
			}

			lxcMounts = append(lxcMounts, mount.Destination)
		}

		// Mount /run as a tmpfs if it exists and isn't already mounted.
		if !slices.Contains(lxcMounts, "/run") {
			err := lxcSetConfigItem(cc, "lxc.mount.entry", "none run tmpfs none,mode=755,optional")
			if err != nil {
				return "", nil, err
			}
		}

		// Configure network handling.
		err = os.MkdirAll(filepath.Join(d.Path(), "network"), 0o711)
		if err != nil {
			return "", nil, err
		}

		err = os.MkdirAll(filepath.Join(d.RootfsPath(), "etc"), 0o755)
		if err != nil && !os.IsExist(err) {
			return "", nil, err
		}

		err = os.WriteFile(filepath.Join(d.Path(), "network", "hosts"), fmt.Appendf(nil, `127.0.0.1   localhost
127.0.1.1   %s

::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`, d.name), 0o644)
		if err != nil {
			return "", nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s etc/hosts none bind,create=file", filepath.Join(d.Path(), "network", "hosts")))
		if err != nil {
			return "", nil, err
		}

		err = os.WriteFile(filepath.Join(d.Path(), "network", "hostname"), fmt.Appendf(nil, "%s\n", d.name), 0o644)
		if err != nil {
			return "", nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s etc/hostname none bind,create=file", filepath.Join(d.Path(), "network", "hostname")))
		if err != nil {
			return "", nil, err
		}

		f, err := os.OpenFile(filepath.Join(d.Path(), "network", "resolv.conf"), os.O_RDWR|os.O_CREATE, 0o644)
		if err != nil {
			return "", nil, err
		}

		f.Close()

		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s etc/resolv.conf none bind,create=file", filepath.Join(d.Path(), "network", "resolv.conf")))
		if err != nil {
			return "", nil, err
		}

		forknetDhcpLogfilePath := filepath.Join(d.LogPath(), "forknet-dhcp.log")
		forknetDhcpLogfile, err := os.Create(forknetDhcpLogfilePath)
		if err != nil {
			return "", nil, err
		}

		err = forknetDhcpLogfile.Close()
		if err != nil {
			return "", nil, err
		}

		err = lxcSetConfigItem(cc, "lxc.hook.start-host", fmt.Sprintf(
			"/proc/%d/exe forknet dhcp %s %s",
			os.Getpid(),
			filepath.Join(d.Path(), "network"),
			forknetDhcpLogfilePath,
		))
		if err != nil {
			return "", nil, err
		}
	} else {
		// Clear OCI config key if present.
		if d.expandedConfig["volatile.container.oci"] != "" {
			volatileSet["volatile.container.oci"] = ""
		}
	}

	// Check if we should start a dedicated LXCFS.
	if d.state.GlobalConfig.InstancesLXCFSPerInstance() {
		if !util.PathExists(filepath.Join(d.RunPath(), "lxcfs", "proc")) {
			// Make sure all the paths exist.
			err := os.Mkdir(filepath.Join(d.DevicesPath(), "lxcfs"), 0o711)
			if err != nil && !os.IsExist(err) {
				return "", nil, err
			}

			err = os.Mkdir(filepath.Join(d.RunPath(), "lxcfs"), 0o700)
			if err != nil && !os.IsExist(err) {
				return "", nil, err
			}

			// Prepare a new LXCFS instance.
			args := []string{
				"-f",
				"-p", filepath.Join(d.RunPath(), "lxcfs.pid"),
				"--runtime-dir", filepath.Join(d.RunPath(), "lxcfs"),
			}

			if os.Getenv("LXCFS_OPTS") != "" {
				userArgs, err := shellquote.Split(os.Getenv("LXCFS_OPTS"))
				if err != nil {
					return "", nil, err
				}

				args = append(args, userArgs...)
			}

			args = append(args, filepath.Join(d.DevicesPath(), "lxcfs"))

			lxcfs, err := subprocess.NewProcess("lxcfs", args, "", "")
			if err != nil {
				return "", nil, err
			}

			// Start LXCFS.
			err = lxcfs.Start(context.TODO())
			if err != nil {
				return "", nil, err
			}

			// Write down our process tracking.
			err = lxcfs.Save(filepath.Join(d.RunPath(), "lxcfs.yaml"))
			if err != nil {
				return "", nil, err
			}
		}

		// Over-mount the system LXCFS (if found).
		for _, entry := range []string{"/var/lib/lxcfs", "/var/lib/incus-lxcfs"} {
			if !util.PathExists(entry) {
				continue
			}

			err = lxcSetConfigItem(cc, "lxc.hook.pre-mount", fmt.Sprintf("mount -o bind %s %s/", filepath.Join(d.DevicesPath(), "lxcfs"), entry))
			if err != nil {
				return "", nil, err
			}
		}
	}

	// Setup BPF token delegation if enabled
	bpfConfig := d.bpfTokenConfig()
	if bpfConfig.enable {
		err = lxcSetConfigItem(cc, "lxc.hook.start-host", shellquote.Join(
			fmt.Sprintf("/proc/%d/exe", os.Getpid()),
			"forkbpf",
			bpfConfig.mountPath,
			bpfConfig.cmdTypes,
			bpfConfig.mapTypes,
			bpfConfig.progTypes,
			bpfConfig.attachTypes,
		))
		if err != nil {
			return "", nil, err
		}
	}

	// Load the LXC raw config.
	err = d.loadRawLXCConfig(cc)
	if err != nil {
		return "", nil, err
	}

	// Generate the LXC config
	configPath := filepath.Join(d.RunPath(), "lxc.conf")
	err = cc.SaveConfigFile(configPath)
	if err != nil {
		_ = os.Remove(configPath)
		return "", nil, err
	}

	// Set ownership to match container root
	currentIdmapset, err := d.CurrentIdmap()
	if err != nil {
		return "", nil, err
	}

	uid := int64(0)
	if currentIdmapset != nil {
		uid, _ = currentIdmapset.ShiftFromNS(0, 0)
	}

	err = os.Chown(d.Path(), int(uid), 0)
	if err != nil {
		return "", nil, err
	}

	// We only need traversal by root in the container
	err = os.Chmod(d.Path(), 0o100)
	if err != nil {
		return "", nil, err
	}

	// If starting stateless, wipe state
	if !d.IsStateful() && util.PathExists(d.StatePath()) {
		_ = os.RemoveAll(d.StatePath())
	}

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

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

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

	// 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 {
		return "", nil, err
	}

	reverter.Success()

	return configPath, postStartHooks, nil
}

type bpfTokenConfig struct {
	enable      bool
	mountPath   string
	cmdTypes    string
	mapTypes    string
	progTypes   string
	attachTypes string
}

func (d *lxc) bpfTokenConfig() bpfTokenConfig {
	if util.IsTrue(d.expandedConfig["security.privileged"]) {
		return bpfTokenConfig{}
	}

	cfg := bpfTokenConfig{
		mountPath:   d.expandedConfig["security.bpffs.path"],
		cmdTypes:    d.expandedConfig["security.bpffs.delegate_cmds"],
		mapTypes:    d.expandedConfig["security.bpffs.delegate_maps"],
		progTypes:   d.expandedConfig["security.bpffs.delegate_progs"],
		attachTypes: d.expandedConfig["security.bpffs.delegate_attachs"],
	}

	if cfg.cmdTypes != "" || cfg.mapTypes != "" || cfg.progTypes != "" || cfg.attachTypes != "" {
		cfg.enable = true
	}

	if cfg.mountPath == "" {
		cfg.mountPath = "/sys/fs/bpf"
	}

	return cfg
}

// detachInterfaceRename enters the container's network namespace and moves the named interface
// in ifName back to the network namespace of the running process as the name specified in hostName.
func (d *lxc) detachInterfaceRename(netns string, ifName string, hostName string) error {
	daemonPID := os.Getpid()

	// Run forknet detach
	_, err := subprocess.RunCommand(
		d.state.OS.ExecPath,
		"forknet",
		"detach",
		"--",
		netns,
		fmt.Sprintf("%d", daemonPID),
		ifName,
		hostName,
	)
	// Process forknet detach response
	if err != nil {
		return err
	}

	return nil
}

// Start starts the instance.
func (d *lxc) Start(stateful bool) error {
	// Check that migration.stateful is set for stateful actions.
	if stateful && !d.CanLiveMigrate() {
		return errors.New("Stateful start requires that the instance migration.stateful be set to true")
	}

	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.
	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(nil)

	if !daemon.SharedMountsSetup {
		err = errors.New("Daemon failed to setup shared mounts base. Does security.nesting need to be turned on?")
		op.Done(err)
		return err
	}

	ctxMap := logger.Ctx{
		"action":    op.Action(),
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
		"stateful":  stateful,
	}

	if op.Action() == "start" {
		d.logger.Info("Starting instance", ctxMap)
	}

	// If stateful, restore now.
	if stateful && d.stateful {
		d.logger.Info("Restoring stateful checkpoint")

		criuMigrationArgs := instance.CriuMigrationArgs{
			Cmd:          liblxc.MIGRATE_RESTORE,
			StateDir:     d.StatePath(),
			Function:     "snapshot",
			Stop:         false,
			ActionScript: false,
			DumpDir:      "",
			PreDumpDir:   "",
		}

		err = d.migrate(&criuMigrationArgs)
		if err != nil && !d.IsRunning() {
			op.Done(err)
			return fmt.Errorf("Failed restoring stateful checkpoint: %w", err)
		}

		_ = os.RemoveAll(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("Failed clearing instance stateful flag: %w", err)
		}

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

		return nil
	} else if d.stateful {
		/* stateless start required when we have state, let's delete it */
		err := os.RemoveAll(d.StatePath())
		if err != nil {
			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("Failed clearing instance stateful flag: %w", err)
		}
	}

	// Run the shared start code.
	configPath, postStartHooks, err := d.startCommon()
	if err != nil {
		op.Done(err)
		return err
	}

	name := project.Instance(d.Project().Name, d.name)

	// Setup minimal environment for forkstart.
	envDict := map[string]string{
		"container": "lxc",
	}

	for k, v := range d.expandedConfig {
		after, ok := strings.CutPrefix(k, "environment.")
		if ok {
			envDict[after] = v
		}
	}

	for _, keepEnv := range []string{"LD_LIBRARY_PATH", "INCUS_DIR", "INCUS_SOCKET"} {
		if os.Getenv(keepEnv) != "" {
			envDict[keepEnv] = os.Getenv(keepEnv)
		}
	}

	_, ok := envDict["PATH"]
	if !ok {
		envDict["PATH"] = os.Getenv("PATH")
	}

	env := make([]string, 0, len(envDict))
	for k, v := range envDict {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}

	// Start the LXC container.
	_, _, err = subprocess.RunCommandSplit(
		context.TODO(),
		env,
		nil,
		d.state.OS.ExecPath,
		"forkstart",
		name,
		d.state.OS.LxcPath,
		configPath,
		d.LogPath())
	if err != nil && !d.IsRunning() {
		// Attempt to extract the LXC errors
		lxcLog := ""
		logPath := filepath.Join(d.LogPath(), "lxc.log")
		if util.PathExists(logPath) {
			logContent, err := os.ReadFile(logPath)
			if err == nil {
				for _, line := range strings.Split(string(logContent), "\n") {
					fields := strings.Fields(line)
					if len(fields) < 4 {
						continue
					}

					// We only care about errors
					if fields[2] != "ERROR" {
						continue
					}

					// Prepend the line break
					if len(lxcLog) == 0 {
						lxcLog += "\n"
					}

					lxcLog += fmt.Sprintf("  %s\n", strings.Join(fields[0:], " "))
				}
			}
		}

		d.logger.Error("Failed starting instance", ctxMap)

		// Return the actual error
		op.Done(err)
		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.

		// Attempt to stop container.
		_ = 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.logger.Info("Started instance", ctxMap)
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStarted.Event(d, nil))
	}

	return nil
}

// OnHook is the top-level hook handler.
func (d *lxc) OnHook(hookName string, args map[string]string) error {
	switch hookName {
	case instance.HookStart:
		return d.onStart(args)
	case instance.HookStopNS:
		return d.onStopNS(args)
	case instance.HookStop:
		return d.onStop(args)
	default:
		return instance.ErrNotImplemented
	}
}

// onStart implements the start hook.
func (d *lxc) onStart(_ map[string]string) error {
	// Make sure we can't call go-lxc functions by mistake
	d.fromHook = true

	// Load the container AppArmor profile
	err := apparmor.InstanceLoad(d.state.OS, d, nil)
	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]))
		if err != nil {
			_ = apparmor.InstanceUnload(d.state.OS, d)
			return err
		}

		err := d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			// Remove the volatile key from the DB
			return tx.DeleteInstanceConfigKey(ctx, int64(d.id), key)
		})
		if err != nil {
			_ = apparmor.InstanceUnload(d.state.OS, d)
			return err
		}
	}

	err = d.templateApplyNow("start")
	if err != nil {
		_ = apparmor.InstanceUnload(d.state.OS, d)
		return err
	}

	// Trigger a rebalance
	defer cgroup.TaskSchedulerTrigger("container", d.name, "started")

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

	return nil
}

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

	// gendoc:generate(entity=image, group=requirements, key=requirements.nesting)
	//
	// ---
	//  type: bool
	//  shortdesc: If set to `true`, indicates that the image cannot work without nesting enabled.
	//
	// Ensure nesting is turned on for images that require nesting.
	if util.IsTrue(d.localConfig["image.requirements.nesting"]) && util.IsFalseOrEmpty(d.expandedConfig["security.nesting"]) {
		return errors.New("The image used by this instance requires nesting. Please set security.nesting=true on the instance")
	}

	return nil
}

// Stop functions.
func (d *lxc) Stop(stateful bool) error {
	d.logger.Debug("Stop started", logger.Ctx{"stateful": stateful})
	defer d.logger.Debug("Stop finished", logger.Ctx{"stateful": stateful})

	// Check that migration.stateful is set for stateful actions.
	if stateful && !d.CanLiveMigrate() {
		return errors.New("Stateful stop requires the instance to have migration.stateful be set to true")
	}

	// Must be run prior to creating the operation lock.
	if !d.IsRunning() {
		return ErrInstanceIsStopped
	}

	// Setup a new operation
	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
	}

	ctxMap := logger.Ctx{
		"action":    op.Action(),
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
		"stateful":  stateful,
	}

	if op.Action() == "stop" {
		d.logger.Info("Stopping instance", ctxMap)
	}

	// Forcefully stop any forkfile process if running.
	d.stopForkfile(true)

	// Release liblxc container once done.
	defer func() {
		d.release()
	}()

	// Load the go-lxc struct
	var cc *liblxc.Container
	if d.expandedConfig["raw.lxc"] != "" {
		cc, err = d.initLXC(true)
		if err != nil {
			op.Done(err)
			return err
		}

		err = d.loadRawLXCConfig(cc)
		if err != nil {
			op.Done(err)
			return err
		}
	} else {
		cc, err = d.initLXC(false)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Handle stateful stop
	if stateful {
		// Cleanup any existing state
		stateDir := d.StatePath()
		_ = os.RemoveAll(stateDir)

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

		criuMigrationArgs := instance.CriuMigrationArgs{
			Cmd:          liblxc.MIGRATE_DUMP,
			StateDir:     stateDir,
			Function:     "snapshot",
			Stop:         true,
			ActionScript: false,
			DumpDir:      "",
			PreDumpDir:   "",
		}

		// Checkpoint
		err = d.migrate(&criuMigrationArgs)
		if err != nil {
			op.Done(err)
			return err
		}

		err = op.Wait(context.Background())
		if err != nil && d.IsRunning() {
			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 {
			return fmt.Errorf("Failed updating instance stateful flag: %w", err)
		}

		d.logger.Info("Stopped instance", ctxMap)
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStopped.Event(d, nil))

		return nil
	} else if util.PathExists(d.StatePath()) {
		_ = os.RemoveAll(d.StatePath())
	}

	// Load cgroup abstraction
	cg, err := d.cgroup(cc, true)
	if err != nil {
		op.Done(err)
		return err
	}

	// Fork-bomb mitigation, prevent forking from this point on
	if d.state.OS.CGInfo.Supports(cgroup.Pids, cg) {
		// Attempt to disable forking new processes
		_ = cg.SetMaxProcesses(0)
	} else if d.state.OS.CGInfo.Supports(cgroup.Freezer, cg) {
		// Attempt to freeze the container
		freezer := make(chan bool, 1)
		go func() {
			_ = d.Freeze()
			freezer <- true
		}()

		select {
		case <-freezer:
		case <-time.After(time.Second * 5):
			_ = d.Unfreeze()
		}
	}

	err = cc.Stop()
	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
	} else if op.Action() == "stop" {
		// If instance stopped, send lifecycle event (even if there has been an error cleaning up).
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStopped.Event(d, nil))
	}

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

	return nil
}

// Shutdown stops the instance.
func (d *lxc) 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
	}

	// Setup a new operation
	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
		}

		// Wait 3s for init to be running enough to get the next signal handle.
		time.Sleep(3 * time.Second)
	}

	ctxMap := logger.Ctx{
		"action":    "shutdown",
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
		"timeout":   timeout,
	}

	if op.Action() == "stop" {
		d.logger.Info("Shutting down instance", ctxMap)
	}

	// Release liblxc container once done.
	defer func() {
		d.release()
	}()

	// Load the go-lxc struct
	var cc *liblxc.Container
	if d.expandedConfig["raw.lxc"] != "" {
		cc, err = d.initLXC(true)
		if err != nil {
			op.Done(err)
			return err
		}

		err = d.loadRawLXCConfig(cc)
		if err != nil {
			op.Done(err)
			return err
		}
	} else {
		cc, err = d.initLXC(false)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	// Request shutdown, but don't wait for container to stop. If call fails then cancel operation with error,
	// otherwise expect the onStop() hook to cancel operation when done (when the container has stopped).
	err = cc.Shutdown(0)
	if err != nil {
		op.Done(err)
	}

	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
	} else if op.Action() == "stop" {
		// If instance stopped, send lifecycle event (even if there has been an error cleaning up).
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceShutdown.Event(d, nil))
	}

	// 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 *lxc) Restart(timeout time.Duration) error {
	return d.restartCommon(d, timeout)
}

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

// onStopNS is triggered by LXC's stop hook once a container is shutdown but before the container's
// namespaces have been closed. The netns path of the stopped container is provided.
func (d *lxc) onStopNS(args map[string]string) error {
	target := args["target"]
	netns := args["netns"]

	// Validate target.
	if !slices.Contains([]string{"stop", "reboot"}, target) {
		d.logger.Error("Container sent invalid target to OnStopNS", logger.Ctx{"target": target})
		return fmt.Errorf("Invalid stop target %q", target)
	}

	// Create/pick up operation, but don't complete it as we leave operation running for the onStop hook below.
	_, err := d.onStopOperationSetup(target)
	if err != nil {
		return err
	}

	// Clean up devices.
	d.cleanupDevices(false, netns)

	return nil
}

// onStop is triggered by LXC's post-stop hook once a container is shutdown and after the
// container's namespaces have been closed.
func (d *lxc) onStop(args map[string]string) error {
	target := args["target"]

	// Validate target
	if !slices.Contains([]string{"stop", "reboot"}, target) {
		d.logger.Error("Container sent invalid target to OnStop", logger.Ctx{"target": target})
		return fmt.Errorf("Invalid stop target: %s", target)
	}

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

	// Make sure we can't call go-lxc functions by mistake
	d.fromHook = true

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

	go func(d *lxc, target string, op *operationlock.InstanceOperation) {
		d.fromHook = false
		err = nil

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

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

		d.logger.Debug("Instance stopped, cleaning up")

		// Wait for any file operations to complete.
		// This is to required so we can actually unmount the container.
		d.stopForkfile(false)

		// Clean up devices.
		d.cleanupDevices(false, "")

		// Stop DHCP client if any.
		if util.PathExists(filepath.Join(d.Path(), "network", "dhcp.pid")) {
			dhcpPIDStr, err := os.ReadFile(filepath.Join(d.Path(), "network", "dhcp.pid"))
			if err == nil {
				dhcpPID, err := strconv.Atoi(strings.TrimSpace(string(dhcpPIDStr)))
				if err == nil {
					_ = unix.Kill(dhcpPID, unix.SIGTERM)
				}
			}
		}

		// Remove directory ownership (to avoid issue if uidmap is reused)
		err := os.Chown(d.Path(), 0, 0)
		if err != nil {
			op.Done(fmt.Errorf("Failed clearing ownership: %w", err))
			return
		}

		err = os.Chmod(d.Path(), 0o100)
		if err != nil {
			op.Done(fmt.Errorf("Failed clearing permissions: %w", err))
			return
		}

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

		// Unload the apparmor profile
		err = apparmor.InstanceUnload(d.state.OS, d)
		if err != nil {
			op.Done(fmt.Errorf("Failed to destroy apparmor namespace: %w", err))
			return
		}

		// Clean all the unix devices
		err = d.removeUnixDevices()
		if err != nil {
			op.Done(fmt.Errorf("Failed to remove unix devices: %w", err))
			return
		}

		// Clean all the disk devices
		err = d.removeDiskDevices()
		if err != nil {
			op.Done(fmt.Errorf("Failed to remove disk devices: %w", err))
			return
		}

		// Stop dedicated LXCFS.
		if util.PathExists(filepath.Join(d.DevicesPath(), "lxcfs", "proc")) && util.PathExists(filepath.Join(d.RunPath(), "lxcfs.yaml")) {
			// Import the running LXCFS.
			lxcfs, err := subprocess.ImportProcess(filepath.Join(d.RunPath(), "lxcfs.yaml"))
			if err != nil && !os.IsExist(err) {
				op.Done(fmt.Errorf("Failed to stop LXCFS: %w", err))
				return
			}

			// Stop LXCFS.
			err = lxcfs.Stop()
			if err != nil && !errors.Is(err, subprocess.ErrNotRunning) {
				op.Done(fmt.Errorf("Failed to stop LXCFS: %w", err))
				return
			}

			_ = unix.Unmount(filepath.Join(d.DevicesPath(), "lxcfs"), unix.MNT_DETACH)
		}

		// Determine if instance should be auto-restarted.
		var autoRestart bool
		if target != "reboot" && op.GetInstanceInitiated() && 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.GetInstanceInitiated() {
			ctxMap := logger.Ctx{
				"action":    target,
				"created":   d.creationDate,
				"ephemeral": d.ephemeral,
				"used":      d.lastUsedDate,
				"stateful":  false,
			}

			d.logger.Info("Shut down instance", ctxMap)
			d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceShutdown.Event(d, nil))
		}

		// Reboot the container
		if target == "reboot" || autoRestart {
			// Start the container again
			err = d.Start(false)
			if err != nil {
				op.Done(fmt.Errorf("Failed restarting instance: %w", err))
				return
			}

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

			return
		}

		// Trigger a rebalance
		defer cgroup.TaskSchedulerTrigger("container", d.name, "stopped")

		// Destroy ephemeral containers
		if d.ephemeral {
			err = d.delete(true)
			if err != nil {
				op.Done(fmt.Errorf("Failed deleting ephemeral instance: %w", err))
				return
			}
		}
	}(d, target, op)

	return nil
}

// cleanupDevices performs any needed device cleanup steps when container is stopped.
// Accepts a stopHookNetnsPath argument which is required when run from the onStopNS hook before the
// container's network namespace is unmounted (which is required for NIC device cleanup).
func (d *lxc) cleanupDevices(instanceRunning bool, stopHookNetnsPath string) {
	for _, entry := range d.expandedDevices.Reversed() {
		// Only stop NIC devices when run from the onStopNS hook, and stop all other devices when run from
		// the onStop hook. This way disk devices are stopped after the instance has been fully stopped.
		if (stopHookNetnsPath != "" && entry.Config["type"] != "nic") || (stopHookNetnsPath == "" && entry.Config["type"] == "nic") {
			continue
		}

		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, instanceRunning, stopHookNetnsPath)
			if err != nil {
				d.logger.Error("Failed to stop device", logger.Ctx{"device": dev.Name(), "err": err})
			}
		}
	}
}

// Freeze functions.
func (d *lxc) Freeze() error {
	ctxMap := logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
	}

	// Check that we're running
	if !d.IsRunning() {
		return errors.New("The instance isn't running")
	}

	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		ctxMap["err"] = err
		d.logger.Error("Failed freezing container", ctxMap)
		return err
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return err
	}

	// Check if the CGroup is available
	if !d.state.OS.CGInfo.Supports(cgroup.Freezer, cg) {
		d.logger.Warn("Unable to freeze container (lack of kernel support)", ctxMap)
		return nil
	}

	// Check that we're not already frozen
	if d.IsFrozen() {
		return errors.New("The container is already frozen")
	}

	d.logger.Info("Freezing container", ctxMap)

	err = cc.Freeze()
	if err != nil {
		ctxMap["err"] = err
		d.logger.Error("Failed freezing container", ctxMap)
		return err
	}

	d.logger.Info("Froze container", ctxMap)
	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstancePaused.Event(d, nil))

	return err
}

// Unfreeze unfreezes the instance.
func (d *lxc) Unfreeze() error {
	ctxMap := logger.Ctx{
		"created":   d.creationDate,
		"ephemeral": d.ephemeral,
		"used":      d.lastUsedDate,
	}

	// Check that we're running
	if !d.IsRunning() {
		return errors.New("The container isn't running")
	}

	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		d.logger.Error("Failed unfreezing container", ctxMap)
		return err
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return err
	}

	// Check if the CGroup is available
	if !d.state.OS.CGInfo.Supports(cgroup.Freezer, cg) {
		d.logger.Warn("Unable to unfreeze container (lack of kernel support)", ctxMap)
		return nil
	}

	// Check that we're frozen
	if !d.IsFrozen() {
		return errors.New("The container is already running")
	}

	d.logger.Info("Unfreezing container", ctxMap)

	err = cc.Unfreeze()
	if err != nil {
		d.logger.Error("Failed unfreezing container", ctxMap)
	}

	d.logger.Info("Unfroze container", ctxMap)
	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceResumed.Event(d, nil))

	return err
}

// Get lxc container state, with 1 second timeout.
// If we don't get a reply, assume the lxc monitor is unresponsive.
func (d *lxc) getLxcState() (liblxc.State, error) {
	if d.IsSnapshot() {
		return liblxc.StateMap["STOPPED"], nil
	}

	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return liblxc.StateMap["STOPPED"], err
	}

	monitor := make(chan liblxc.State, 1)

	go func(c *liblxc.Container) {
		monitor <- c.State()
	}(cc)

	select {
	case state := <-monitor:
		return state, nil
	case <-time.After(5 * time.Second):
		return liblxc.StateMap["FROZEN"], errors.New("Monitor is unresponsive")
	}
}

// RenderWithUsage renders the API response including disk usage.
func (d *lxc) 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
	}

	snapResp.Size = volumeState.Used
	return snapResp, etag, nil
}

// Render renders the state of the instance.
func (d *lxc) Render() (any, any, error) {
	// Ignore err as the arch string on error is correct (unknown)
	architectureName, _ := osarch.ArchitectureName(d.architecture)
	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 = 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 = 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 renders the full state of the instance.
func (d *lxc) RenderFull(hostInterfaces []net.Interface) (*api.InstanceFull, any, error) {
	if d.IsSnapshot() {
		return nil, nil, errors.New("RenderFull only works with containers")
	}

	// Pre-fetch the data.
	pool, err := d.getStoragePool()
	if err != nil {
		return nil, nil, err
	}

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

	// Get the Container struct
	base, etag, err := d.Render()
	if err != nil {
		return nil, nil, err
	}

	// Convert to ContainerFull
	ct := api.InstanceFull{Instance: *base.(*api.Instance)}

	// Add the ContainerState
	ct.State, err = d.renderState(ct.StatusCode, hostInterfaces)
	if err != nil {
		return nil, nil, err
	}

	// Add the ContainerSnapshots
	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 ct.Snapshots == nil {
			ct.Snapshots = []api.InstanceSnapshot{}
		}

		ct.Snapshots = append(ct.Snapshots, *render.(*api.InstanceSnapshot))
	}

	// Add the ContainerBackups
	backups, err := d.Backups()
	if err != nil {
		return nil, nil, err
	}

	for _, backup := range backups {
		render := backup.Render()

		if ct.Backups == nil {
			ct.Backups = []api.InstanceBackup{}
		}

		ct.Backups = append(ct.Backups, *render)
	}

	return &ct, etag, nil
}

// renderState renders just the running state of the instance.
func (d *lxc) renderState(statusCode api.StatusCode, hostInterfaces []net.Interface) (*api.InstanceState, error) {
	status := api.InstanceState{
		Status:     statusCode.String(),
		StatusCode: statusCode,
	}

	pid := d.InitPID()
	processesState, _ := d.processesState(pid)

	if d.isRunningStatusCode(statusCode) {
		var err error

		status.CPU = d.cpuState()
		status.Memory = d.memoryState()
		status.Network = d.networkState(hostInterfaces)
		status.Pid = int64(pid)
		status.Processes = processesState

		status.StartedAt, err = d.processStartedAt(d.InitPID())
		if err != nil {
			return nil, err
		}
	}

	status.Disk = d.diskState()

	d.release()

	return &status, nil
}

// RenderState renders just the running state of the instance.
func (d *lxc) RenderState(hostInterfaces []net.Interface) (*api.InstanceState, error) {
	return d.renderState(d.statusCode(), hostInterfaces)
}

// snapshot creates a snapshot of the instance.
func (d *lxc) snapshot(name string, expiry time.Time, stateful bool) error {
	// Check that migration.stateful is set for stateful actions.
	if stateful && !d.CanLiveMigrate() {
		return errors.New("Stateful snapshots require that the instance has migration.stateful be set to true")
	}

	// Deal with state.
	if stateful {
		// Quick checks.
		if !d.IsRunning() {
			return errors.New("Unable to create a stateful snapshot. The instance isn't running")
		}

		_, err := exec.LookPath("criu")
		if err != nil {
			return errors.New("Unable to create a stateful snapshot. CRIU isn't installed")
		}

		// Cleanup any existing state
		stateDir := d.StatePath()
		_ = os.RemoveAll(stateDir)

		// Create the state path and make sure we don't keep state around after the snapshot has been made.
		err = os.MkdirAll(stateDir, 0o700)
		if err != nil {
			return err
		}

		defer func() { _ = os.RemoveAll(stateDir) }()

		// Release liblxc container once done.
		defer func() {
			d.release()
		}()

		// Load the go-lxc struct
		if d.expandedConfig["raw.lxc"] != "" {
			cc, err := d.initLXC(true)
			if err != nil {
				return err
			}

			err = d.loadRawLXCConfig(cc)
			if err != nil {
				return err
			}
		} else {
			_, err = d.initLXC(false)
			if err != nil {
				return err
			}
		}

		/* TODO: ideally we would freeze here and unfreeze below after
		 * we've copied the filesystem, to make sure there are no
		 * changes by the container while snapshotting. Unfortunately
		 * there is abug in CRIU where it doesn't leave the container
		 * in the same state it found it w.r.t. freezing, i.e. CRIU
		 * freezes too, and then /always/ thaws, even if the container
		 * was frozen. Until that's fixed, all calls to Unfreeze()
		 * after snapshotting will fail.
		 */
		criuMigrationArgs := instance.CriuMigrationArgs{
			Cmd:          liblxc.MIGRATE_DUMP,
			StateDir:     stateDir,
			Function:     "snapshot",
			Stop:         false,
			ActionScript: false,
			DumpDir:      "",
			PreDumpDir:   "",
		}

		// Dump the state.
		err = d.migrate(&criuMigrationArgs)
		if err != nil {
			return fmt.Errorf("Failed taking stateful checkpoint: %w", err)
		}
	}

	// Wait for any file operations to complete to have a more consistent snapshot.
	d.stopForkfile(false)

	return d.snapshotCommon(d, name, expiry, stateful)
}

// Snapshot takes a new snapshot.
func (d *lxc) Snapshot(name string, expiry time.Time, stateful bool) error {
	return d.snapshot(name, expiry, stateful)
}

// Restore restores a snapshot.
func (d *lxc) Restore(sourceContainer instance.Instance, stateful bool, diskOnly bool) error {
	var ctxMap logger.Ctx

	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)

	// Stop the container.
	wasRunning := d.IsRunning()
	if wasRunning {
		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 container 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":    sourceContainer.Name(),
	}

	d.logger.Info("Restoring instance", ctxMap)

	// Wait for any file operations to complete.
	// This is required so we can actually unmount the container and restore its rootfs.
	d.stopForkfile(false)

	// Initialize storage interface for the container and mount the rootfs for criu state check.
	pool, err := storagePools.LoadByInstance(d.state, d)
	if err != nil {
		op.Done(err)
		return err
	}

	d.logger.Debug("Mounting instance to check for CRIU state path existence")

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

	// Ensure that storage is mounted for state path checks and for backup.yaml updates.
	_, err = d.mount()
	if err != nil {
		op.Done(err)
		return err
	}

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

	// Check for CRIU if necessary, before doing a bunch of filesystem manipulations.
	// Requires container be mounted to check StatePath exists.
	if util.PathExists(d.StatePath()) {
		_, err := exec.LookPath("criu")
		if err != nil {
			err = errors.New("Failed to restore container state. CRIU isn't installed")
			op.Done(err)
			return err
		}
	}

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

	reverter.Success()

	// Restore the rootfs.
	err = pool.RestoreInstanceSnapshot(d, sourceContainer, nil)
	if err != nil {
		op.Done(err)
		return err
	}

	args := db.InstanceArgs{}
	if !diskOnly {
		// Restore the configuration.
		args = db.InstanceArgs{
			Architecture: sourceContainer.Architecture(),
			Config:       sourceContainer.LocalConfig(),
			Description:  sourceContainer.Description(),
			Devices:      sourceContainer.LocalDevices(),
			Ephemeral:    sourceContainer.IsEphemeral(),
			Profiles:     sourceContainer.Profiles(),
			Project:      sourceContainer.Project().Name,
			Type:         sourceContainer.Type(),
			Snapshot:     sourceContainer.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"] = sourceContainer.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
	}

	// If the container wasn't running but was stateful, should we restore it as running?
	if stateful {
		if !util.PathExists(d.StatePath()) {
			err = errors.New("Stateful snapshot restore requested but snapshot is stateless")
			op.Done(err)
			return err
		}

		d.logger.Debug("Performing stateful restore", ctxMap)
		d.stateful = true

		criuMigrationArgs := instance.CriuMigrationArgs{
			Cmd:          liblxc.MIGRATE_RESTORE,
			StateDir:     d.StatePath(),
			Function:     "snapshot",
			Stop:         false,
			ActionScript: false,
			DumpDir:      "",
			PreDumpDir:   "",
		}

		// Checkpoint.
		err = d.migrate(&criuMigrationArgs)
		if err != nil {
			op.Done(err)
			return fmt.Errorf("Failed taking stateful checkpoint: %w", err)
		}

		// Remove the state from the parent container; we only keep this in snapshots.
		err2 := os.RemoveAll(d.StatePath())
		if err2 != nil && !errors.Is(err, fs.ErrNotExist) {
			op.Done(err)
			return err
		}

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

		d.logger.Debug("Performed stateful restore", ctxMap)
		d.logger.Info("Restored instance", ctxMap)
		return nil
	}

	// Restart the container.
	if wasRunning {
		d.logger.Debug("Starting instance after snapshot restore")
		err = d.Start(false)
		if err != nil {
			op.Done(err)
			return err
		}
	}

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceRestored.Event(d, map[string]any{"snapshot": sourceContainer.Name()}))
	d.logger.Info("Restored instance", ctxMap)

	return nil
}

func (d *lxc) cleanup() {
	// Unmount any leftovers
	_ = d.removeUnixDevices()
	_ = d.removeDiskDevices()

	// Remove the security profiles
	_ = apparmor.InstanceDelete(d.state.OS, d)
	seccomp.DeleteProfile(d)

	// Remove the devices path
	_ = os.Remove(d.DevicesPath())

	// Remove the shmounts path
	_ = os.RemoveAll(d.ShmountsPath())
}

// Delete deletes the instance.
func (d *lxc) Delete(force 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)
	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 deletes the instance without creating an operation lock.
func (d *lxc) delete(force 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)
	}

	if !force && util.IsTrue(d.expandedConfig["security.protection.delete"]) && !d.IsSnapshot() {
		err := errors.New("Instance is protected")
		d.logger.Warn("Failed to delete instance", logger.Ctx{"err": err})
		return err
	}

	// Wait for any file operations to complete.
	// This is required so we can actually unmount the container and delete it.
	if !d.IsSnapshot() {
		d.stopForkfile(false)
	}

	// Delete any persistent warnings for instance.
	err := d.warningsDelete()
	if err != nil {
		return err
	}

	pool, err := storagePools.LoadByInstance(d.state, d)
	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.(*lxc).delete(true) // 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
			}
		}
	}

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

		// 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{"err": err})
		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
}

// Rename renames the instance. Accepts an argument to enable applying deferred TemplateTriggerRename.
func (d *lxc) 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 := cluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName)
				if err != nil {
					d.logger.Error("Failed renaming snapshot", ctxMap)
					return fmt.Errorf("Failed renaming snapshot: %w", 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 cluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldParts[0], oldParts[1], newParts[1])
		}

		return cluster.RenameInstance(ctx, tx.Tx(), d.project.Name, oldName, newName)
	})
	if err != nil {
		d.logger.Error("Failed renaming instance", ctxMap)
		return fmt.Errorf("Failed renaming instance: %w", 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 fmt.Errorf("Failed renaming instance: %w", 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 fmt.Errorf("Failed renaming instance: %w", 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) })
	}

	// Invalidate the go-lxc cache.
	d.release()

	d.cConfig = false

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

// CGroupSet sets a cgroup value for the instance.
func (d *lxc) CGroupSet(key string, value string) error {
	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return err
	}

	// Make sure the container is running.
	// We use InitPID here rather than IsRunning because this task can be triggered during the container's
	// startup process, which is during the time that the start lock is held, which causes IsRunning to
	// return false (because the container hasn't fully started yet) but it is sufficiently started to
	// have its cgroup disk limits set.
	if d.InitPID() <= 0 {
		return errors.New("Can't set cgroups on a stopped container")
	}

	err = cc.SetCgroupItem(key, value)
	if err != nil {
		return fmt.Errorf("Failed to set cgroup %s=\"%s\": %w", key, value, err)
	}

	return nil
}

// Update applies updated config.
func (d *lxc) 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.ActionCreate, operationlock.ActionRestart, operationlock.ActionRestore}, false, false)
	if err != nil {
		return fmt.Errorf("Failed to create instance update operation: %w", err)
	}

	defer op.Done(nil)

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

	// Define a function which reverts everything.  Defer this function
	// so that it doesn't need to be explicitly called in every failing
	// return path.  Track whether or not we want to undo the changes
	// using a closure.
	undoChanges := true
	defer func() {
		if undoChanges {
			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
			d.release()
			d.cConfig = false
			_, _ = d.initLXC(true)
			_ = d.setupCredentials(true)
			cgroup.TaskSchedulerTrigger("container", d.name, "changed")
		}
	}()

	// Apply the various changes
	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 and refresh the LXC 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 LXC 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 strings.HasPrefix(newDev["source"], "tmpfs:") || strings.HasPrefix(newDev["source"], "tmpfs-overlay:") {
					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 {
		// Look for deleted idmap keys.
		protectedKeys := []string{
			"volatile.idmap.base",
			"volatile.idmap.current",
			"volatile.idmap.next",
			"volatile.last_state.idmap",
		}

		for _, k := range changedConfig {
			if !slices.Contains(protectedKeys, k) {
				continue
			}

			_, ok := d.expandedConfig[k]
			if !ok {
				return errors.New("Volatile idmap keys can't be deleted by the user")
			}
		}

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

	// Run through initLXC to catch anything we missed
	if userRequested {
		d.release()
		d.cConfig = false
		_, err = d.initLXC(true)
		if err != nil {
			return fmt.Errorf("Initialize LXC: %w", err)
		}
	}

	// If raw.lxc changed, re-validate the config.
	if slices.Contains(changedConfig, "raw.lxc") && d.expandedConfig["raw.lxc"] != "" {
		// Get a new liblxc instance.
		cc, err := liblxc.NewContainer(d.name, d.state.OS.LxcPath)
		if err != nil {
			return err
		}

		err = d.loadRawLXCConfig(cc)
		if err != nil {
			// Release the liblxc instance.
			_ = cc.Release()
			return err
		}

		// Release the liblxc instance.
		_ = cc.Release()
	}

	// If apparmor changed, re-validate the apparmor profile (even if not running).
	if slices.Contains(changedConfig, "raw.apparmor") || slices.Contains(changedConfig, "security.nesting") {
		err = apparmor.InstanceValidate(d.state.OS, d, nil)
		if err != nil {
			return fmt.Errorf("Parse AppArmor profile: %w", err)
		}
	}

	if slices.Contains(changedConfig, "security.idmap.isolated") || slices.Contains(changedConfig, "security.idmap.base") || slices.Contains(changedConfig, "security.idmap.size") || slices.Contains(changedConfig, "raw.idmap") || slices.Contains(changedConfig, "security.privileged") {
		var idmapSet *idmap.Set
		base := int64(0)
		if !d.IsPrivileged() {
			// Update the idmap.
			idmapSet, base, err = d.findIdmap()
			if err != nil {
				return fmt.Errorf("Failed to get ID map: %w", err)
			}
		}

		jsonIdmap, err := idmapSet.ToJSON()
		if err != nil {
			return fmt.Errorf("Failed to encode ID map: %w", err)
		}

		d.localConfig["volatile.idmap.next"] = jsonIdmap
		d.localConfig["volatile.idmap.base"] = fmt.Sprintf("%v", base)

		// Invalidate the idmap cache.
		d.idmapset = nil
	}

	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
	}

	// Apply the live changes
	if isRunning {
		cc, err := d.initLXC(false)
		if err != nil {
			return err
		}

		cg, err := d.cgroup(cc, true)
		if err != nil {
			return err
		}

		// Live update the container config
		for _, key := range changedConfig {
			value := d.expandedConfig[key]

			if key == "raw.apparmor" || key == "security.nesting" {
				// Update the AppArmor profile
				err = apparmor.InstanceLoad(d.state.OS, d, nil)
				if err != nil {
					return err
				}
			} else if key == "security.guestapi" {
				if util.IsTrueOrEmpty(value) {
					err = d.insertMount(internalUtil.VarPath("guestapi"), "/dev/incus", "none", unix.MS_BIND, idmap.StorageTypeNone)
					if err != nil {
						return err
					}
				} else {
					// Connect to files API.
					files, err := d.FileSFTP()
					if err != nil {
						return err
					}

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

					_, err = files.Lstat("/dev/incus")
					if err == nil {
						err = d.removeMount("/dev/incus")
						if err != nil {
							return err
						}

						err = files.Remove("/dev/incus")
						if err != nil {
							return err
						}
					}
				}
			} else if key == "linux.kernel_modules" && value != "" {
				for _, module := range strings.Split(value, ",") {
					module = strings.TrimPrefix(module, " ")
					err := linux.LoadModule(module)
					if err != nil {
						return fmt.Errorf("Failed to load kernel module '%s': %w", module, err)
					}
				}
			} else if key == "limits.disk.priority" {
				if !d.state.OS.CGInfo.Supports(cgroup.Blkio, cg) {
					continue
				}

				priorityInt := 5
				diskPriority := d.expandedConfig["limits.disk.priority"]
				if diskPriority != "" {
					priorityInt, err = strconv.Atoi(diskPriority)
					if err != nil {
						return err
					}
				}

				// Minimum valid value is 10
				priority := int64(priorityInt * 100)
				if priority == 0 {
					priority = 10
				}

				err = cg.SetBlkioWeight(priority)
				if err != nil {
					return err
				}
			} else if key == "limits.memory.oom_priority" {
				// Configure the OOM priority.
				err = d.setOOMPriority(cc.InitPid())
				if err != nil {
					d.logger.Warn("Failed to set OOM priority", logger.Ctx{
						"err":      err,
						"instance": d.Name(),
						"project":  d.Project().Name,
					})
				}
			} else if key == "limits.memory" || strings.HasPrefix(key, "limits.memory.") {
				// Skip if no memory CGroup
				if !d.state.OS.CGInfo.Supports(cgroup.Memory, cg) {
					continue
				}

				// Set the new memory limit
				memory := d.expandedConfig["limits.memory"]
				memoryEnforce := d.expandedConfig["limits.memory.enforce"]
				memorySwap := d.expandedConfig["limits.memory.swap"]
				var memoryInt int64

				// Parse memory
				if memory == "" {
					memoryInt = -1
				} else {
					memoryInt, err = ParseMemoryStr(memory)
					if err != nil {
						return err
					}
				}

				// Store the old values for revert
				oldMemswLimit := int64(-1)
				if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) {
					oldMemswLimit, err = cg.GetMemorySwapLimit()
					if err != nil {
						oldMemswLimit = -1
					}
				}
				oldLimit, err := cg.GetMemoryLimit()
				if err != nil {
					oldLimit = -1
				}

				oldSoftLimit, err := cg.GetMemorySoftLimit()
				if err != nil {
					oldSoftLimit = -1
				}

				revertMemory := func() {
					if oldSoftLimit != -1 {
						_ = cg.SetMemorySoftLimit(oldSoftLimit)
					}

					if oldLimit != -1 {
						_ = cg.SetMemoryLimit(oldLimit)
					}

					if oldMemswLimit != -1 {
						_ = cg.SetMemorySwapLimit(oldMemswLimit)
					}
				}

				// Reset everything
				if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) {
					err = cg.SetMemorySwapLimit(-1)
					if err != nil {
						revertMemory()
						return err
					}
				}

				err = cg.SetMemoryLimit(-1)
				if err != nil {
					revertMemory()
					return err
				}

				err = cg.SetMemorySoftLimit(-1)
				if err != nil {
					revertMemory()
					return err
				}

				// Set the new values
				if memoryEnforce == "soft" {
					// Set new limit.
					err = cg.SetMemorySoftLimit(memoryInt)
					if err != nil {
						revertMemory()
						return err
					}
				} else {
					err = cg.SetMemoryLimit(memoryInt)
					if err != nil {
						revertMemory()
						return err
					}

					if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) {
						if util.IsTrueOrEmpty(memorySwap) || util.IsFalse(memorySwap) {
							err = cg.SetMemorySwapLimit(0)
							if err != nil {
								revertMemory()
								return err
							}
						} else {
							// Additional memory as swap.
							swapInt, err := units.ParseByteSizeString(memorySwap)
							if err != nil {
								revertMemory()
								return err
							}

							err = cg.SetMemorySwapLimit(swapInt)
							if err != nil {
								revertMemory()
								return err
							}
						}
					}

					// If on Cgroup1, set soft limit to value 10% less than hard limit.
					if memoryInt > 0 && slices.Contains([]cgroup.Layout{cgroup.CgroupsLegacy, cgroup.CgroupsHybrid}, d.state.OS.CGInfo.Layout) {
						err = cg.SetMemorySoftLimit(int64(float64(memoryInt) * 0.9))
						if err != nil {
							revertMemory()
							return err
						}
					}
				}

				if !d.state.OS.CGInfo.Supports(cgroup.MemorySwappiness, cg) {
					continue
				}

				// Configure the swappiness
				if key == "limits.memory.swap" || key == "limits.memory.swap.priority" {
					memorySwapPriority := d.expandedConfig["limits.memory.swap.priority"]
					if util.IsFalse(memorySwap) {
						err = cg.SetMemorySwappiness(0)
						if err != nil {
							return err
						}
					} else {
						priority := 10
						if memorySwapPriority != "" {
							priority, err = strconv.Atoi(memorySwapPriority)
							if err != nil {
								return err
							}
						}

						// Maximum priority (10) should be default swappiness (60).
						err = cg.SetMemorySwappiness(int64(70 - priority))
						if err != nil {
							return err
						}
					}
				}
			} else if key == "limits.cpu" || key == "limits.cpu.nodes" {
				// Clear the "volatile.cpu.nodes" if needed.
				d.ClearLimitsCPUNodes(changedConfig)

				// Trigger a scheduler re-run
				defer cgroup.TaskSchedulerTrigger("container", d.name, "changed") //nolint:revive
			} else if key == "limits.cpu.priority" || key == "limits.cpu.allowance" {
				// Skip if no cpu CGroup
				if !d.state.OS.CGInfo.Supports(cgroup.CPU, cg) {
					continue
				}

				// Apply new CPU limits
				cpuShares, cpuCfsQuota, cpuCfsPeriod, err := cgroup.ParseCPU(d.expandedConfig["limits.cpu.allowance"], d.expandedConfig["limits.cpu.priority"])
				if err != nil {
					return err
				}

				err = cg.SetCPUShare(cpuShares)
				if err != nil {
					return err
				}

				err = cg.SetCPUCfsLimit(cpuCfsPeriod, cpuCfsQuota)
				if err != nil {
					return err
				}
			} else if key == "limits.processes" {
				if !d.state.OS.CGInfo.Supports(cgroup.Pids, cg) {
					continue
				}

				if value == "" {
					err = cg.SetMaxProcesses(-1)
					if err != nil {
						return err
					}
				} else {
					valueInt, err := strconv.ParseInt(value, 10, 64)
					if err != nil {
						return err
					}

					err = cg.SetMaxProcesses(valueInt)
					if err != nil {
						return err
					}
				}
			} else if strings.HasPrefix(key, "limits.hugepages.") {
				if !d.state.OS.CGInfo.Supports(cgroup.Hugetlb, cg) {
					continue
				}

				pageType := ""

				switch key {
				case "limits.hugepages.64KB":
					pageType = "64KB"
				case "limits.hugepages.1MB":
					pageType = "1MB"
				case "limits.hugepages.2MB":
					pageType = "2MB"
				case "limits.hugepages.1GB":
					pageType = "1GB"
				}

				valueInt := int64(-1)
				if value != "" {
					valueInt, err = units.ParseByteSizeString(value)
					if err != nil {
						return err
					}
				}

				err = cg.SetHugepagesLimit(pageType, valueInt)
				if err != nil {
					return err
				}
			}
		}

		// Update the credentials directory.
		err = d.setupCredentials(true)
		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 := cluster.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 = cluster.UpdateInstance(ctx, tx.Tx(), d.project.Name, d.name, *object)
		if err != nil {
			return err
		}

		err = cluster.UpdateInstanceConfig(ctx, tx.Tx(), int64(object.ID), d.localConfig)
		if err != nil {
			return err
		}

		devices, err := cluster.APIToDevices(d.localDevices.CloneNative())
		if err != nil {
			return err
		}

		err = cluster.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 cluster.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)
	}

	// Send devIncus notifications
	if isRunning {
		// Config changes (only for user.* keys
		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
			}
		}
	}

	// Success, update the closure to mark that the changes should be kept.
	undoChanges = false

	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
}

// Export backs up the instance.
func (d *lxc) 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.
	_, err := d.mount()
	if err != nil {
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	defer func() { _ = d.unmount() }()

	// Get IDMap to unshift container as the tarball is created.
	idmap, err := d.DiskIdmap()
	if err != nil {
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	// Create the tarball.
	metaTarWriter := instancewriter.NewInstanceTarWriter(metaWriter, idmap)

	var rootfsTarWriter *instancewriter.InstanceTarWriter
	if rootfsWriter != nil {
		rootfsTarWriter = instancewriter.NewInstanceTarWriter(rootfsWriter, idmap)
	}

	// Keep track of the first path we saw for each path with nlink>1.
	cDir := d.Path()

	// Path inside the tar image is the pathname starting after cDir.
	// For the rootfs tarball in a split image, the path inside is the pathname starting after rootfs/
	metaOffset := len(cDir) + 1
	rootfsOffset := len(d.RootfsPath())

	writeToMetaTar := func(fPath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		err = metaTarWriter.WriteFile(fPath[metaOffset:], fPath, fi, false)
		if err != nil {
			d.logger.Debug("Error tarring up", logger.Ctx{"path": fPath, "err": err})
			return err
		}

		return nil
	}

	var writeToRootfsTar func(string, os.FileInfo, error) error
	if rootfsWriter != nil {
		writeToRootfsTar = func(path string, fi os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			err = rootfsTarWriter.WriteFile(path[rootfsOffset:], 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()
			if rootfsTarWriter != nil {
				_ = rootfsTarWriter.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()
			if rootfsTarWriter != nil {
				_ = rootfsTarWriter.Close()
			}

			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}

		err = yaml.Unmarshal(content, &meta)
		if err != nil {
			_ = metaTarWriter.Close()
			if rootfsTarWriter != nil {
				_ = rootfsTarWriter.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()
		if rootfsTarWriter != nil {
			_ = rootfsTarWriter.Close()
		}

		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	defer func() { _ = os.RemoveAll(tempDir) }()

	data, err := yaml.Marshal(&meta)
	if err != nil {
		_ = metaTarWriter.Close()
		if rootfsTarWriter != nil {
			_ = rootfsTarWriter.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()
		if rootfsTarWriter != nil {
			_ = rootfsTarWriter.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()
		if rootfsTarWriter != nil {
			_ = rootfsTarWriter.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()
		if rootfsTarWriter != nil {
			_ = rootfsTarWriter.Close()
		}

		d.logger.Debug("Error writing to tarfile", logger.Ctx{"err": err})
		d.logger.Error("Failed exporting instance", ctxMap)
		return nil, err
	}

	// If present, add config.json (OCI) to the tarball.
	fnam = filepath.Join(d.Path(), "config.json")
	if util.PathExists(fnam) {
		fi, err := os.Lstat(fnam)
		if err != nil {
			_ = metaTarWriter.Close()
			if rootfsTarWriter != nil {
				_ = rootfsTarWriter.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()
			if rootfsTarWriter != nil {
				_ = rootfsTarWriter.Close()
			}

			d.logger.Debug("Error writing to tarfile", logger.Ctx{"err": err})
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	}

	// Include all the rootfs files.
	fnam = d.RootfsPath()
	if rootfsWriter == nil {
		err = filepath.Walk(fnam, writeToMetaTar)
		if err != nil {
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	} else {
		err = filepath.Walk(fnam, writeToRootfsTar)
		if err != nil {
			d.logger.Error("Failed exporting instance", ctxMap)
			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
	}

	if rootfsTarWriter != nil {
		err = rootfsTarWriter.Close()
		if err != nil {
			d.logger.Error("Failed exporting instance", ctxMap)
			return nil, err
		}
	}

	d.logger.Info("Exported instance", ctxMap)
	return &meta, nil
}

func collectCRIULogFile(d instance.Instance, imagesDir string, function string, method string) error {
	t := time.Now().Format(time.RFC3339)
	newPath := filepath.Join(d.LogPath(), fmt.Sprintf("%s_%s_%s.log", function, method, t))
	return internalUtil.FileCopy(filepath.Join(imagesDir, fmt.Sprintf("%s.log", method)), newPath)
}

func getCRIULogErrors(imagesDir string, method string) (string, error) {
	f, err := os.Open(path.Join(imagesDir, fmt.Sprintf("%s.log", method)))
	if err != nil {
		return "", err
	}

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

	scanner := bufio.NewScanner(f)
	ret := []string{}
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "Error") || strings.Contains(line, "Warn") {
			ret = append(ret, scanner.Text())
		}
	}

	return strings.Join(ret, "\n"), nil
}

// Check if CRIU supports pre-dumping and number of pre-dump iterations.
func (d *lxc) migrationSendCheckForPreDumpSupport() (bool, int) {
	// Check if this architecture/kernel/criu combination supports pre-copy dirty memory tracking feature.
	_, err := subprocess.RunCommand("criu", "check", "--feature", "mem_dirty_track")
	if err != nil {
		// CRIU says it does not know about dirty memory tracking.
		// This means the rest of this function is irrelevant.
		return false, 0
	}

	// CRIU says it can actually do pre-dump. Let's set it to true
	// unless the user wants something else.
	usePreDumps := true

	// What does the configuration say about pre-copy
	tmp := d.ExpandedConfig()["migration.incremental.memory"]

	if tmp != "" {
		usePreDumps = util.IsTrue(tmp)
	}

	var maxIterations int

	// migration.incremental.memory.iterations is the value after which the
	// container will be definitely migrated, even if the remaining number
	// of memory pages is below the defined threshold.
	tmp = d.ExpandedConfig()["migration.incremental.memory.iterations"]
	if tmp != "" {
		maxIterations, _ = strconv.Atoi(tmp)
	} else {
		// default to 10
		maxIterations = 10
	}

	if maxIterations > 999 {
		// the pre-dump directory is hardcoded to a string
		// with maximal 3 digits. 999 pre-dumps makes no
		// sense at all, but let's make sure the number
		// is not higher than this.
		maxIterations = 999
	}

	logger.Debugf("Using maximal %d iterations for pre-dumping", maxIterations)

	return usePreDumps, maxIterations
}

func (d *lxc) migrationSendWriteActionScript(directory string, operation string, secret string, execPath string) error {
	script := fmt.Sprintf(`#!/bin/sh -e
if [ "$CRTOOLS_SCRIPT_ACTION" = "post-dump" ]; then
	%s migratedumpsuccess %s %s
fi
`, execPath, operation, secret)

	f, err := os.Create(filepath.Join(directory, "action.sh"))
	if err != nil {
		return err
	}

	err = f.Chmod(0o500)
	if err != nil {
		return err
	}

	_, err = f.WriteString(script)
	if err != nil {
		return err
	}

	return f.Close()
}

func (d *lxc) MigrateSend(args instance.MigrateSendArgs) error {
	d.logger.Debug("Migration send starting")
	defer d.logger.Debug("Migration send stopped")

	// Check for an existing operation.

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

	// If not running, stop any forkfile instance.
	if !d.IsRunning() {
		d.stopForkfile(false)
	}

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

	var stateConn io.ReadWriteCloser
	if args.Live {
		stateConn, err = args.StateConn(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 != ""
	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

	// Add CRIU and predump info to source header.
	maxDumpIterations := 0
	if args.Live {
		var offerUsePreDumps bool
		offerUsePreDumps, maxDumpIterations = d.migrationSendCheckForPreDumpSupport()
		offerHeader.Predump = proto.Bool(offerUsePreDumps)
		offerHeader.Criu = migration.CRIUType_CRIU_RSYNC.Enum()
	} else {
		offerHeader.Predump = proto.Bool(false)

		if d.IsRunning() {
			// Indicate instance is running to target (can trigger MultiSync mode).
			offerHeader.Criu = migration.CRIUType_NONE.Enum()
		}
	}

	// Add idmap info to source header for containers.
	idmapset, err := d.DiskIdmap()
	if err != nil {
		err := fmt.Errorf("Failed getting container disk idmap: %w", err)
		op.Done(err)
		return err
	} else if idmapset != nil {
		offerHeader.Idmap = make([]*migration.IDMapType, 0, len(idmapset.Entries))
		for _, ctnIdmap := range idmapset.Entries {
			idmap := migration.IDMapType{
				Isuid:    proto.Bool(ctnIdmap.IsUID),
				Isgid:    proto.Bool(ctnIdmap.IsGID),
				Hostid:   proto.Int32(int32(ctnIdmap.HostID)),
				Nsid:     proto.Int32(int32(ctnIdmap.NSID)),
				Maprange: proto.Int32(int32(ctnIdmap.MapRange)),
			}

			offerHeader.Idmap = append(offerHeader.Idmap, &idmap)
		}
	}

	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)
	if err != nil {
		err := fmt.Errorf("Failed generating instance depending volumes offer: %w", err)
		op.Done(err)
		return err
	}

	offerHeader.DependentVolumes = dependentVolumesOffer

	// 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)
			offerHeader.Snapshots = append(offerHeader.Snapshots, instance.SnapshotToProtobuf(srcConfig.Snapshots[i]))
		}
	}

	// 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: %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)
	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, migration.MigrationFSType_RSYNC, 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)
	if err != nil {
		err := fmt.Errorf("Failed to negotiate migration types for dependent volumes: %w", err)
		op.Done(err)
		return err
	}

	d.logger.Debug("Generate dependent volumes args")
	dependentVolumes := []localMigration.DependentVolumeArgs{}
	for _, volWithType := range volumesWithTypes {
		dependentVolumes = append(dependentVolumes, localMigration.ProtobufToDependentVolume(volWithType.Volume, volWithType.VolumeTypes[0]))
	}

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

	// If s.live is true or Criu is set to CRIUType_NONE rather than nil, it indicates that the source instance
	// is running, and if we are doing a non-optimized transfer (i.e using rsync or raw block transfer) then we
	// should do a two stage transfer to minimize downtime.
	instanceRunning := args.Live || (respHeader.Criu != nil && *respHeader.Criu == migration.CRIUType_NONE)
	nonOptimizedMigration := volSourceArgs.MigrationType.FSType == migration.MigrationFSType_RSYNC || volSourceArgs.MigrationType.FSType == migration.MigrationFSType_BLOCK_AND_RSYNC
	if instanceRunning && nonOptimizedMigration {
		// Indicate this info to the storage driver so that it can alter its behaviour if needed.
		volSourceArgs.MultiSync = true
	}

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

	restoreSuccess := make(chan bool, 1)
	defer close(restoreSuccess)

	// Don't defer close this one as its needed potentially after this function has ended.
	dumpSuccess := make(chan error, 1)

	g.Go(func() error {
		d.logger.Debug("Migrate send transfer started")
		defer d.logger.Debug("Migrate send transfer finished")

		var err error

		d.logger.Debug("Starting storage migration phase")

		err = pool.MigrateInstance(d, filesystemConn, volSourceArgs, d.op)
		if err != nil {
			return err
		}

		d.logger.Debug("Finished storage migration phase")

		if args.Live {
			d.logger.Debug("Starting live migration phase")

			// Setup rsync options (used for CRIU state transfers).
			rsyncBwlimit := pool.Driver().Config()["rsync.bwlimit"]
			rsyncFeatures := respHeader.GetRsyncFeaturesSlice()
			if !slices.Contains(rsyncFeatures, "bidirectional") {
				// If no bi-directional support, assume 3.7 level.
				// NOTE: Do NOT extend this list of arguments.
				rsyncFeatures = []string{"xattrs", "delete", "compress"}
			}

			if respHeader.Criu == nil {
				return errors.New("Got no CRIU socket type for live migration")
			} else if *respHeader.Criu != migration.CRIUType_CRIU_RSYNC {
				return fmt.Errorf("Formats other than criu rsync not understood (%q)", respHeader.Criu)
			}

			checkpointDir, err := os.MkdirTemp("", "incus_checkpoint_")
			if err != nil {
				return err
			}

			if liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 0, 4) {
				// What happens below is slightly convoluted. Due to various complications
				// with networking, there's no easy way for criu to exit and leave the
				// container in a frozen state for us to somehow resume later.
				// Instead, we use what criu calls an "action-script", which is basically a
				// callback that lets us know when the dump is done. (Unfortunately, we
				// can't pass arguments, just an executable path, so we write a custom
				// action script with the real command we want to run.)
				// This script then blocks until the migration operation either finishes
				// successfully or fails, and exits 1 or 0, which causes criu to either
				// leave the container running or kill it as we asked.
				dumpDone := make(chan bool, 1)
				actionScriptOpSecret, err := internalUtil.RandomHexString(32)
				if err != nil {
					_ = os.RemoveAll(checkpointDir)
					return err
				}

				actionScriptOp, err := operations.OperationCreate(
					d.state,
					d.Project().Name,
					operations.OperationClassWebsocket,
					operationtype.InstanceLiveMigrate,
					nil,
					nil,
					func(op *operations.Operation) error {
						result := <-restoreSuccess
						if !result {
							return errors.New("restore failed, failing CRIU")
						}

						return nil
					},
					nil,
					func(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
						secret := r.FormValue("secret")
						if secret == "" {
							return errors.New("Missing action script secret")
						}

						if secret != actionScriptOpSecret {
							return os.ErrPermission
						}

						c, err := ws.Upgrader.Upgrade(w, r, nil)
						if err != nil {
							return err
						}

						dumpDone <- true

						closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
						return c.WriteMessage(websocket.CloseMessage, closeMsg)
					},
					nil,
				)
				if err != nil {
					_ = os.RemoveAll(checkpointDir)
					return err
				}

				err = d.migrationSendWriteActionScript(checkpointDir, actionScriptOp.URL(), actionScriptOpSecret, d.state.OS.ExecPath)
				if err != nil {
					_ = os.RemoveAll(checkpointDir)
					return err
				}

				preDumpCounter := 0
				preDumpDir := ""

				// Check if the other side knows about pre-dumping and the associated
				// rsync protocol.
				if respHeader.GetPredump() {
					d.logger.Debug("The other side does support pre-copy")
					final := false
					for !final {
						preDumpCounter++
						if preDumpCounter < maxDumpIterations {
							final = false
						} else {
							final = true
						}

						dumpDir := fmt.Sprintf("%03d", preDumpCounter)
						loopArgs := preDumpLoopArgs{
							stateConn:     stateConn,
							checkpointDir: checkpointDir,
							bwlimit:       rsyncBwlimit,
							preDumpDir:    preDumpDir,
							dumpDir:       dumpDir,
							final:         final,
							rsyncFeatures: rsyncFeatures,
						}

						final, err = d.migrateSendPreDumpLoop(&loopArgs)
						if err != nil {
							_ = os.RemoveAll(checkpointDir)
							return err
						}

						preDumpDir = fmt.Sprintf("%03d", preDumpCounter)
						preDumpCounter++
					}
				} else {
					d.logger.Debug("The other side does not support pre-copy")
				}

				err = actionScriptOp.Start()
				if err != nil {
					_ = os.RemoveAll(checkpointDir)
					return err
				}

				go func() {
					d.logger.Debug("Final CRIU dump started")
					defer d.logger.Debug("Final CRIU dump stopped")
					criuMigrationArgs := instance.CriuMigrationArgs{
						Cmd:          liblxc.MIGRATE_DUMP,
						Stop:         true,
						ActionScript: true,
						PreDumpDir:   preDumpDir,
						DumpDir:      "final",
						StateDir:     checkpointDir,
						Function:     "migration",
					}

					// Do the final CRIU dump. This is needs no special handling if
					// pre-dumps are used or not.
					dumpSuccess <- d.migrate(&criuMigrationArgs)
					_ = os.RemoveAll(checkpointDir)
				}()

				select {
				// The checkpoint failed, let's just abort.
				case err = <-dumpSuccess:
					return err
				// The dump finished, let's continue on to the restore.
				case <-dumpDone:
					d.logger.Debug("Dump finished, continuing with restore...")
				}
			} else {
				d.logger.Debug("The version of liblxc is older than 2.0.4 and the live migration will probably fail")
				defer func() { _ = os.RemoveAll(checkpointDir) }()
				criuMigrationArgs := instance.CriuMigrationArgs{
					Cmd:          liblxc.MIGRATE_DUMP,
					StateDir:     checkpointDir,
					Function:     "migration",
					Stop:         true,
					ActionScript: false,
					DumpDir:      "final",
					PreDumpDir:   "",
				}

				err = d.migrate(&criuMigrationArgs)
				if err != nil {
					return err
				}
			}

			// We do the transfer serially right now, but there's really no reason for us to;
			// since we have separate websockets, we can do it in parallel if we wanted to.
			// However assuming we're network bound, there's really no reason to do these in.
			// parallel. In the future when we're using p.haul's protocol, it will make sense
			// to do these in parallel.
			ctName, _, _ := api.GetParentAndSnapshotName(d.Name())
			err = rsync.Send(ctName, internalUtil.AddSlash(checkpointDir), stateConn, nil, rsyncFeatures, rsyncBwlimit, d.state.OS.ExecPath)
			if err != nil {
				return err
			}

			d.logger.Debug("Finished live migration phase")
		}

		// Perform final sync if in multi sync mode.
		if volSourceArgs.MultiSync {
			d.logger.Debug("Starting final storage migration phase")

			// Indicate to the storage driver we are doing final sync and because of this don't send
			// snapshots as they don't need to have a final sync as not being modified.
			volSourceArgs.FinalSync = true
			volSourceArgs.Snapshots = nil
			volSourceArgs.Info.Config.VolumeSnapshots = nil

			err = pool.MigrateInstance(d, filesystemConn, volSourceArgs, d.op)
			if err != nil {
				return err
			}

			d.logger.Debug("Finished final storage migration phase")
		}

		return nil
	})

	{
		// Wait for routines to finish and collect first error.
		err := g.Wait()

		if args.Live {
			restoreSuccess <- err == nil

			if err == nil {
				err := <-dumpSuccess
				if err != nil {
					d.logger.Error("Dump failed after successful restore", logger.Ctx{"err": err})
				}
			}
		}

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

type preDumpLoopArgs struct {
	stateConn     io.ReadWriteCloser
	checkpointDir string
	bwlimit       string
	preDumpDir    string
	dumpDir       string
	final         bool
	rsyncFeatures []string
}

// migrateSendPreDumpLoop is the main logic behind the pre-copy migration.
// This function contains the actual pre-dump, the corresponding rsync transfer and it tells the outer loop to
// abort if the threshold of memory pages transferred by pre-dumping has been reached.
func (d *lxc) migrateSendPreDumpLoop(args *preDumpLoopArgs) (bool, error) {
	// Do a CRIU pre-dump
	criuMigrationArgs := instance.CriuMigrationArgs{
		Cmd:          liblxc.MIGRATE_PRE_DUMP,
		Stop:         false,
		ActionScript: false,
		PreDumpDir:   args.preDumpDir,
		DumpDir:      args.dumpDir,
		StateDir:     args.checkpointDir,
		Function:     "migration",
	}

	d.logger.Debug("Doing another CRIU pre-dump", logger.Ctx{"preDumpDir": args.preDumpDir})

	final := args.final

	if d.Type() != instancetype.Container {
		return false, errors.New("Instance is not container type")
	}

	err := d.migrate(&criuMigrationArgs)
	if err != nil {
		return final, fmt.Errorf("Failed sending instance: %w", err)
	}

	// Send the pre-dump.
	ctName, _, _ := api.GetParentAndSnapshotName(d.Name())
	err = rsync.Send(ctName, internalUtil.AddSlash(args.checkpointDir), args.stateConn, nil, args.rsyncFeatures, args.bwlimit, d.state.OS.ExecPath)
	if err != nil {
		return final, err
	}

	// The function readCriuStatsDump() reads the CRIU 'stats-dump' file
	// in path and returns the pages_written, pages_skipped_parent, error.
	readCriuStatsDump := func(path string) (uint64, uint64, error) {
		// Get dump statistics with crit
		dumpStats, err := crit.GetDumpStats(path)
		if err != nil {
			return 0, 0, fmt.Errorf("Failed to parse CRIU's 'stats-dump' file: %w", err)
		}

		return dumpStats.GetPagesWritten(), dumpStats.GetPagesSkippedParent(), nil
	}

	// Read the CRIU's 'stats-dump' file
	dumpPath := internalUtil.AddSlash(args.checkpointDir)
	dumpPath += internalUtil.AddSlash(args.dumpDir)
	written, skippedParent, err := readCriuStatsDump(dumpPath)
	if err != nil {
		return final, err
	}

	totalPages := written + skippedParent
	var percentageSkipped int
	if totalPages > 0 {
		percentageSkipped = int(100 - ((100 * written) / totalPages))
	}

	d.logger.Debug("CRIU pages", logger.Ctx{"pages": written, "skipped": skippedParent, "skippedPerc": percentageSkipped})

	// threshold is the percentage of memory pages that needs
	// to be pre-copied for the pre-copy migration to stop.
	var threshold int
	tmp := d.ExpandedConfig()["migration.incremental.memory.goal"]
	if tmp != "" {
		threshold, _ = strconv.Atoi(tmp)
	} else {
		// defaults to 70%
		threshold = 70
	}

	if percentageSkipped > threshold {
		d.logger.Debug("Memory pages skipped due to pre-copy is larger than threshold", logger.Ctx{"skippedPerc": percentageSkipped, "thresholdPerc": threshold})
		d.logger.Debug("This was the last pre-dump; next dump is the final dump")
		final = true
	}

	// If in pre-dump mode, the receiving side expects a message to know if this was the last pre-dump.
	logger.Debug("Sending another CRIU pre-dump header")
	sync := migration.MigrationSync{
		FinalPreDump: proto.Bool(final),
	}

	data, err := proto.Marshal(&sync)
	if err != nil {
		return false, err
	}

	_, err = args.stateConn.Write(data)
	if err != nil {
		return final, err
	}

	d.logger.Debug("Sending another CRIU pre-dump header done")

	return final, nil
}

func (d *lxc) resetContainerDiskIdmap(srcIdmap *idmap.Set) error {
	dstIdmap, err := d.DiskIdmap()
	if err != nil {
		return err
	}

	if dstIdmap == nil {
		dstIdmap = &idmap.Set{}
	}

	if !srcIdmap.Equals(dstIdmap) {
		jsonIdmap, err := srcIdmap.ToJSON()
		if err != nil {
			return fmt.Errorf("Failed to encode ID map: %w", err)
		}

		d.logger.Debug("Setting new volatile.last_state.idmap from source instance", logger.Ctx{"sourceIdmap": srcIdmap})
		err = d.VolatileSet(map[string]string{"volatile.last_state.idmap": jsonIdmap})
		if err != nil {
			return err
		}
	}

	return nil
}

func (d *lxc) 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
	}

	var stateConn io.ReadWriteCloser
	if args.Live {
		stateConn, err = args.StateConn(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)
	if err != nil {
		return fmt.Errorf("Failed receiving migration offer from source: %w", err)
	}

	criuType := migration.CRIUType_CRIU_RSYNC.Enum()
	if offerHeader.Criu != nil && *offerHeader.Criu == migration.CRIUType_NONE {
		criuType = migration.CRIUType_NONE.Enum()
	} else {
		if !args.Live {
			criuType = nil
		}
	}

	// 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 {
		_, 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

	// Add CRIU info to response.
	respHeader.Criu = criuType

	volumesWithTypes, err := storagePools.DependentVolumesMatchMigrationType(d.state, offerHeader.DependentVolumes, args.Snapshots)
	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)
		dependentVolumes = append(dependentVolumes, localMigration.ProtobufToDependentVolume(volWithType.Volume, volWithType.VolumeTypes[0]))
	}

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

	if offerHeader.GetPredump() {
		// If the other side wants pre-dump and if this side supports it, let's use it.
		respHeader.Predump = proto.Bool(true)
	} else {
		respHeader.Predump = proto.Bool(false)
	}

	// Get rsync options from sender, these are passed into mySink function as part of
	// MigrationSinkArgs below.
	rsyncFeatures := respHeader.GetRsyncFeaturesSlice()

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

	srcIdmap := &idmap.Set{}
	for _, idmapSet := range offerHeader.Idmap {
		e := idmap.Entry{
			IsUID:    *idmapSet.Isuid,
			IsGID:    *idmapSet.Isgid,
			NSID:     int64(*idmapSet.Nsid),
			HostID:   int64(*idmapSet.Hostid),
			MapRange: int64(*idmapSet.Maprange),
		}

		srcIdmap.Entries = append(srcIdmap.Entries, e)
	}

	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)
			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 filesystem transfer started")
		defer d.logger.Debug("Migrate receive filesystem transfer finished")

		var err error

		// We do the fs receive in parallel so we don't have to reason about when to receive
		// what. The sending side is smart enough to send the filesystem bits that it can
		// before it seizes the container to start checkpointing, so the total transfer time
		// will be minimized even if we're dumb here.
		snapshots := []*migration.Snapshot{}

		// Legacy: we only sent the snapshot names, so we just copy the container'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
		}

		// Default to not expecting to receive the final rootfs sync.
		sendFinalFsDelta := false

		// If we are doing a stateful live transfer or the CRIU type indicates we
		// are doing a stateless transfer with a running instance then we should
		// expect the source to send us a final rootfs sync.
		if args.Live {
			sendFinalFsDelta = true
		} else if criuType != nil && *criuType == migration.CRIUType_NONE {
			sendFinalFsDelta = true
		}

		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:                  sendFinalFsDelta,            // Indicates we will get a final rootfs sync.
			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 container'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 {
				volTargetArgs.Snapshots = append(volTargetArgs.Snapshots, &migration.Snapshot{Name: snap.Name})

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

					// Ensure that snapshot and parent container 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 && pool.Driver().Info().Remote

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

		// For containers, the fs map of the source is sent as part of the migration
		// stream, then at the end we need to record that map as last_state so that
		// shifting can happen on startup if needed.
		err = d.resetContainerDiskIdmap(srcIdmap)
		if err != nil {
			return err
		}

		if args.ClusterMoveSourceName != d.name {
			err = d.DeferTemplateApply(instance.TemplateTriggerCopy)
			if err != nil {
				return err
			}
		}

		return nil
	})

	// Start live state transfer routine (if required) and initialize a channel that is closed when the
	// routine finishes. It is never closed if the routine is not started.
	stateTransferDone := make(chan struct{})
	if args.Live {
		g.Go(func() error {
			d.logger.Debug("Migrate receive state transfer started")
			defer d.logger.Debug("Migrate receive state transfer finished")

			defer close(stateTransferDone)

			imagesDir, err := os.MkdirTemp("", "incus_restore_")
			if err != nil {
				return err
			}

			defer func() { _ = os.RemoveAll(imagesDir) }()

			sync := &migration.MigrationSync{
				FinalPreDump: proto.Bool(false),
			}

			if respHeader.GetPredump() {
				for !sync.GetFinalPreDump() {
					d.logger.Debug("Waiting to receive pre-dump rsync")

					// Transfer a CRIU pre-dump.
					err = rsync.Recv(internalUtil.AddSlash(imagesDir), stateConn, nil, rsyncFeatures)
					if err != nil {
						return fmt.Errorf("Failed receiving pre-dump rsync: %w", err)
					}

					d.logger.Debug("Done receiving pre-dump rsync")

					d.logger.Debug("Waiting to receive pre-dump header")

					// We can't use io.ReadAll here because sender doesn't call Close() to
					// send the frame end indicator after writing the pre-dump header.
					// So define a small buffer sufficient to fit migration.MigrationSync and
					// then read what we have into it.
					buf := make([]byte, 128)
					n, err := stateConn.Read(buf)
					if err != nil {
						return fmt.Errorf("Failed receiving pre-dump header: %w", err)
					}

					err = proto.Unmarshal(buf[:n], sync)
					if err != nil {
						return fmt.Errorf("Failed unmarshalling pre-dump header: %w (%v)", err, string(buf))
					}

					d.logger.Debug("Done receiving pre-dump header")
				}
			}

			// Final CRIU dump.
			d.logger.Debug("About to receive final dump rsync")
			err = rsync.Recv(internalUtil.AddSlash(imagesDir), stateConn, nil, rsyncFeatures)
			if err != nil {
				return fmt.Errorf("Failed receiving final dump rsync: %w", err)
			}

			d.logger.Debug("Done receiving final dump rsync")

			// Wait until filesystem transfer is done before starting final state sync and restore.
			<-fsTransferDone

			// But only proceed if no errors have occurred thus far.
			err = ctx.Err()
			if err != nil {
				return err
			}

			criuMigrationArgs := instance.CriuMigrationArgs{
				Cmd:          liblxc.MIGRATE_RESTORE,
				StateDir:     imagesDir,
				Function:     "migration",
				Stop:         false,
				ActionScript: false,
				DumpDir:      "final",
				PreDumpDir:   "",
			}

			// Currently we only do a single CRIU pre-dump so we can hardcode "final"
			// here since we know that "final" is the folder for CRIU's final dump.
			err = d.migrate(&criuMigrationArgs)
			if err != nil {
				return err
			}

			return nil
		})
	}

	{
		// Wait until the filesystem transfer and state transfer routines have finished.
		<-fsTransferDone
		if args.Live {
			<-stateTransferDone
		}

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

// Migrate migrates the instance to another node.
func (d *lxc) migrate(args *instance.CriuMigrationArgs) error {
	ctxMap := logger.Ctx{
		"created":      d.creationDate,
		"ephemeral":    d.ephemeral,
		"used":         d.lastUsedDate,
		"statedir":     args.StateDir,
		"actionscript": args.ActionScript,
		"predumpdir":   args.PreDumpDir,
		"features":     args.Features,
		"stop":         args.Stop,
	}

	_, err := exec.LookPath("criu")
	if err != nil {
		return localMigration.ErrNoLiveMigration
	}

	d.logger.Info("Migrating container", ctxMap)

	prettyCmd := ""
	switch args.Cmd {
	case liblxc.MIGRATE_PRE_DUMP:
		prettyCmd = "pre-dump"
	case liblxc.MIGRATE_DUMP:
		prettyCmd = "dump"
	case liblxc.MIGRATE_RESTORE:
		prettyCmd = "restore"
	default:
		prettyCmd = "unknown"
		d.logger.Warn("Unknown migrate call", logger.Ctx{"cmd": args.Cmd})
	}

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

	preservesInodes := pool.Driver().Info().PreservesInodes

	/* This feature was only added in 2.0.1, let's not ask for it
	 * before then or migrations will fail.
	 */
	if !liblxc.RuntimeLiblxcVersionAtLeast(liblxc.Version(), 2, 0, 1) {
		preservesInodes = false
	}

	finalStateDir := args.StateDir
	var migrateErr error

	/* For restore, we need an extra fork so that we daemonize the monitor
	 * instead of having it be a child. So let's hijack the command
	 * here and do the extra fork.
	 */
	if args.Cmd == liblxc.MIGRATE_RESTORE {
		// Check that we're not already running.
		if d.IsRunning() {
			return errors.New("The container is already running")
		}

		// Run the shared start code.
		configPath, postStartHooks, err := d.startCommon()
		if err != nil {
			if args.Op != nil {
				args.Op.Done(err)
			}

			return err
		}

		/*
		 * For unprivileged containers we need to shift the
		 * perms on the images images so that they can be
		 * opened by the process after it is in its user
		 * namespace.
		 */
		idmapset, err := d.CurrentIdmap()
		if err != nil {
			return err
		}

		if idmapset != nil {
			storageType, err := d.getStorageType()
			if err != nil {
				return fmt.Errorf("Storage type: %w", err)
			}

			if storageType == "zfs" {
				err = idmapset.ShiftPath(args.StateDir, storageDrivers.ShiftZFSSkipper)
			} else if storageType == "btrfs" {
				err = storageDrivers.ShiftBtrfsRootfs(args.StateDir, idmapset)
			} else {
				err = idmapset.ShiftPath(args.StateDir, nil)
			}

			if err != nil {
				return err
			}
		}

		if args.DumpDir != "" {
			finalStateDir = fmt.Sprintf("%s/%s", args.StateDir, args.DumpDir)
		}

		_, migrateErr = subprocess.RunCommand(
			d.state.OS.ExecPath,
			"forkmigrate",
			d.name,
			d.state.OS.LxcPath,
			configPath,
			finalStateDir,
			fmt.Sprintf("%v", preservesInodes),
		)

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

				// Attempt to stop container.
				_ = d.Stop(false)

				return err
			}
		}
	} else {
		// Load the go-lxc struct
		var cc *liblxc.Container
		if d.expandedConfig["raw.lxc"] != "" {
			cc, err = d.initLXC(true)
			if err != nil {
				return err
			}

			err = d.loadRawLXCConfig(cc)
			if err != nil {
				return err
			}
		} else {
			cc, err = d.initLXC(false)
			if err != nil {
				return err
			}
		}

		script := ""
		if args.ActionScript {
			script = filepath.Join(args.StateDir, "action.sh")
		}

		if args.DumpDir != "" {
			finalStateDir = fmt.Sprintf("%s/%s", args.StateDir, args.DumpDir)
		}

		// TODO: make this configurable? Ultimately I think we don't
		// want to do that; what we really want to do is have "modes"
		// of criu operation where one is "make this succeed" and the
		// other is "make this fast". Anyway, for now, let's choose a
		// really big size so it almost always succeeds, even if it is
		// slow.
		ghostLimit := uint64(256 * 1024 * 1024)

		opts := liblxc.MigrateOptions{
			Stop:            args.Stop,
			Directory:       finalStateDir,
			Verbose:         true,
			PreservesInodes: preservesInodes,
			ActionScript:    script,
			GhostLimit:      ghostLimit,
		}

		if args.PreDumpDir != "" {
			opts.PredumpDir = fmt.Sprintf("../%s", args.PreDumpDir)
		}

		if !d.IsRunning() {
			// otherwise the migration will needlessly fail
			args.Stop = false
		}

		migrateErr = cc.Migrate(args.Cmd, opts)
	}

	collectErr := collectCRIULogFile(d, finalStateDir, args.Function, prettyCmd)
	if collectErr != nil {
		d.logger.Error("Error collecting checkpoint log file", logger.Ctx{"err": collectErr})
	}

	if migrateErr != nil {
		log, err2 := getCRIULogErrors(finalStateDir, prettyCmd)
		if err2 == nil {
			d.logger.Warn("Failed migrating container", ctxMap)
			migrateErr = fmt.Errorf("%s %s failed\n%s", args.Function, prettyCmd, log)
		}

		return migrateErr
	}

	d.logger.Info("Migrated container", ctxMap)

	return nil
}

func (d *lxc) templateApplyNow(trigger instance.TemplateTrigger) 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.Unmarshal(content, &metadata)
	if err != nil {
		return fmt.Errorf("Could not parse %s: %w", fname, err)
	}

	// Find rootUID and rootGID
	idmapset, err := d.DiskIdmap()
	if err != nil {
		return fmt.Errorf("Failed to set ID map: %w", err)
	}

	rootUID := int64(0)
	rootGID := int64(0)

	// Get the right uid and gid for the container
	if idmapset != nil {
		rootUID, rootGID = idmapset.ShiftIntoNS(0, 0)
	}

	// Figure out the container 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 container metadata
	containerMeta := make(map[string]string)
	containerMeta["name"] = d.name
	containerMeta["type"] = "container"
	containerMeta["architecture"] = arch

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

	if d.IsPrivileged() {
		containerMeta["privileged"] = "true"
	} else {
		containerMeta["privileged"] = "false"
	}

	// Setup security check.
	rootfsPath, err := os.OpenFile(d.RootfsPath(), unix.O_PATH, 0)
	if err != nil {
		return fmt.Errorf("Failed to open instance rootfs path: %w", err)
	}

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

	checkBeneath := func(targetPath string) error {
		fd, err := unix.Openat2(int(rootfsPath.Fd()), targetPath, &unix.OpenHow{
			Flags:   unix.O_PATH | unix.O_CLOEXEC,
			Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS,
		})
		if err != nil {
			if errors.Is(err, unix.EXDEV) {
				return errors.New("Template is attempting access to path outside of container")
			}

			return nil
		}

		_ = unix.Close(fd)

		return nil
	}

	// 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 security checks.
			relPath := strings.TrimLeft(tplPath, "/")

			err = checkBeneath(relPath)
			if err != nil {
				return err
			}

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

			// Open the file to template, create if needed
			fullpath := filepath.Join(d.RootfsPath(), relPath)
			if util.PathExists(fullpath) {
				if tpl.CreateOnly {
					return nil
				}

				// Open the existing file
				w, err = os.Create(fullpath)
				if err != nil {
					return fmt.Errorf("Failed to create template file: %w", err)
				}
			} else {
				// UID and GID
				fileUID := int64(0)
				fileGID := int64(0)

				if tpl.UID != "" {
					id, err := strconv.ParseInt(tpl.UID, 10, 64)
					if err != nil {
						return fmt.Errorf("Bad file UID %q for %q: %w", tpl.UID, tplPath, err)
					}

					fileUID = id
				}

				if tpl.GID != "" {
					id, err := strconv.ParseInt(tpl.GID, 10, 64)
					if err != nil {
						return fmt.Errorf("Bad file GID %q for %q: %w", tpl.GID, tplPath, err)
					}

					fileGID = id
				}

				if idmapset != nil {
					fileUID, fileGID = idmapset.ShiftIntoNS(fileUID, fileGID)
				}

				// Mode
				fileMode := fs.FileMode(0o644)
				if tpl.Mode != "" {
					if len(tpl.Mode) == 3 {
						tpl.Mode = fmt.Sprintf("0%s", tpl.Mode)
					}

					mode, err := strconv.ParseInt(tpl.Mode, 0, 0)
					if err != nil {
						return fmt.Errorf("Bad mode %q for %q: %w", tpl.Mode, tplPath, err)
					}

					fileMode = os.FileMode(mode) & os.ModePerm
				}

				// Create the directories leading to the file
				err = internalUtil.MkdirAllOwner(path.Dir(fullpath), 0o755, int(rootUID), int(rootGID))
				if err != nil {
					return err
				}

				// Create the file itself
				w, err = os.Create(fullpath)
				if err != nil {
					return err
				}

				// Fix ownership and mode
				err = w.Chown(int(fileUID), int(fileGID))
				if err != nil {
					return err
				}

				err = w.Chmod(fileMode)
				if err != nil {
					return err
				}
			}
			defer func() { _ = w.Close() }()

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

			err = internalUtil.RenderTemplateFile(w, string(tplString), pongo2.Context{
				"trigger":    trigger,
				"path":       tplPath,
				"container":  containerMeta,
				"instance":   containerMeta,
				"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
}

func (d *lxc) inheritInitPidFd() *os.File {
	if d.state.OS.PidFds {
		pidFdFile, err := d.InitPidFd()
		if err != nil {
			return nil
		}

		return pidFdFile
	}

	return nil
}

// FileSFTPConn returns a connection to the forkfile handler.
func (d *lxc) FileSFTPConn() (net.Conn, error) {
	// Lock to avoid concurrent spawning.
	spawnUnlock, err := locking.Lock(context.TODO(), fmt.Sprintf("forkfile_%d", d.id))
	if err != nil {
		return nil, err
	}

	defer spawnUnlock()

	// Create any missing directories in case the instance has never been started before.
	err = os.MkdirAll(d.RunPath(), 0o700)
	if err != nil {
		return nil, err
	}

	// Trickery to handle paths > 108 chars.
	dirFile, err := os.Open(d.RunPath())
	if err != nil {
		return nil, err
	}

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

	forkfileAddr, err := net.ResolveUnixAddr("unix", fmt.Sprintf("/proc/self/fd/%d/forkfile.sock", dirFile.Fd()))
	if err != nil {
		return nil, err
	}

	// Attempt to connect on existing socket.
	forkfilePath := filepath.Join(d.RunPath(), "forkfile.sock")
	forkfileConn, err := net.DialUnix("unix", nil, forkfileAddr)
	if err == nil {
		// Found an existing server.
		return forkfileConn, nil
	}

	// Check for ongoing operations (that may involve shifting or replacing the root volume) so as to avoid
	// allowing SFTP access while the container's filesystem setup is in flux.
	// If there is an update operation ongoing and the instance is running then do not wait for the operation
	// to complete before continuing as it is possible that a disk device is being removed that requires SFTP
	// to clean up the path inside the container. Also it is not possible to be shifting/replacing the root
	// volume when the instance is running, so there should be no reason to wait for the operation to finish.
	op := operationlock.Get(d.Project().Name, d.Name())
	if op.Action() != operationlock.ActionUpdate || !d.IsRunning() {
		_ = op.Wait(context.Background())
	}

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

	// Create the listener.
	_ = os.Remove(forkfilePath)
	forkfileListener, err := net.ListenUnix("unix", forkfileAddr)
	if err != nil {
		return nil, err
	}

	reverter.Add(func() {
		_ = forkfileListener.Close()
		_ = os.Remove(forkfilePath)
	})

	// Spawn forkfile in a Go routine.
	chReady := make(chan error)
	go func() {
		// Lock to avoid concurrent running forkfile.
		runUnlock, err := locking.Lock(context.TODO(), d.forkfileRunningLockName())
		if err != nil {
			chReady <- err
			return
		}

		defer runUnlock()

		// Mount the filesystem if needed.
		if !d.IsRunning() {
			// Mount the root filesystem if required.
			_, err := d.mount()
			if err != nil {
				chReady <- err
				return
			}

			defer func() { _ = d.unmount() }()
		}

		// Start building the command.
		args := []string{
			d.state.OS.ExecPath,
			"forkfile",
			"--",
		}

		extraFiles := []*os.File{}

		// Get the listener file.
		forkfileFile, err := forkfileListener.File()
		if err != nil {
			chReady <- err
			return
		}

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

		args = append(args, "3")
		extraFiles = append(extraFiles, forkfileFile)

		// Get the rootfs.
		rootfsFile, err := os.Open(d.RootfsPath())
		if err != nil {
			chReady <- err
			return
		}

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

		args = append(args, "4")
		extraFiles = append(extraFiles, rootfsFile)

		// Get the pidfd.
		pidFd := d.inheritInitPidFd()
		if pidFd != nil {
			defer func() { _ = pidFd.Close() }()
			args = append(args, "5")
			extraFiles = append(extraFiles, pidFd)
		} else {
			args = append(args, "-1")
		}

		// Finalize the args.
		args = append(args, fmt.Sprintf("%d", d.InitPID()))

		// Prepare sftp server.
		forkfile := exec.Cmd{
			Path:       d.state.OS.ExecPath,
			Args:       args,
			ExtraFiles: extraFiles,
		}

		var stderr bytes.Buffer
		forkfile.Stderr = &stderr

		if !d.IsRunning() {
			// Get the disk idmap.
			idmapset, err := d.DiskIdmap()
			if err != nil {
				chReady <- err
				return
			}

			if idmapset != nil {
				forkfile.SysProcAttr = &syscall.SysProcAttr{
					Cloneflags: syscall.CLONE_NEWUSER,
					Credential: &syscall.Credential{
						Uid: uint32(0),
						Gid: uint32(0),
					},
					UidMappings: idmapset.ToUIDMappings(),
					GidMappings: idmapset.ToGIDMappings(),
				}
			}
		}

		// Start the server.
		err = forkfile.Start()
		if err != nil {
			chReady <- fmt.Errorf("Failed to run forkfile: %w: %s", err, strings.TrimSpace(stderr.String()))
			return
		}

		// Write PID file.
		pidFile := filepath.Join(d.RunPath(), "forkfile.pid")
		err = os.WriteFile(pidFile, fmt.Appendf(nil, "%d\n", forkfile.Process.Pid), 0o600)
		if err != nil {
			chReady <- fmt.Errorf("Failed to write forkfile PID: %w", err)
			return
		}

		// Close the listener and delete the socket immediately after forkfile exits to avoid clients
		// thinking a listener is available while other deferred calls are being processed.
		defer func() {
			_ = forkfileListener.Close()
			_ = os.Remove(forkfilePath)
			_ = os.Remove(pidFile)
		}()

		// Indicate the process was spawned without error.
		close(chReady)

		// Wait for completion.
		err = forkfile.Wait()
		if err != nil {
			d.logger.Error("SFTP server stopped with error", logger.Ctx{"err": err, "stderr": strings.TrimSpace(stderr.String())})
			return
		}
	}()

	// Wait for forkfile to have been spawned.
	err = <-chReady
	if err != nil {
		return nil, err
	}

	// Connect to the new server.
	forkfileConn, err = net.DialUnix("unix", nil, forkfileAddr)
	if err != nil {
		return nil, err
	}

	// All done.
	reverter.Success()

	return forkfileConn, nil
}

// FileSFTP returns an SFTP connection to the forkfile handler.
func (d *lxc) 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
}

// stopForkFile attempts to send SIGTERM (if force is true) or SIGINT to forkfile then waits for it to exit.
func (d *lxc) stopForkfile(force bool) {
	// Make sure that when the function exits, no forkfile is running by acquiring the lock (which indicates
	// that forkfile isn't running and holding the lock) and then releasing it.
	defer func() {
		unlock, err := locking.Lock(context.TODO(), d.forkfileRunningLockName())
		if err != nil {
			return
		}

		unlock()
	}()

	content, err := os.ReadFile(filepath.Join(d.RunPath(), "forkfile.pid"))
	if err != nil {
		return
	}

	pid, err := strconv.ParseInt(strings.TrimSpace(string(content)), 10, 64)
	if err != nil {
		return
	}

	d.logger.Debug("Stopping forkfile", logger.Ctx{"pid": pid, "force": force})

	if force {
		// Forcefully kill the running process.
		_ = unix.Kill(int(pid), unix.SIGTERM)
	} else {
		// Try to send SIGINT to forkfile to indicate it should not accept any new connection.
		_ = unix.Kill(int(pid), unix.SIGINT)
	}
}

// Console attaches to the instance console.
func (d *lxc) Console(protocol string) (*os.File, chan error, error) {
	if protocol != instance.ConsoleTypeConsole {
		return nil, nil, fmt.Errorf("Container instances don't support %q output", protocol)
	}

	chDisconnect := make(chan error, 1)

	args := []string{
		d.state.OS.ExecPath,
		"forkconsole",
		project.Instance(d.Project().Name, d.Name()),
		d.state.OS.LxcPath,
		filepath.Join(d.RunPath(), "lxc.conf"),
		"tty=0",
		"escape=-1",
	}

	idmapset, err := d.CurrentIdmap()
	if err != nil {
		return nil, nil, err
	}

	var rootUID, rootGID int64
	if idmapset != nil {
		rootUID, rootGID = idmapset.ShiftIntoNS(0, 0)
	}

	// Create a PTS pair.
	ptx, pty, err := linux.OpenPty(rootUID, rootGID)
	if err != nil {
		return nil, nil, err
	}

	// Switch the console file descriptor into raw mode.
	_, err = termios.MakeRaw(int(ptx.Fd()))
	if err != nil {
		return nil, nil, err
	}

	cmd := exec.Cmd{}
	cmd.Path = d.state.OS.ExecPath
	cmd.Args = args
	cmd.Stdin = pty
	cmd.Stdout = pty
	cmd.Stderr = pty

	err = cmd.Start()
	if err != nil {
		return nil, nil, err
	}

	go func() {
		err = cmd.Wait()
		_ = ptx.Close()
		_ = pty.Close()
	}()

	go func() {
		<-chDisconnect
		_ = cmd.Process.Kill()
	}()

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceConsole.Event(d, logger.Ctx{"type": instance.ConsoleTypeConsole}))

	return ptx, chDisconnect, nil
}

// ConsoleLog returns console log.
func (d *lxc) ConsoleLog(opts liblxc.ConsoleLogOptions) (string, error) {
	cc, err := d.initLXC(false)
	if err != nil {
		return "", err
	}

	msg, err := cc.ConsoleLog(opts)
	if err != nil {
		return "", err
	}

	if opts.ClearLog {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceConsoleReset.Event(d, nil))
	} else if opts.ReadLog && opts.WriteToLogFile {
		d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceConsoleRetrieved.Event(d, nil))
	}

	return string(msg), nil
}

// Exec executes a command inside the instance.
func (d *lxc) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
	// Generate the LXC config if missing.
	configPath := filepath.Join(d.RunPath(), "lxc.conf")
	if !util.PathExists(configPath) {
		cc, err := d.initLXC(true)
		if err != nil {
			return nil, fmt.Errorf("Load go-lxc struct: %w", err)
		}

		err = cc.SaveConfigFile(configPath)
		if err != nil {
			_ = os.Remove(configPath)
			return nil, err
		}
	}

	// Prepare the environment
	envSlice := []string{}

	for k, v := range req.Environment {
		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
	}

	// Setup logfile
	logPath := filepath.Join(d.LogPath(), "forkexec.log")
	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0o644)
	if err != nil {
		return nil, err
	}

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

	// Prepare the subcommand
	cname := project.Instance(d.Project().Name, d.Name())
	args := []string{
		d.state.OS.ExecPath,
		"forkexec",
		cname,
		d.state.OS.LxcPath,
		filepath.Join(d.RunPath(), "lxc.conf"),
		req.Cwd,
		fmt.Sprintf("%d", req.User),
		fmt.Sprintf("%d", req.Group),
	}

	if d.state.OS.CoreScheduling && !d.state.OS.ContainerCoreScheduling {
		args = append(args, "1")
	} else {
		args = append(args, "0")
	}

	args = append(args, "--")
	args = append(args, "env")
	args = append(args, envSlice...)

	args = append(args, "--")
	args = append(args, "cmd")
	args = append(args, req.Command...)

	cmd := exec.Cmd{}
	cmd.Path = d.state.OS.ExecPath
	cmd.Args = args

	cmd.Stdin = nil
	cmd.Stdout = logFile
	cmd.Stderr = logFile

	// Mitigation for CVE-2019-5736
	useRexec := false
	if d.expandedConfig["raw.idmap"] != "" {
		err := instance.AllowedUnprivilegedOnlyMap(d.expandedConfig["raw.idmap"])
		if err != nil {
			useRexec = true
		}
	}

	if util.IsTrue(d.expandedConfig["security.privileged"]) {
		useRexec = true
	}

	if useRexec {
		cmd.Env = append(os.Environ(), "LXC_MEMFD_REXEC=1")
	}

	// Setup communication PIPE
	rStatus, wStatus, err := os.Pipe()
	defer func() { _ = rStatus.Close() }()
	if err != nil {
		return nil, err
	}

	cmd.ExtraFiles = []*os.File{stdin, stdout, stderr, wStatus}
	err = cmd.Start()
	_ = wStatus.Close()
	if err != nil {
		return nil, err
	}

	attachedPid := linux.ReadPid(rStatus)
	if attachedPid <= 0 {
		_ = cmd.Wait()
		d.logger.Error("Failed to retrieve PID of executing child process")
		return nil, errors.New("Failed to retrieve PID of executing child process")
	}

	d.logger.Debug("Retrieved PID of executing child process", logger.Ctx{"attachedPid": attachedPid})

	d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceExec.Event(d, logger.Ctx{"command": req.Command}))

	instCmd := &lxcCmd{
		cmd:              &cmd,
		attachedChildPid: int(attachedPid),
	}

	return instCmd, nil
}

func (d *lxc) cpuStateUsage(cg *cgroup.CGroup) (int64, bool) {
	if !d.state.OS.CGInfo.Supports(cgroup.CPUAcct, cg) {
		return -1, false
	}

	value, err := cg.GetCPUAcctUsage()
	if err != nil {
		return -1, true
	}

	return value, true
}

func (d *lxc) cpuState() api.InstanceStateCPU {
	cpu := api.InstanceStateCPU{}

	cc, err := d.initLXC(false)
	if err != nil {
		return cpu
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return cpu
	}

	cpuUsage, ok := d.cpuStateUsage(cg)
	if ok {
		cpu.Usage = cpuUsage
	}

	cpuCount, err := cg.GetEffectiveCPUs()
	if err != nil {
		return cpu
	}

	limitPeriod, limitQuota, err := cg.GetCPUCfsLimit()
	if err != nil {
		return cpu
	}

	if limitQuota == -1 {
		cpu.AllocatedTime = int64(cpuCount) * 1_000_000_000
	} else {
		cpu.AllocatedTime = 1_000_000_000 * limitQuota / limitPeriod
	}

	return cpu
}

func (d *lxc) diskState() map[string]api.InstanceStateDisk {
	disk := map[string]api.InstanceStateDisk{}

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

		var usage *storagePools.VolumeUsage

		if dev.Config["path"] == "/" {
			pool, err := d.getStoragePool()
			if err != nil {
				d.logger.Error("Error loading storage pool", logger.Ctx{"err": err})
				continue
			}

			usage, err = pool.GetInstanceUsage(d)
			if err != nil {
				if !errors.Is(err, storageDrivers.ErrNotSupported) {
					d.logger.Error("Error getting disk usage", logger.Ctx{"err": err})
				}

				continue
			}
		} else if dev.Config["pool"] != "" {
			pool, err := storagePools.LoadByName(d.state, dev.Config["pool"])
			if err != nil {
				d.logger.Error("Error loading storage pool", logger.Ctx{"poolName": dev.Config["pool"], "err": err})
				continue
			}

			volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
			usage, err = pool.GetCustomVolumeUsage(d.Project().Name, volName)
			if err != nil {
				if !errors.Is(err, storageDrivers.ErrNotSupported) {
					d.logger.Error("Error getting volume usage", logger.Ctx{"volume": dev.Config["source"], "err": err})
				}

				continue
			}
		} else {
			continue
		}

		state := api.InstanceStateDisk{}
		if usage != nil {
			state.Usage = usage.Used
			state.Total = usage.Total
		}

		disk[dev.Name] = state
	}

	return disk
}

func (d *lxc) memoryState() api.InstanceStateMemory {
	memory := api.InstanceStateMemory{}

	cc, err := d.initLXC(false)
	if err != nil {
		return memory
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return memory
	}

	if !d.state.OS.CGInfo.Supports(cgroup.Memory, cg) {
		return memory
	}

	// Memory in bytes
	value, err := cg.GetMemoryUsage()
	if err == nil {
		memory.Usage = value
	}

	// Memory peak in bytes
	if d.state.OS.CGInfo.Supports(cgroup.MemoryMaxUsage, cg) {
		value, err = cg.GetMemoryMaxUsage()
		if err == nil {
			memory.UsagePeak = value
		}
	}

	// Memory total in bytes
	value, err = cg.GetEffectiveMemoryLimit()
	if err == nil {
		memory.Total = value
	}

	if d.state.OS.CGInfo.Supports(cgroup.MemorySwapUsage, cg) {
		// Swap in bytes
		if memory.Usage > 0 {
			value, err := cg.GetMemorySwapUsage()
			if err == nil {
				memory.SwapUsage = value
			}
		}

		// Swap peak in bytes
		if memory.UsagePeak > 0 {
			value, err = cg.GetMemorySwapMaxUsage()
			if err == nil {
				memory.SwapUsagePeak = value
			}
		}
	}

	return memory
}

func (d *lxc) networkState(hostInterfaces []net.Interface) map[string]api.InstanceStateNetwork {
	result := map[string]api.InstanceStateNetwork{}

	pid := d.InitPID()
	if pid < 1 {
		return result
	}

	couldUseNetnsGetifaddrs := d.state.OS.NetnsGetifaddrs
	if couldUseNetnsGetifaddrs {
		nw, err := netutils.NetnsGetifaddrs(int32(pid), hostInterfaces)
		if err != nil {
			couldUseNetnsGetifaddrs = false
			d.logger.Warn("Failed to retrieve network information via netlink", logger.Ctx{"pid": pid})
		} else {
			result = nw
		}
	}

	if !couldUseNetnsGetifaddrs {
		pidFd := d.inheritInitPidFd()
		pidFdNr := "-1"
		if pidFd != nil {
			defer func() { _ = pidFd.Close() }()
			pidFdNr = "3"
		}

		// Get the network state from the container
		out, _, err := subprocess.RunCommandSplit(
			context.TODO(),
			nil,
			[]*os.File{pidFd},
			d.state.OS.ExecPath,
			"forknet",
			"info",
			"--",
			fmt.Sprintf("%d", pid),
			pidFdNr)
		// Process forkgetnet response
		if err != nil {
			d.logger.Error("Error calling 'forknet", logger.Ctx{"err": err, "pid": pid})
			return result
		}

		// If we can use netns_getifaddrs() but it failed and the setns() +
		// netns_getifaddrs() succeeded we should just always fallback to the
		// setns() + netns_getifaddrs() style retrieval.
		d.state.OS.NetnsGetifaddrs = false

		nw := map[string]api.InstanceStateNetwork{}
		err = json.Unmarshal([]byte(out), &nw)
		if err != nil {
			d.logger.Error("Failure to read forknet json", logger.Ctx{"err": err})
			return result
		}

		result = nw
	}

	// Get host_name from volatile data if not set already.
	for name, dev := range result {
		if dev.HostName == "" {
			dev.HostName = d.localConfig[fmt.Sprintf("volatile.%s.host_name", name)]
			result[name] = dev
		}
	}

	return result
}

func (d *lxc) processesState(pid int) (int64, error) {
	// Return 0 if not running
	if pid == -1 {
		return 0, errors.New("PID of LXC instance could not be initialized")
	}

	cc, err := d.initLXC(false)
	if err != nil {
		return -1, err
	}

	cg, err := d.cgroup(cc, true)
	if err != nil {
		return 0, err
	}

	if d.state.OS.CGInfo.Supports(cgroup.Pids, cg) {
		value, err := cg.GetProcessesUsage()
		if err != nil {
			return -1, err
		}

		return value, nil
	}

	pids := []int64{int64(pid)}

	// Go through the pid list, adding new pids at the end so we go through them all
	for i := range pids {
		fname := fmt.Sprintf("/proc/%d/task/%d/children", pids[i], pids[i])
		fcont, err := os.ReadFile(fname)
		if err != nil {
			// the process terminated during execution of this loop
			continue
		}

		content := strings.Split(string(fcont), " ")
		for j := range content {
			pid, err := strconv.ParseInt(content[j], 10, 64)
			if err == nil {
				pids = append(pids, pid)
			}
		}
	}

	return int64(len(pids)), nil
}

// getStorageType returns the storage type of the instance's storage pool.
func (d *lxc) getStorageType() (string, error) {
	pool, err := d.getStoragePool()
	if err != nil {
		return "", err
	}

	return pool.Driver().Info().Name, nil
}

// mount the instance's rootfs volume if needed.
func (d *lxc) mount() (*storagePools.MountInfo, error) {
	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 rootfs volume if needed.
func (d *lxc) unmount() error {
	pool, err := d.getStoragePool()
	if err != nil {
		return err
	}

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

		return nil
	}

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

	return nil
}

// insertMountGo inserts a mount into a container.
// This function is used for the seccomp notifier and so cannot call any
// functions that would cause LXC to talk to the container's monitor. Otherwise
// we'll have a deadlock (with a timeout but still). The InitPID() call here is
// the exception since the seccomp notifier will make sure to always pass a
// valid PID.
func (d *lxc) insertMountGo(source, target, fstype string, flags int, mntnsPID int, idmapType idmap.StorageType) error {
	pid := mntnsPID
	if pid <= 0 {
		// Get the init PID
		pid = d.InitPID()
		if pid == -1 {
			// Container isn't running
			return errors.New("Can't insert mount into stopped container")
		}
	}

	// Create the temporary mount target
	var tmpMount string
	var err error
	if internalUtil.IsDir(source) {
		tmpMount, err = os.MkdirTemp(d.ShmountsPath(), "incus_mount_")
		if err != nil {
			return fmt.Errorf("Failed to create shmounts path: %s", err)
		}
	} else {
		f, err := os.CreateTemp(d.ShmountsPath(), "incus_mount_")
		if err != nil {
			return fmt.Errorf("Failed to create shmounts path: %s", err)
		}

		tmpMount = f.Name()
		_ = f.Close()
	}

	defer func() { _ = os.Remove(tmpMount) }()

	// Mount the filesystem
	err = unix.Mount(source, tmpMount, fstype, uintptr(flags), "")
	if err != nil {
		return fmt.Errorf("Failed to setup temporary mount: %s", err)
	}

	defer func() { _ = unix.Unmount(tmpMount, unix.MNT_DETACH) }()

	// Ensure that only flags modifying mount _properties_ make it through.
	// Strip things such as MS_BIND which would cause the creation of a
	// shifted mount to be skipped.
	// (Fyi, this is just one of the reasons why multiplexers are bad;
	// specifically when they do heinous things such as confusing flags
	// with commands.)

	// This is why multiplexers are bad
	shiftfsFlags := (flags & (unix.MS_RDONLY |
		unix.MS_NOSUID |
		unix.MS_NODEV |
		unix.MS_NOEXEC |
		unix.MS_DIRSYNC |
		unix.MS_NOATIME |
		unix.MS_NODIRATIME))

	// Move the mount inside the container
	mntsrc := filepath.Join("/dev/.incus-mounts", filepath.Base(tmpMount))
	pidStr := fmt.Sprintf("%d", pid)

	pidFdNr, pidFd := seccomp.MakePidFd(pid, d.state)
	if pidFdNr >= 0 {
		defer func() { _ = pidFd.Close() }()
	}

	if !strings.HasPrefix(target, "/") {
		target = "/" + target
	}

	_, err = subprocess.RunCommandInheritFds(
		context.Background(),
		[]*os.File{pidFd},
		d.state.OS.ExecPath,
		"forkmount",
		"go-mount",
		"--",
		pidStr,
		fmt.Sprintf("%d", pidFdNr),
		mntsrc,
		target,
		string(idmapType),
		fmt.Sprintf("%d", shiftfsFlags))
	if err != nil {
		return err
	}

	return nil
}

func (d *lxc) insertMountLXC(source, target, fstype string, flags int) error {
	cname := project.Instance(d.Project().Name, d.Name())
	configPath := filepath.Join(d.RunPath(), "lxc.conf")
	if fstype == "" {
		fstype = "none"
	}

	if !strings.HasPrefix(target, "/") {
		target = "/" + target
	}

	_, err := subprocess.RunCommand(
		d.state.OS.ExecPath,
		"forkmount",
		"lxc-mount",
		"--",
		cname,
		d.state.OS.LxcPath,
		configPath,
		source,
		target,
		fstype,
		fmt.Sprintf("%d", flags))
	if err != nil {
		return err
	}

	return nil
}

func (d *lxc) moveMount(source, target, fstype string, flags int, idmapType idmap.StorageType) error {
	// Get the init PID
	pid := d.InitPID()
	if pid == -1 {
		// Container isn't running
		return errors.New("Can't insert mount into stopped container")
	}

	switch idmapType {
	case idmap.StorageTypeIdmapped:
	case idmap.StorageTypeNone:
	default:
		return errors.New("Invalid idmap value specified")
	}

	pidFdNr, pidFd := seccomp.MakePidFd(pid, d.state)
	if pidFdNr >= 0 {
		defer func() { _ = pidFd.Close() }()
	}

	pidStr := fmt.Sprintf("%d", pid)

	if !strings.HasPrefix(target, "/") {
		target = "/" + target
	}

	_, err := subprocess.RunCommandInheritFds(
		context.Background(),
		[]*os.File{pidFd},
		d.state.OS.ExecPath,
		"forkmount",
		"move-mount",
		"--",
		pidStr,
		fmt.Sprintf("%d", pidFdNr),
		fstype,
		source,
		target,
		string(idmapType),
		fmt.Sprintf("%d", flags))
	if err != nil {
		return err
	}

	return nil
}

func (d *lxc) insertMount(source, target, fstype string, flags int, idmapType idmap.StorageType) error {
	if d.state.OS.IdmappedMounts && idmapType == idmap.StorageTypeIdmapped {
		return d.moveMount(source, target, fstype, flags, idmapType)
	}

	if d.state.OS.LXCFeatures["mount_injection_file"] && idmapType == idmap.StorageTypeNone {
		return d.insertMountLXC(source, target, fstype, flags)
	}

	return d.insertMountGo(source, target, fstype, flags, -1, idmapType)
}

func (d *lxc) removeMount(mount string) error {
	// Get the init PID
	pid := d.InitPID()
	if pid == -1 {
		// Container isn't running
		return errors.New("Can't remove mount from stopped container")
	}

	if d.state.OS.LXCFeatures["mount_injection_file"] {
		configPath := filepath.Join(d.RunPath(), "lxc.conf")
		cname := project.Instance(d.Project().Name, d.Name())

		if !strings.HasPrefix(mount, "/") {
			mount = "/" + mount
		}

		_, err := subprocess.RunCommand(
			d.state.OS.ExecPath,
			"forkmount",
			"lxc-umount",
			"--",
			cname,
			d.state.OS.LxcPath,
			configPath,
			mount)
		if err != nil {
			return err
		}
	} else {
		// Remove the mount from the container
		pidFd := d.inheritInitPidFd()
		pidFdNr := "-1"
		if pidFd != nil {
			defer func() { _ = pidFd.Close() }()
			pidFdNr = "3"
		}

		_, err := subprocess.RunCommandInheritFds(
			context.TODO(),
			[]*os.File{pidFd},
			d.state.OS.ExecPath,
			"forkmount",
			"go-umount",
			"--",
			fmt.Sprintf("%d", pid),
			pidFdNr,
			mount)
		if err != nil {
			return err
		}
	}

	return nil
}

// InsertSeccompUnixDevice inserts a seccomp device.
func (d *lxc) InsertSeccompUnixDevice(prefix string, m deviceConfig.Device, pid int) error {
	if pid < 0 {
		return errors.New("Invalid request PID specified")
	}

	rootLink := fmt.Sprintf("/proc/%d/root", pid)
	rootPath, err := os.Readlink(rootLink)
	if err != nil {
		return err
	}

	uid, gid, _, _, err := seccomp.TaskIDs(pid)
	if err != nil {
		return err
	}

	idmapset, err := d.CurrentIdmap()
	if err != nil {
		return err
	}

	nsuid, nsgid := idmapset.ShiftFromNS(uid, gid)
	m["uid"] = fmt.Sprintf("%d", nsuid)
	m["gid"] = fmt.Sprintf("%d", nsgid)

	if !path.IsAbs(m["path"]) {
		cwdLink := fmt.Sprintf("/proc/%d/cwd", pid)
		prefixPath, err := os.Readlink(cwdLink)
		if err != nil {
			return err
		}

		prefixPath = strings.TrimPrefix(prefixPath, rootPath)
		m["path"] = filepath.Join(rootPath, prefixPath, m["path"])
	} else {
		m["path"] = filepath.Join(rootPath, m["path"])
	}

	idmapSet, err := d.CurrentIdmap()
	if err != nil {
		return err
	}

	dev, err := device.UnixDeviceCreate(d.state, idmapSet, d.DevicesPath(), prefix, m, true)
	if err != nil {
		return fmt.Errorf("Failed to setup device: %s", err)
	}

	devPath := dev.HostPath
	tgtPath := dev.RelativePath

	// Bind-mount it into the container
	defer func() { _ = os.Remove(devPath) }()
	return d.insertMountGo(devPath, tgtPath, "none", unix.MS_BIND, pid, idmap.StorageTypeNone)
}

func (d *lxc) 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
	}

	// Go through all the unix devices
	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
}

// 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 *lxc) FillNetworkDevice(name string, m deviceConfig.Device) (deviceConfig.Device, error) {
	var err error
	newDevice := m.Clone()

	// Function to try and guess an available name
	nextInterfaceName := func() (string, error) {
		devNames := []string{}

		// Include all static interface names
		for _, dev := range d.expandedDevices.Sorted() {
			if dev.Config["name"] != "" && !slices.Contains(devNames, dev.Config["name"]) {
				devNames = append(devNames, dev.Config["name"])
			}
		}

		// Include all currently allocated interface names
		for k, v := range d.expandedConfig {
			if !strings.HasPrefix(k, internalInstance.ConfigVolatilePrefix) {
				continue
			}

			fields := strings.SplitN(k, ".", 3)
			if len(fields) != 3 {
				continue
			}

			if fields[2] != "name" || slices.Contains(devNames, v) {
				continue
			}

			devNames = append(devNames, v)
		}

		// Attempt to include all existing interfaces
		cname := project.Instance(d.Project().Name, d.Name())
		cc, err := liblxc.NewContainer(cname, d.state.OS.LxcPath)
		if err == nil {
			defer func() { _ = cc.Release() }()

			interfaces, err := cc.Interfaces()
			if err == nil {
				for _, name := range interfaces {
					if slices.Contains(devNames, name) {
						continue
					}

					devNames = append(devNames, name)
				}
			}
		}

		i := 0
		name := ""
		for {
			if m["type"] == "infiniband" {
				name = fmt.Sprintf("ib%d", i)
			} else {
				name = fmt.Sprintf("eth%d", i)
			}

			// Find a free device name
			if !slices.Contains(devNames, name) {
				return name, nil
			}

			i++
		}
	}

	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
	}

	// Fill in the interface name.
	if m["name"] == "" {
		configKey := fmt.Sprintf("volatile.%s.name", name)
		volatileName := d.localConfig[configKey]
		if volatileName == "" {
			// Generate a new interface name.
			volatileName, err = nextInterfaceName()
			if err != nil || volatileName == "" {
				return nil, fmt.Errorf("Failed generating %q: %w", configKey, err)
			}

			// Update the database and update volatileName with stored value.
			volatileName, err = d.insertConfigkey(configKey, volatileName)
			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] = volatileName
			d.expandedConfig[configKey] = volatileName
		}

		if volatileName == "" {
			return nil, fmt.Errorf("Failed getting %q", configKey)
		}

		newDevice["name"] = volatileName
	}

	return newDevice, nil
}

func (d *lxc) 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
	}

	// Go through all the unix devices
	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
}

// IsFrozen returns if instance is frozen.
func (d *lxc) IsFrozen() bool {
	return d.statusCode() == api.Frozen
}

// IsNesting returns if instance is nested.
func (d *lxc) IsNesting() bool {
	return util.IsTrue(d.expandedConfig["security.nesting"])
}

func (d *lxc) isCurrentlyPrivileged() bool {
	if !d.IsRunning() {
		return d.IsPrivileged()
	}

	idmap, err := d.CurrentIdmap()
	if err != nil {
		return d.IsPrivileged()
	}

	return idmap == nil
}

// IsPrivileged returns if instance is privileged.
func (d *lxc) IsPrivileged() bool {
	return util.IsTrue(d.expandedConfig["security.privileged"])
}

// IsRunning returns if instance is running.
func (d *lxc) IsRunning() bool {
	return d.isRunningStatusCode(d.statusCode())
}

// CanMigrate returns whether the instance can be migrated.
func (d *lxc) CanMigrate() string {
	return d.canMigrate(d)
}

// LockExclusive attempts to get exclusive access to the instance's root volume.
func (d *lxc) 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
	}

	// Stop forkfile as otherwise it will hold the root volume open preventing unmount.
	d.stopForkfile(false)

	return op, err
}

// InitPID returns PID of init process.
func (d *lxc) InitPID() int {
	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return -1
	}

	return cc.InitPid()
}

// InitPidFd returns pidfd of init process.
func (d *lxc) InitPidFd() (*os.File, error) {
	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return nil, err
	}

	return cc.InitPidFd()
}

// DevptsFd returns dirfd of devpts mount.
func (d *lxc) DevptsFd() (*os.File, error) {
	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return nil, err
	}

	defer d.release()

	if !liblxc.HasAPIExtension("devpts_fd") {
		return nil, errors.New("Missing devpts_fd extension")
	}

	return cc.DevptsFd()
}

// CurrentIdmap returns current IDMAP.
func (d *lxc) CurrentIdmap() (*idmap.Set, error) {
	jsonIdmap, ok := d.LocalConfig()["volatile.idmap.current"]
	if !ok {
		return d.DiskIdmap()
	}

	return idmap.NewSetFromJSON(jsonIdmap)
}

// DiskIdmap returns DISK IDMAP.
func (d *lxc) DiskIdmap() (*idmap.Set, error) {
	jsonIdmap, ok := d.LocalConfig()["volatile.last_state.idmap"]
	if !ok {
		return nil, nil
	}

	return idmap.NewSetFromJSON(jsonIdmap)
}

// NextIdmap returns next IDMAP.
func (d *lxc) NextIdmap() (*idmap.Set, error) {
	jsonIdmap, ok := d.LocalConfig()["volatile.idmap.next"]
	if !ok {
		return d.CurrentIdmap()
	}

	return idmap.NewSetFromJSON(jsonIdmap)
}

// statusCode returns instance status code.
func (d *lxc) statusCode() api.StatusCode {
	// Shortcut to avoid spamming liblxc 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
		}
	}

	state, err := d.getLxcState()
	if err != nil {
		return api.Error
	}

	statusCode := lxcStatusCode(state)

	if statusCode == api.Running && util.IsTrue(d.LocalConfig()["volatile.last_state.ready"]) {
		return api.Ready
	}

	return statusCode
}

// State returns instance state.
func (d *lxc) State() string {
	return strings.ToUpper(d.statusCode().String())
}

// LogFilePath log file path.
func (d *lxc) LogFilePath() string {
	return filepath.Join(d.LogPath(), "lxc.log")
}

func (d *lxc) CGroup() (*cgroup.CGroup, error) {
	// Load the go-lxc struct
	cc, err := d.initLXC(false)
	if err != nil {
		return nil, err
	}

	return d.cgroup(cc, true)
}

func (d *lxc) cgroup(cc *liblxc.Container, running bool) (*cgroup.CGroup, error) {
	if cc == nil {
		return nil, errors.New("Container not initialized for cgroup")
	}

	rw := lxcCgroupReadWriter{}
	rw.cc = cc
	rw.running = running

	cg, err := cgroup.New(&rw)
	if err != nil {
		return nil, err
	}

	cg.UnifiedCapable = liblxc.HasAPIExtension("cgroup2")
	return cg, nil
}

type lxcCgroupReadWriter struct {
	cc      *liblxc.Container
	running bool
}

func (rw *lxcCgroupReadWriter) Get(version cgroup.Backend, controller string, key string) (string, error) {
	if !rw.running {
		lxcKey := fmt.Sprintf("lxc.cgroup.%s", key)

		if version == cgroup.V2 {
			lxcKey = fmt.Sprintf("lxc.cgroup2.%s", key)
		}

		return strings.Join(rw.cc.ConfigItem(lxcKey), "\n"), nil
	}

	return strings.Join(rw.cc.CgroupItem(key), "\n"), nil
}

func (rw *lxcCgroupReadWriter) Set(version cgroup.Backend, controller string, key string, value string) error {
	if !rw.running {
		if version == cgroup.V1 {
			return lxcSetConfigItem(rw.cc, fmt.Sprintf("lxc.cgroup.%s", key), value)
		}

		return lxcSetConfigItem(rw.cc, fmt.Sprintf("lxc.cgroup2.%s", key), value)
	}

	return rw.cc.SetCgroupItem(key, value)
}

// UpdateBackupFile writes the instance's backup.yaml file to storage.
func (d *lxc) 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)
}

// Info returns "lxc" and the currently loaded version of LXC.
func (d *lxc) Info() instance.Info {
	return instance.Info{
		Name:    "lxc",
		Version: liblxc.Version(),
		Type:    instancetype.Container,
		Error:   nil,
	}
}

func (d *lxc) Metrics(hostInterfaces []net.Interface) (*metrics.MetricSet, error) {
	out := metrics.NewMetricSet(map[string]string{"project": d.project.Name, "name": d.name, "type": instancetype.Container.String()})

	if !d.IsRunning() {
		return nil, ErrInstanceIsStopped
	}

	cc, err := d.initLXC(false)
	if err != nil {
		return nil, err
	}

	// Load cgroup abstraction
	cg, err := d.cgroup(cc, true)
	if err != nil {
		return nil, err
	}

	// Get Memory limit.
	memoryLimit, err := cg.GetEffectiveMemoryLimit()
	if err != nil {
		d.logger.Warn("Failed getting effective memory limit", logger.Ctx{"err": err})
	}

	memoryCached := int64(0)

	// Get memory stats.
	memStats, err := cg.GetMemoryStats()
	if err != nil {
		d.logger.Warn("Failed to get memory stats", logger.Ctx{"err": err})
	} else {
		for k, v := range memStats {
			var metricType metrics.MetricType

			switch k {
			case "active_anon":
				metricType = metrics.MemoryActiveAnonBytes
			case "active_file":
				metricType = metrics.MemoryActiveFileBytes
			case "active":
				metricType = metrics.MemoryActiveBytes
			case "inactive_anon":
				metricType = metrics.MemoryInactiveAnonBytes
			case "inactive_file":
				metricType = metrics.MemoryInactiveFileBytes
			case "inactive":
				metricType = metrics.MemoryInactiveBytes
			case "unevictable":
				metricType = metrics.MemoryUnevictableBytes
			case "writeback":
				metricType = metrics.MemoryWritebackBytes
			case "dirty":
				metricType = metrics.MemoryDirtyBytes
			case "mapped":
				metricType = metrics.MemoryMappedBytes
			case "rss":
				metricType = metrics.MemoryRSSBytes
			case "shmem":
				metricType = metrics.MemoryShmemBytes
			case "cache":
				metricType = metrics.MemoryCachedBytes
				memoryCached = int64(v)
			}

			out.AddSamples(metricType, metrics.Sample{Value: float64(v)})
		}
	}

	// Get memory usage.
	memoryUsage, err := cg.GetMemoryUsage()
	if err != nil {
		d.logger.Warn("Failed to get memory usage", logger.Ctx{"err": err})
	}

	if memoryLimit > 0 {
		out.AddSamples(metrics.MemoryMemTotalBytes, metrics.Sample{Value: float64(memoryLimit)})
		out.AddSamples(metrics.MemoryMemAvailableBytes, metrics.Sample{Value: float64(memoryLimit - memoryUsage + memoryCached)})
		out.AddSamples(metrics.MemoryMemFreeBytes, metrics.Sample{Value: float64(memoryLimit - memoryUsage)})
	}

	// Get oom kills.
	oomKills, err := cg.GetOOMKills()
	if err != nil {
		d.logger.Warn("Failed to get oom kills", logger.Ctx{"err": err})
	}

	out.AddSamples(metrics.MemoryOOMKillsTotal, metrics.Sample{Value: float64(oomKills)})

	// Handle swap.
	if d.state.OS.CGInfo.Supports(cgroup.MemorySwapUsage, cg) {
		swapUsage, err := cg.GetMemorySwapUsage()
		if err != nil {
			d.logger.Warn("Failed to get swap usage", logger.Ctx{"err": err})
		} else {
			out.AddSamples(metrics.MemorySwapBytes, metrics.Sample{Value: float64(swapUsage)})
		}
	}

	// Get CPU stats
	usage, err := cg.GetCPUAcctUsageAll()
	if err != nil {
		d.logger.Warn("Failed to get CPU usage", logger.Ctx{"err": err})
	} else {
		for cpu, stats := range usage {
			cpuID := strconv.Itoa(int(cpu))

			out.AddSamples(metrics.CPUSecondsTotal, metrics.Sample{Value: float64(stats.System) / 1000000000, Labels: map[string]string{"mode": "system", "cpu": cpuID}})
			out.AddSamples(metrics.CPUSecondsTotal, metrics.Sample{Value: float64(stats.User) / 1000000000, Labels: map[string]string{"mode": "user", "cpu": cpuID}})
		}
	}

	// Get CPUs.
	CPUs, err := cg.GetEffectiveCPUs()
	if err != nil {
		d.logger.Warn("Failed to get CPUs", logger.Ctx{"err": err})
	} else {
		out.AddSamples(metrics.CPUs, metrics.Sample{Value: float64(CPUs)})
	}

	// Get disk stats
	diskStats, err := cg.GetIOStats()
	if err != nil {
		d.logger.Warn("Failed to get disk stats", logger.Ctx{"err": err})
	} else {
		for disk, stats := range diskStats {
			labels := map[string]string{"device": disk}

			out.AddSamples(metrics.DiskReadBytesTotal, metrics.Sample{Value: float64(stats.ReadBytes), Labels: labels})
			out.AddSamples(metrics.DiskReadsCompletedTotal, metrics.Sample{Value: float64(stats.ReadsCompleted), Labels: labels})
			out.AddSamples(metrics.DiskWrittenBytesTotal, metrics.Sample{Value: float64(stats.WrittenBytes), Labels: labels})
			out.AddSamples(metrics.DiskWritesCompletedTotal, metrics.Sample{Value: float64(stats.WritesCompleted), Labels: labels})
		}
	}

	// Get filesystem stats
	fsStats, err := d.getFSStats()
	if err != nil {
		d.logger.Warn("Failed to get fs stats", logger.Ctx{"err": err})
	} else {
		out.Merge(fsStats)
	}

	// Get network stats
	networkState := d.networkState(hostInterfaces)

	for name, state := range networkState {
		labels := map[string]string{"device": name}

		out.AddSamples(metrics.NetworkReceiveBytesTotal, metrics.Sample{Value: float64(state.Counters.BytesReceived), Labels: labels})
		out.AddSamples(metrics.NetworkReceivePacketsTotal, metrics.Sample{Value: float64(state.Counters.PacketsReceived), Labels: labels})
		out.AddSamples(metrics.NetworkTransmitBytesTotal, metrics.Sample{Value: float64(state.Counters.BytesSent), Labels: labels})
		out.AddSamples(metrics.NetworkTransmitPacketsTotal, metrics.Sample{Value: float64(state.Counters.PacketsSent), Labels: labels})
		out.AddSamples(metrics.NetworkReceiveErrsTotal, metrics.Sample{Value: float64(state.Counters.ErrorsReceived), Labels: labels})
		out.AddSamples(metrics.NetworkTransmitErrsTotal, metrics.Sample{Value: float64(state.Counters.ErrorsSent), Labels: labels})
		out.AddSamples(metrics.NetworkReceiveDropTotal, metrics.Sample{Value: float64(state.Counters.PacketsDroppedInbound), Labels: labels})
		out.AddSamples(metrics.NetworkTransmitDropTotal, metrics.Sample{Value: float64(state.Counters.PacketsDroppedOutbound), Labels: labels})
	}

	// Get number of processes
	pids, err := d.processesState(d.InitPID())
	if err != nil {
		d.logger.Warn("Failed to get total number of processes", logger.Ctx{"err": err})
	} else {
		out.AddSamples(metrics.ProcsTotal, metrics.Sample{Value: float64(pids)})
	}

	// Set the timestamps
	startedAt, err := d.processStartedAt(d.InitPID())
	if err != nil {
		d.logger.Warn("Failed to get instance startup time", logger.Ctx{"err": err})
	} else {
		out.AddSamples(metrics.BootTimeSeconds, metrics.Sample{Value: float64(startedAt.Unix())})
	}

	out.AddSamples(metrics.TimeSeconds, metrics.Sample{Value: float64(time.Now().Unix())})

	return out, nil
}

func (d *lxc) getFSStats() (*metrics.MetricSet, error) {
	type mountInfo struct {
		Mountpoint string
		FSType     string
	}

	out := metrics.NewMetricSet(nil)

	mounts, err := os.ReadFile("/proc/mounts")
	if err != nil {
		return nil, fmt.Errorf("Failed to read /proc/mounts: %w", err)
	}

	mountMap := make(map[string]mountInfo)
	scanner := bufio.NewScanner(bytes.NewReader(mounts))

	for scanner.Scan() {
		fields := strings.Fields(scanner.Text())

		mountMap[fields[0]] = mountInfo{Mountpoint: fields[1], FSType: fields[2]}
	}

	// Get disk devices
	for _, dev := range d.expandedDevices {
		if dev["type"] != "disk" || dev["path"] == "" {
			continue
		}

		var statfs *unix.Statfs_t
		labels := make(map[string]string)
		realDev := ""

		if dev["pool"] != "" {
			// Expected volume name.
			var volName string
			var volType storageDrivers.VolumeType
			if dev["source"] != "" {
				volName = project.StorageVolume(d.project.Name, dev["source"])
				volType = storageDrivers.VolumeTypeCustom
			} else {
				volName = project.Instance(d.project.Name, d.name)
				volType = storageDrivers.VolumeTypeContainer
			}

			// Check that we have a mountpoint.
			mountpoint := storageDrivers.GetVolumeMountPath(dev["pool"], volType, volName)
			if mountpoint == "" || !util.PathExists(mountpoint) {
				continue
			}

			// Grab the filesystem information.
			statfs, err = linux.StatVFS(mountpoint)
			if err != nil {
				return nil, fmt.Errorf("Failed to stat %s: %w", mountpoint, err)
			}

			// Grab the pool information to compare.
			poolStatfs, err := linux.StatVFS(internalUtil.VarPath("storage-pools", dev["pool"]))
			if err != nil {
				return nil, fmt.Errorf("Failed to stat %s: %w", mountpoint, err)
			}

			// Check if we have actual mount-specific information.
			if statfs.Type == poolStatfs.Type && statfs.Blocks == poolStatfs.Blocks && statfs.Bfree == poolStatfs.Bfree && statfs.Bavail == poolStatfs.Bavail {
				continue
			}

			// Check if mountPath is in mountMap
			isMounted := false

			for mountDev, mountInfo := range mountMap {
				if mountInfo.Mountpoint != mountpoint {
					continue
				}

				isMounted = true
				realDev = mountDev
				break
			}

			if !isMounted {
				realDev = dev["source"]
			}
		} else {
			// Skip special disks.
			if device.IsSpecialDisk(dev["source"]) {
				continue
			}

			source := dev["source"]

			statfs, err = linux.StatVFS(source)
			if err != nil {
				return nil, fmt.Errorf("Failed to stat %s: %w", dev["source"], err)
			}

			isMounted := false

			// Check if mountPath is in mountMap
			for mountDev, mountInfo := range mountMap {
				if mountInfo.Mountpoint != source {
					continue
				}

				isMounted = true
				stat := unix.Stat_t{}

				// Check if dev has a backing file
				err = unix.Stat(source, &stat)
				if err != nil {
					return nil, fmt.Errorf("Failed to stat %s: %w", dev["source"], err)
				}

				backingFilePath := fmt.Sprintf("/sys/dev/block/%d:%d/loop/backing_file", unix.Major(uint64(stat.Dev)), unix.Minor(uint64(stat.Dev)))

				if util.PathExists(backingFilePath) {
					// Read backing file
					backingFile, err := os.ReadFile(backingFilePath)
					if err != nil {
						return nil, fmt.Errorf("Failed to read %s: %w", backingFilePath, err)
					}

					realDev = string(backingFile)
				} else {
					// Use dev as device
					realDev = mountDev
				}

				break
			}

			if !isMounted {
				realDev = dev["source"]
			}
		}

		// Add labels
		labels["device"] = realDev
		labels["mountpoint"] = dev["path"]

		fsType, err := linux.FSTypeToName(int32(statfs.Type))
		if err == nil {
			labels["fstype"] = fsType
		}

		// Add sample
		statfsBsize := uint64(statfs.Bsize)
		out.AddSamples(metrics.FilesystemSizeBytes, metrics.Sample{Value: float64(statfs.Blocks * statfsBsize), Labels: labels})
		out.AddSamples(metrics.FilesystemAvailBytes, metrics.Sample{Value: float64(statfs.Bavail * statfsBsize), Labels: labels})
		out.AddSamples(metrics.FilesystemFreeBytes, metrics.Sample{Value: float64(statfs.Bfree * statfsBsize), Labels: labels})
	}

	return out, nil
}

func (d *lxc) loadRawLXCConfig(cc *liblxc.Container) error {
	// Load the LXC raw config.
	lxcConfig, ok := d.expandedConfig["raw.lxc"]
	if !ok {
		return nil
	}

	// Write to temp config file.
	f, err := os.CreateTemp("", "incus_config_")
	if err != nil {
		return err
	}

	err = internalIO.WriteAll(f, []byte(lxcConfig))
	if err != nil {
		return err
	}

	err = f.Close()
	if err != nil {
		return err
	}

	// Load the config.
	err = cc.LoadConfigFile(f.Name())
	if err != nil {
		return fmt.Errorf("Failed to load config file %q: %w", f.Name(), err)
	}

	_ = os.Remove(f.Name())

	return nil
}

// forfileRunningLockName returns the forkfile-running_ID lock name.
func (d *common) forkfileRunningLockName() string {
	return fmt.Sprintf("forkfile-running_%d", d.id)
}

// ReloadDevice triggers an empty Update call to the underlying device.
func (d *lxc) 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)
}

// CanLiveMigrate returns whether the container is live-migratable.
func (d *lxc) CanLiveMigrate() bool {
	return util.IsTrue(d.expandedConfig["migration.stateful"])
}

// setupCredentials sets up the systemd credentials directory.
func (d *lxc) setupCredentials(update bool) error {
	// Skip updating if the container isn't running.
	if update && !d.IsRunning() {
		return nil
	}

	credentialsDir := filepath.Join(d.Path(), "credentials")
	credentials := map[string][]byte{}
	oldCredentials := map[string]bool{}

	var rootUID, rootGID int64
	idmapset, err := d.NextIdmap()
	if err != nil {
		return err
	}

	if idmapset != nil {
		rootUID, rootGID = idmapset.ShiftIntoNS(0, 0)
	}

	for k, v := range d.expandedConfig {
		after, ok := strings.CutPrefix(k, "systemd.credential.")
		if ok {
			credentials[after] = []byte(v)
			continue
		}

		after, ok = strings.CutPrefix(k, "systemd.credential-binary.")
		if ok {
			data, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(v, "="))
			if err != nil {
				return fmt.Errorf("Invalid base64 value for %q: %q", k, v)
			}

			credentials[after] = data
		}
	}

	// Cleanup the credentials directory.
	if update && util.PathExists(credentialsDir) {
		credEntries, err := os.ReadDir(credentialsDir)
		if err != nil {
			return fmt.Errorf("Failed to list credentials directory: %w", err)
		}

		for _, entry := range credEntries {
			oldCredentials[entry.Name()] = true
		}
	} else {
		_ = os.RemoveAll(credentialsDir)

		err = internalUtil.MkdirAllOwner(credentialsDir, 0o100, int(rootUID), int(rootGID))
		if err != nil {
			return fmt.Errorf("Failed to create credentials directory: %w", err)
		}
	}

	credsRoot, err := os.OpenRoot(credentialsDir)
	if err != nil {
		return fmt.Errorf("Failed to open the credentials directory: %w", err)
	}

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

	for k, v := range credentials {
		err := credsRoot.WriteFile(k, v, 0o400)
		if err != nil {
			return fmt.Errorf("Failed to write credential %q: %w", k, err)
		}

		err = credsRoot.Chown(k, int(rootUID), int(rootGID))
		if err != nil {
			return fmt.Errorf("Failed setting permissions for file %q: %w", k, err)
		}

		delete(oldCredentials, k)
	}

	for oldCredential := range oldCredentials {
		err = os.Remove(filepath.Join(credentialsDir, oldCredential))
		if err != nil {
			return fmt.Errorf("Failed to remove credential %q: %w", oldCredential, err)
		}
	}

	return nil
}

// GuestOS returns the guest OS. For containers, we can safely assume Linux.
func (d *lxc) GuestOS() string {
	return "linux"
}

// CreateQcow2Snapshot creates a qcow2 snapshot for a running instance. Not supported by containers.
func (d *lxc) CreateQcow2Snapshot(devPath string, devName string, snapName string, backingFilename string) error {
	return nil
}

// DeleteQcow2Snapshot deletes a qcow2 snapshot for a running instance. Not supported by containers.
func (d *lxc) DeleteQcow2Snapshot(devName string, snapshotIndex int, backingFilename string) error {
	return nil
}

// ExportQcow2Block exports a qcow2 block device. Not supported by containers.
func (d *lxc) ExportQcow2Block(diskIndex int) (func(), string, error) {
	return nil, "", nil
}

// setNICLink sets the link status of the given device.
func (d *lxc) setNICLink(devName string, connected bool, assumeUp bool) error {
	// This check is added so that devices that cannot handle link states do not fail to initialize.
	if connected && assumeUp {
		return nil
	}

	link, err := ip.LinkByName(d.localConfig["volatile."+devName+".host_name"])
	if err != nil {
		return fmt.Errorf("Failed to find interface %s: %w", devName, err)
	}

	if connected {
		err = link.SetUp()
		if err != nil {
			return fmt.Errorf("Failed to bring %s up: %w", devName, err)
		}
	} else {
		err = link.SetDown()
		if err != nil {
			return fmt.Errorf("Failed to bring %s down: %w", devName, err)
		}
	}

	return nil
}
