// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgroup

import (
	
)

// Include explicit NUL to be sure we include it in the slice.
const (
	v2MaxFile    = "/cpu.max\x00"
	v1QuotaFile  = "/cpu.cfs_quota_us\x00"
	v1PeriodFile = "/cpu.cfs_period_us\x00"
)

// CPU owns the FDs required to read the CPU limit from a cgroup.
type CPU struct {
	version Version

	// For cgroup v1, this is cpu.cfs_quota_us.
	// For cgroup v2, this is cpu.max.
	quotaFD int

	// For cgroup v1, this is cpu.cfs_period_us.
	// For cgroup v2, this is unused.
	periodFD int
}

func ( CPU) () {
	switch .version {
	case V1:
		linux.Close(.quotaFD)
		linux.Close(.periodFD)
	case V2:
		linux.Close(.quotaFD)
	default:
		throw("impossible cgroup version")
	}
}

func checkBufferSize( []byte,  int) {
	if len() !=  {
		println("runtime: cgroup buffer length", len(), "want", )
		throw("runtime: cgroup invalid buffer length")
	}
}

// OpenCPU returns a CPU for the CPU cgroup containing the current process, or
// ErrNoCgroup if the process is not in a CPU cgroup.
//
// scratch must have length ScratchSize.
func ( []byte) (CPU, error) {
	checkBufferSize(, ScratchSize)

	 := [:PathSize]
	 := [PathSize:]

	, ,  := FindCPU(, )
	if  != nil {
		return CPU{}, 
	}

	switch  {
	case 1:
		 := copy([:], v1QuotaFile)
		 := [:+]
		,  := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
		if  != 0 {
			// This may fail if this process was migrated out of
			// the cgroup found by FindCPU and that cgroup has been
			// deleted.
			return CPU{}, errSyscallFailed
		}

		 = copy([:], v1PeriodFile)
		 = [:+]
		,  := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
		if  != 0 {
			// This may fail if this process was migrated out of
			// the cgroup found by FindCPU and that cgroup has been
			// deleted.
			return CPU{}, errSyscallFailed
		}

		 := CPU{
			version:  1,
			quotaFD:  ,
			periodFD: ,
		}
		return , nil
	case 2:
		 := copy([:], v2MaxFile)
		 := [:+]
		,  := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
		if  != 0 {
			// This may fail if this process was migrated out of
			// the cgroup found by FindCPU and that cgroup has been
			// deleted.
			return CPU{}, errSyscallFailed
		}

		 := CPU{
			version:  2,
			quotaFD:  ,
			periodFD: -1,
		}
		return , nil
	default:
		throw("impossible cgroup version")
		panic("unreachable")
	}
}

// Returns average CPU throughput limit from the cgroup, or ok false if there
// is no limit.
func ( CPU) (float64, bool, error) {
	switch .version {
	case 1:
		,  := readV1Number(.quotaFD)
		if  != nil {
			return 0, false, errMalformedFile
		}

		if  < 0 {
			// No limit.
			return 0, false, nil
		}

		,  := readV1Number(.periodFD)
		if  != nil {
			return 0, false, errMalformedFile
		}

		return float64() / float64(), true, nil
	case 2:
		// quotaFD is the cpu.max FD.
		return readV2Limit(.quotaFD)
	default:
		throw("impossible cgroup version")
		panic("unreachable")
	}
}

// Returns the value from the quota/period file.
func readV1Number( int) (int64, error) {
	// The format of the file is "<value>\n" where the value is in
	// int64 microseconds and, if quota, may be -1 to indicate no limit.
	//
	// MaxInt64 requires 19 bytes to display in base 10, thus the
	// conservative max size of this file is 19 + 1 (newline) = 20 bytes.
	// We'll provide a bit more for good measure.
	//
	// Always read from the beginning of the file to get a fresh value.
	var  [64]byte
	,  := linux.Pread(, [:], 0)
	if  != 0 {
		return 0, errSyscallFailed
	}
	if  == len() {
		return 0, errMalformedFile
	}

	 := [:]
	return parseV1Number()
}

// Returns CPU throughput limit, or ok false if there is no limit.
func readV2Limit( int) (float64, bool, error) {
	// The format of the file is "<quota> <period>\n" where quota and
	// period are microseconds and quota may be "max" to indicate no limit.
	//
	// Note that the kernel is inconsistent about whether the values are
	// uint64 or int64: values are parsed as uint64 but printed as int64.
	// See kernel/sched/core.c:cpu_max_{show,write}.
	//
	// In practice, the kernel limits the period to 1s (1000000us) (see
	// max_cfs_quota_period), and the quota to (1<<44)us (see
	// max_cfs_runtime), so these values can't get large enough for the
	// distinction to matter.
	//
	// MaxInt64 requires 19 bytes to display in base 10, thus the
	// conservative max size of this file is 19 + 19 + 1 (space) + 1
	// (newline) = 40 bytes. We'll provide a bit more for good measure.
	//
	// Always read from the beginning of the file to get a fresh value.
	var  [64]byte
	,  := linux.Pread(, [:], 0)
	if  != 0 {
		return 0, false, errSyscallFailed
	}
	if  == len() {
		return 0, false, errMalformedFile
	}

	 := [:]
	return parseV2Limit()
}

// FindCPU finds the path to the CPU cgroup that this process is a member of
// and places it in out. scratch is a scratch buffer for internal use.
//
// out must have length PathSize. scratch must have length ParseSize.
//
// Returns the number of bytes written to out and the cgroup version (1 or 2).
//
// Returns ErrNoCgroup if the process is not in a CPU cgroup.
func ( []byte,  []byte) (int, Version, error) {
	checkBufferSize(, PathSize)
	checkBufferSize(, ParseSize)

	// The cgroup path is <cgroup mount point> + <relative path>.
	// relative path is the cgroup relative to the mount root.

	, ,  := FindCPUCgroup(, )
	if  != nil {
		return 0, 0, 
	}

	,  = FindCPUMountPoint(, [:], , )
	return , , 
}

// FindCPUCgroup finds the path to the CPU cgroup that this process is a member of
// and places it in out. scratch is a scratch buffer for internal use.
//
// out must have length PathSize. scratch must have length ParseSize.
//
// Returns the number of bytes written to out and the cgroup version (1 or 2).
//
// Returns ErrNoCgroup if the process is not in a CPU cgroup.
func ( []byte,  []byte) (int, Version, error) {
	 := []byte("/proc/self/cgroup\x00")
	,  := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
	if  == linux.ENOENT {
		return 0, 0, ErrNoCgroup
	} else if  != 0 {
		return 0, 0, errSyscallFailed
	}

	// The relative path always starts with /, so we can directly append it
	// to the mount point.
	, ,  := parseCPUCgroup(, linux.Read, [:], )
	if  != nil {
		linux.Close()
		return 0, 0, 
	}

	linux.Close()
	return , , nil
}

// FindCPUMountPoint finds the mount point containing the specified cgroup and
// version with cpu controller, and compose the full path to the cgroup in out.
// scratch is a scratch buffer for internal use.
//
// out must have length PathSize, may overlap with cgroup.
// scratch must have length ParseSize.
//
// Returns the number of bytes written to out.
//
// Returns ErrNoCgroup if no matching mount point is found.
func (,  []byte,  Version,  []byte) (int, error) {
	checkBufferSize(, PathSize)
	checkBufferSize(, ParseSize)

	 := []byte("/proc/self/mountinfo\x00")
	,  := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
	if  == linux.ENOENT {
		return 0, ErrNoCgroup
	} else if  != 0 {
		return 0, errSyscallFailed
	}

	,  := parseCPUMount(, linux.Read, , , , )
	if  != nil {
		linux.Close()
		return 0, 
	}
	linux.Close()

	return , nil
}