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

package docker

import (
	"os"
	"path/filepath"
	"time"

	"github.com/hashicorp/nomad/client/lib/cgroupslib"
	"github.com/hashicorp/nomad/helper"
)

const (
	// cpusetSyncPeriod is how often we check to see if the cpuset of a task
	// needs to be updated - if there is no work to do, no action is taken
	cpusetSyncPeriod = 3 * time.Second
)

// cpuset is used to manage the cpuset.cpus interface file in the cgroup that
// docker daemon creates for the container being run by the task driver. we
// must do this hack because docker does not allow specifying a pre-existing
// cgroup in which to run the container (i.e. one that we control).
type cpuset struct {
	doneCh      <-chan bool
	source      string
	destination string
	previous    string
	sync        func(string, string)
}

func (c *cpuset) watch() {
	if c.sync == nil {
		// use the real thing if we are not doing tests
		c.sync = c.copyCpuset
	}

	ticks, cancel := helper.NewSafeTimer(cpusetSyncPeriod)
	defer cancel()

	for {
		select {
		case <-c.doneCh:
			return
		case <-ticks.C:
			c.sync(c.source, c.destination)
			ticks.Reset(cpusetSyncPeriod)
		}
	}
}

func effectiveCpusetFile() string {
	switch cgroupslib.GetMode() {
	case cgroupslib.CG1:
		return "cpuset.effective_cpus"
	default:
		return "cpuset.cpus.effective"
	}
}

func (c *cpuset) copyCpuset(source, destination string) {
	source = filepath.Join(source, effectiveCpusetFile())
	destination = filepath.Join(destination, "cpuset.cpus")

	// read the current value of usable cores
	b, err := os.ReadFile(source)
	if err != nil {
		return
	}

	// if the current value is the same as the value we wrote last,
	// there is nothing to do
	current := string(b)
	if current == c.previous {
		return
	}

	// otherwise write the new value
	err = os.WriteFile(destination, b, 0o644)
	if err != nil {
		return
	}

	// we wrote a new value; store that value so we do not write it again
	c.previous = current
}
