// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

package cgroups

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

// SelfCgroupIdentifier is the identifier to be used to get self cgroup
const (
	selfSysPath          = "/sys"
	SelfCgroupIdentifier = "self"
)

type selfReaderFilter struct {
	readerFilter ReaderFilter
	procPath     string
}

func (f *selfReaderFilter) init(inContainer bool, baseController string) error {
	// If we run in a container, /sys/fs/cgroup directly contains the values for our own container
	if inContainer {
		f.readerFilter = func(_, _ string) (string, error) {
			return SelfCgroupIdentifier, filepath.SkipDir
		}

		return nil
	}

	// If we don't run in a container, we expect to be in host cgroup namespace, otherwise this will not work
	// as the path retrieved from `/proc/self/cgroup` may not be the expected one
	relativePath, err := IdentiferFromCgroupReferences(f.procPath, SelfCgroupIdentifier, baseController, func(path, _ string) (string, error) {
		return path, nil
	})
	if err != nil {
		return fmt.Errorf("unable to get self relative cgroup path, err: %w", err)
	}

	f.readerFilter = func(path, _ string) (string, error) {
		if strings.HasSuffix(path, relativePath) {
			return SelfCgroupIdentifier, filepath.SkipDir
		}

		return "", nil
	}
	return nil
}

func (f *selfReaderFilter) filter(path, name string) (string, error) {
	return f.readerFilter(path, name)
}

// NewSelfReader allows to get current process cgroup stats
// selfProcPath should always be `/proc`, taken as parameter to allow unit testing
func NewSelfReader(selfProcPath string, inContainer bool, opts ...ReaderOption) (*Reader, error) {
	selfFilter := selfReaderFilter{
		procPath: selfProcPath,
	}

	// The self requires to always read `/proc` and `/sys`, even if `/host/sys` is present, for instance.
	// We use HostPrefix = `/sys` to filter out any other mount path.
	// If we're on host with cgroup not mounted at `/sys`, it will not work.
	opts = append(opts, WithReaderFilter(selfFilter.filter), WithProcPath(selfProcPath), WithHostPrefix(selfSysPath))
	selfReader, err := NewReader(opts...)
	if err != nil {
		return nil, err
	}

	// cgroupV2 uses a single, unified, path, no baseController is required
	baseController := selfReader.cgroupV1BaseController
	if selfReader.CgroupVersion() == 2 {
		baseController = ""
	}

	err = selfFilter.init(inContainer, baseController)
	if err != nil {
		return nil, err
	}

	err = selfReader.RefreshCgroups(0)
	if err != nil {
		return nil, err
	}

	return selfReader, nil
}
