// Copyright IBM Corp. 2015, 2025
// SPDX-License-Identifier: BUSL-1.1

//go:build linux

package cgroupslib

import (
	"bufio"
	"io"
	"os"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/hashicorp/go-set/v3"
)

// detect tries to detect which cgroups version we have by looking at the mount
// and whether Nomad owns the cgroup.
// - For cgroups v1 this requires root.
// - For cgroups v2 we look for root or whether we're the owner of the slice.
// - All other cases, including any file permission errors, return OFF.
func detect() Mode {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return OFF
	}
	defer func() {
		_ = f.Close()
	}()

	mode := scan(f)

	if mode == CG1 && os.Geteuid() > 0 {
		return OFF
	}

	if mode == CG2 {
		if !functionalCgroups2("cgroup.controllers") {
			return OFF
		}
		uid := os.Geteuid()
		if uid > 0 {
			// allow for cgroup delegation if we own the slice
			cgPath := filepathCG("nomad.slice")
			fi, err := os.Stat(cgPath)
			if err != nil {
				return OFF
			}
			if uid != int(fi.Sys().(*syscall.Stat_t).Uid) {
				return OFF
			}
		}
	}

	return mode
}

func functionalCgroups2(controllersFile string) bool {
	requiredCgroup2Controllers := []string{"cpuset", "cpu", "io", "memory", "pids"}

	controllersRootPath := filepath.Join(root, controllersFile)
	content, err := os.ReadFile(controllersRootPath)
	if err != nil {
		return false
	}

	rootSubtreeControllers := set.From[string](strings.Fields(string(content)))
	return rootSubtreeControllers.ContainsSlice(requiredCgroup2Controllers)
}

func scan(in io.Reader) Mode {
	scanner := bufio.NewScanner(in)
	for scanner.Scan() {
		tokens := set.From(strings.Fields(scanner.Text()))
		if tokens.Contains("/sys/fs/cgroup") {
			if tokens.Contains("tmpfs") {
				return CG1
			}
			if tokens.Contains("cgroup2") {
				return CG2
			}
		}
	}
	return OFF
}
