//go:build !remote

package libpod

import (
	"fmt"
	"path"
	"path/filepath"
	"strings"

	"github.com/containers/common/pkg/cgroups"
	"github.com/containers/common/pkg/config"
	"github.com/containers/podman/v5/libpod/define"
	"github.com/containers/podman/v5/pkg/rootless"
	spec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
)

func (r *Runtime) platformMakePod(pod *Pod, resourceLimits *spec.LinuxResources) (string, error) {
	cgroupParent := ""
	// Check Cgroup parent sanity, and set it if it was not set
	if r.config.Cgroups() != "disabled" {
		switch r.config.Engine.CgroupManager {
		case config.CgroupfsCgroupsManager:
			canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent)
			if canUseCgroup {
				// need to actually create parent here
				if pod.config.CgroupParent == "" {
					pod.config.CgroupParent = CgroupfsDefaultCgroupParent
				} else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") {
					return "", fmt.Errorf("systemd slice received as cgroup parent when using cgroupfs: %w", define.ErrInvalidArg)
				}
				// If we are set to use pod cgroups, set the cgroup parent that
				// all containers in the pod will share
				if pod.config.UsePodCgroup {
					pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID())
					cgroupParent = pod.state.CgroupPath
					// cgroupfs + rootless = permission denied when creating the cgroup.
					if !rootless.IsRootless() {
						res, err := GetLimits(resourceLimits)
						if err != nil {
							return "", err
						}
						res.SkipDevices = true
						// Need to both create and update the cgroup
						// rather than create a new path in c/common for pod cgroup creation
						// just create as if it is a ctr and then update figures out that we need to
						// populate the resource limits on the pod level
						cgc, err := cgroups.New(pod.state.CgroupPath, &res)
						if err != nil {
							return "", err
						}
						err = cgc.Update(&res)
						if err != nil {
							return "", err
						}
					}
				}
			}
		case config.SystemdCgroupsManager:
			if pod.config.CgroupParent == "" {
				if rootless.IsRootless() {
					pod.config.CgroupParent = SystemdDefaultRootlessCgroupParent
				} else {
					pod.config.CgroupParent = SystemdDefaultCgroupParent
				}
			} else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") {
				return "", fmt.Errorf("did not receive systemd slice as cgroup parent when using systemd to manage cgroups: %w", define.ErrInvalidArg)
			}
			// If we are set to use pod cgroups, set the cgroup parent that
			// all containers in the pod will share
			if pod.config.UsePodCgroup {
				cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()), resourceLimits)
				if err != nil {
					return "", fmt.Errorf("unable to create pod cgroup for pod %s: %w", pod.ID(), err)
				}
				pod.state.CgroupPath = cgroupPath
				cgroupParent = pod.state.CgroupPath
			}
		default:
			return "", fmt.Errorf("unsupported Cgroup manager: %s - cannot validate cgroup parent: %w", r.config.Engine.CgroupManager, define.ErrInvalidArg)
		}
	}

	if pod.config.UsePodCgroup {
		logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath)
	}

	return cgroupParent, nil
}

func (p *Pod) removePodCgroup() error {
	// Remove pod cgroup, if present
	if p.state.CgroupPath == "" {
		return nil
	}
	logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath)

	cgroup, err := cgroups.GetOwnCgroup()
	if err != nil {
		return err
	}

	// if we are trying to delete a cgroup that is our ancestor, we need to move the
	// current process out of it before the cgroup is destroyed.
	if isSubDir(cgroup, string(filepath.Separator)+p.state.CgroupPath) {
		parent := path.Dir(p.state.CgroupPath)
		if err := cgroups.MoveUnderCgroup(parent, "cleanup", nil); err != nil {
			return err
		}
	}

	switch p.runtime.config.Engine.CgroupManager {
	case config.SystemdCgroupsManager:
		if err := deleteSystemdCgroup(p.state.CgroupPath, p.ResourceLim()); err != nil {
			return fmt.Errorf("removing pod %s cgroup: %w", p.ID(), err)
		}
	case config.CgroupfsCgroupsManager:
		// Delete the cgroupfs cgroup
		// Make sure the conmon cgroup is deleted first
		// Since the pod is almost gone, don't bother failing
		// hard - instead, just log errors.
		conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon")
		conmonCgroup, err := cgroups.Load(conmonCgroupPath)
		if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless {
			return fmt.Errorf("retrieving pod %s conmon cgroup: %w", p.ID(), err)
		}
		if err == nil {
			if err = conmonCgroup.Delete(); err != nil {
				return fmt.Errorf("removing pod %s conmon cgroup: %w", p.ID(), err)
			}
		}
		cgroup, err := cgroups.Load(p.state.CgroupPath)
		if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless {
			return fmt.Errorf("retrieving pod %s cgroup: %w", p.ID(), err)
		}
		if err == nil {
			if err := cgroup.Delete(); err != nil {
				return fmt.Errorf("removing pod %s cgroup: %w", p.ID(), err)
			}
		}
	default:
		// This should be caught much earlier, but let's still
		// keep going so we make sure to evict the pod before
		// ending up with an inconsistent state.
		return fmt.Errorf("unrecognized cgroup manager %s when removing pod %s cgroups: %w", p.runtime.config.Engine.CgroupManager, p.ID(), define.ErrInternal)
	}
	return nil
}
