// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package wrapperd provides a wrapper for running services.
package wrapperd

import (
	"flag"
	"log"
	"os"
	"strings"

	"github.com/containerd/cgroups"
	cgroupsv2 "github.com/containerd/cgroups/v2"
	"github.com/containerd/containerd/sys"
	"github.com/siderolabs/gen/slices"
	"golang.org/x/sys/unix"
	"kernel.org/pub/linux/libs/security/libcap/cap"

	krnl "github.com/siderolabs/talos/pkg/kernel"
	"github.com/siderolabs/talos/pkg/machinery/constants"
	"github.com/siderolabs/talos/pkg/machinery/kernel"
)

var (
	name        string
	droppedCaps string
	cgroupPath  string
	oomScore    int
	uid         int
)

// Main is the entrypoint into /sbin/wrapperd.
//
//nolint:gocyclo
func Main() {
	flag.StringVar(&name, "name", "", "process name")
	flag.StringVar(&droppedCaps, "dropped-caps", "", "comma-separated list of capabilities to drop")
	flag.StringVar(&cgroupPath, "cgroup-path", "", "cgroup path to use")
	flag.IntVar(&oomScore, "oom-score", 0, "oom score to set")
	flag.IntVar(&uid, "uid", 0, "uid to set for the process")
	flag.Parse()

	currentPid := os.Getpid()

	if oomScore != 0 {
		if err := sys.AdjustOOMScore(currentPid, oomScore); err != nil {
			log.Fatalf("Failed to change OOMScoreAdj of process %s to %d", name, oomScore)
		}
	}

	// load the cgroup and put the process into the cgroup
	if cgroupPath != "" {
		if cgroups.Mode() == cgroups.Unified {
			cgv2, err := cgroupsv2.LoadManager(constants.CgroupMountPath, cgroupPath)
			if err != nil {
				log.Fatalf("failed to load cgroup %s: %v", cgroupPath, err)
			}

			if err := cgv2.AddProc(uint64(currentPid)); err != nil {
				log.Fatalf("Failed to move process %s to cgroup: %v", name, err)
			}
		} else {
			cgv1, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(cgroupPath))
			if err != nil {
				log.Fatalf("failed to load cgroup %s: %v", cgroupPath, err)
			}

			if err := cgv1.Add(cgroups.Process{
				Pid: currentPid,
			}); err != nil {
				log.Fatalf("Failed to move process %s to cgroup: %v", name, err)
			}
		}
	}

	prop, err := krnl.ReadParam(&kernel.Param{Key: "proc.sys.kernel.kexec_load_disabled"})
	if v := strings.TrimSpace(string(prop)); err == nil && v != "0" {
		log.Printf("kernel.kexec_load_disabled is %v, skipping dropping capabilities", v)
	} else if droppedCaps != "" {
		caps := strings.Split(droppedCaps, ",")
		dropCaps := slices.Map(caps, func(c string) cap.Value {
			capability, capErr := cap.FromName(c)
			if capErr != nil {
				log.Fatalf("failed to parse capability: %v", capErr)
			}

			return capability
		})

		// drop capabilities
		iab := cap.IABGetProc()
		if err = iab.SetVector(cap.Bound, true, dropCaps...); err != nil {
			log.Fatalf("failed to set capabilities: %v", err)
		}

		if err = iab.SetProc(); err != nil {
			log.Fatalf("failed to apply capabilities: %v", err)
		}
	}

	if uid > 0 {
		err = unix.Setuid(uid)
		if err != nil {
			log.Fatalf("failed to setuid: %v", err)
		}
	}

	if err := unix.Exec(flag.Args()[0], flag.Args()[0:], os.Environ()); err != nil {
		log.Fatalf("failed to exec: %v", err)
	}
}
