package oci

import (
	"fmt"
	"os"
	"runtime"
	"sync"

	"github.com/moby/moby/v2/daemon/internal/platform"
	"github.com/moby/moby/v2/daemon/pkg/oci/caps"
	"github.com/opencontainers/runtime-spec/specs-go"
)

func iPtr(i int64) *int64 { return &i }

const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

// DefaultPathEnv is unix style list of directories to search for
// executables. Each directory is separated from the next by a colon
// ':' character .
// For Windows containers, an empty string is returned as the default
// path will be set by the container, and Docker has no context of what the
// default path should be.
//
// TODO(thaJeztah) align Windows default with BuildKit; see https://github.com/moby/buildkit/pull/1747
// TODO(thaJeztah) use defaults from containerd (but align it with BuildKit; see https://github.com/moby/buildkit/pull/1747)
func DefaultPathEnv(os string) string {
	if os == "windows" {
		return ""
	}
	return defaultUnixPathEnv
}

// DefaultSpec returns the default spec used by docker for the current Platform
func DefaultSpec() specs.Spec {
	if runtime.GOOS == "windows" {
		return DefaultWindowsSpec()
	}
	return DefaultLinuxSpec()
}

// DefaultWindowsSpec create a default spec for running Windows containers
func DefaultWindowsSpec() specs.Spec {
	return specs.Spec{
		Version: specs.Version,
		Windows: &specs.Windows{},
		Process: &specs.Process{},
		Root:    &specs.Root{},
	}
}

// DefaultLinuxSpec create a default spec for running Linux containers
func DefaultLinuxSpec() specs.Spec {
	return specs.Spec{
		Version: specs.Version,
		Process: &specs.Process{
			Capabilities: &specs.LinuxCapabilities{
				Bounding:  caps.DefaultCapabilities(),
				Permitted: caps.DefaultCapabilities(),
				Effective: caps.DefaultCapabilities(),
			},
		},
		Root: &specs.Root{},
		Mounts: []specs.Mount{
			{
				Destination: "/proc",
				Type:        "proc",
				Source:      "proc",
				Options:     []string{"nosuid", "noexec", "nodev"},
			},
			{
				Destination: "/dev",
				Type:        "tmpfs",
				Source:      "tmpfs",
				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
			},
			{
				Destination: "/dev/pts",
				Type:        "devpts",
				Source:      "devpts",
				Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
			},
			{
				Destination: "/sys",
				Type:        "sysfs",
				Source:      "sysfs",
				Options:     []string{"nosuid", "noexec", "nodev", "ro"},
			},
			{
				Destination: "/sys/fs/cgroup",
				Type:        "cgroup",
				Source:      "cgroup",
				Options:     []string{"ro", "nosuid", "noexec", "nodev"},
			},
			{
				Destination: "/dev/mqueue",
				Type:        "mqueue",
				Source:      "mqueue",
				Options:     []string{"nosuid", "noexec", "nodev"},
			},
			{
				Destination: "/dev/shm",
				Type:        "tmpfs",
				Source:      "shm",
				Options:     []string{"nosuid", "noexec", "nodev", "mode=1777"},
			},
		},
		Linux: &specs.Linux{
			MaskedPaths: defaultLinuxMaskedPaths(),
			ReadonlyPaths: []string{
				"/proc/bus",
				"/proc/fs",
				"/proc/irq",
				"/proc/sys",
				"/proc/sysrq-trigger",
			},
			Namespaces: []specs.LinuxNamespace{
				{Type: specs.MountNamespace},
				{Type: specs.NetworkNamespace},
				{Type: specs.UTSNamespace},
				{Type: specs.PIDNamespace},
				{Type: specs.IPCNamespace},
			},
			// Devices implicitly contains the following devices:
			// null, zero, full, random, urandom, tty, console, and ptmx.
			// ptmx is a bind mount or symlink of the container's ptmx.
			// See also: https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#default-devices
			Devices: []specs.LinuxDevice{},
			Resources: &specs.LinuxResources{
				Devices: []specs.LinuxDeviceCgroup{
					{
						Allow:  false,
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(1),
						Minor:  iPtr(5),
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(1),
						Minor:  iPtr(3),
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(1),
						Minor:  iPtr(9),
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(1),
						Minor:  iPtr(8),
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(5),
						Minor:  iPtr(0),
						Access: "rwm",
					},
					{
						Allow:  true,
						Type:   "c",
						Major:  iPtr(5),
						Minor:  iPtr(1),
						Access: "rwm",
					},
					{
						Allow:  false,
						Type:   "c",
						Major:  iPtr(10),
						Minor:  iPtr(229),
						Access: "rwm",
					},
				},
			},
		},
	}
}

// defaultLinuxMaskedPaths returns the default list of paths to mask in a Linux
// container. The paths won't change while the docker daemon is running, so just
// compute them once.
var defaultLinuxMaskedPaths = sync.OnceValue(func() []string {
	maskedPaths := []string{
		"/proc/acpi",
		"/proc/asound",
		"/proc/interrupts", // https://github.com/moby/moby/security/advisories/GHSA-6fw5-f8r9-fgfm
		"/proc/kcore",
		"/proc/keys",
		"/proc/latency_stats",
		"/proc/sched_debug",
		"/proc/scsi",
		"/proc/timer_list",
		"/proc/timer_stats",
		"/sys/devices/virtual/powercap", // https://github.com/moby/moby/security/advisories/GHSA-jq35-85cj-fj4p
		"/sys/firmware",
	}

	// https://github.com/moby/moby/security/advisories/GHSA-6fw5-f8r9-fgfm
	cpus := platform.PossibleCPU()
	for _, cpu := range cpus {
		path := fmt.Sprintf("/sys/devices/system/cpu/cpu%d/thermal_throttle", cpu)
		if _, err := os.Stat(path); err == nil {
			maskedPaths = append(maskedPaths, path)
		}
	}
	return maskedPaths
})
