//go:build !windows

package rootless

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strings"

	"github.com/k3s-io/k3s/pkg/util/errors"

	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

func setupMounts(stateDir string) error {
	// Remove symlinks to the rootful files, so that we can create our own files.
	removeList := []string{
		"/var/run/netns",
		"/run/containerd",
		"/run/xtables.lock",
	}
	for _, f := range removeList {
		_ = os.RemoveAll(f)
	}

	runDir, err := resolveRunDir()
	if err != nil {
		return err
	}

	mountMap := [][]string{
		{"/var/log", filepath.Join(stateDir, "logs")},
		{"/var/lib/cni", filepath.Join(stateDir, "cni")},
		{"/var/lib/kubelet", filepath.Join(stateDir, "kubelet")},
		{"/etc/rancher", filepath.Join(stateDir, "etc", "rancher")},
		{"/run/k3s/containerd", filepath.Join(runDir, "k3s", "containerd")},
	}

	for _, v := range mountMap {
		if err := setupMount(v[0], v[1]); err != nil {
			return errors.WithMessagef(err, "failed to setup mount %s => %s", v[0], v[1])
		}
	}

	if devKmsg, err := os.Open("/dev/kmsg"); err == nil {
		devKmsg.Close()
	} else {
		// kubelet requires /dev/kmsg to be readable
		// https://github.com/rootless-containers/usernetes/issues/204
		// https://github.com/rootless-containers/usernetes/pull/214
		logrus.Debugf("`kernel.dmesg_restrict` seems to be set, bind-mounting /dev/null into /dev/kmsg")
		if err := unix.Mount("/dev/null", "/dev/kmsg", "none", unix.MS_BIND, ""); err != nil {
			return err
		}
	}

	return nil
}

func setupMount(target, dir string) error {
	toCreate := target
	for {
		if toCreate == "/" {
			return fmt.Errorf("missing /%s on the root filesystem", strings.Split(target, "/")[0])
		}

		if err := os.MkdirAll(toCreate, 0700); err == nil {
			break
		}

		toCreate = filepath.Base(toCreate)
	}

	if err := os.MkdirAll(toCreate, 0700); err != nil {
		return errors.WithMessagef(err, "failed to create directory %s", toCreate)
	}

	logrus.Debug("Mounting none ", toCreate, " tmpfs")
	if err := unix.Mount("none", toCreate, "tmpfs", 0, ""); err != nil {
		return errors.WithMessagef(err, "failed to mount tmpfs to %s", toCreate)
	}

	if err := os.MkdirAll(target, 0700); err != nil {
		return errors.WithMessagef(err, "failed to create directory %s", target)
	}

	if dir == "" {
		return nil
	}

	if err := os.MkdirAll(dir, 0700); err != nil {
		return errors.WithMessagef(err, "failed to create directory %s", dir)
	}

	logrus.Debug("Mounting ", dir, target, " none bind")
	return unix.Mount(dir, target, "none", unix.MS_BIND, "")
}

func resolveRunDir() (string, error) {
	runDir := os.Getenv("XDG_RUNTIME_DIR")
	if runDir == "" {
		u, err := user.Lookup(os.Getenv("USER"))
		if err != nil {
			return "", err
		}
		runDir = filepath.Join("/run/user", u.Uid)
	}
	return runDir, nil
}
