// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package cgroup

import (
	"context"
	"path/filepath"

	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf"
	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf/devicefilter"
	"github.com/opencontainers/runc/libcontainer/devices"
	"github.com/opencontainers/runc/libcontainer/specconv"
	"golang.org/x/sys/unix"
	"golang.org/x/xerrors"

	"github.com/gitpod-io/gitpod/common-go/log"
)

type FuseDeviceEnablerV2 struct{}

func (c *FuseDeviceEnablerV2) Name() string  { return "fuse-device-enabler-v2" }
func (c *FuseDeviceEnablerV2) Type() Version { return Version2 }

func (c *FuseDeviceEnablerV2) Apply(ctx context.Context, opts *PluginOptions) error {
	fullCgroupPath := filepath.Join(opts.BasePath, opts.CgroupPath)
	log.WithField("cgroupPath", fullCgroupPath).Debug("configuring devices")

	cgroupFD, err := unix.Open(fullCgroupPath, unix.O_DIRECTORY|unix.O_RDONLY|unix.O_CLOEXEC, 0600)
	if err != nil {
		return xerrors.Errorf("cannot get directory fd for %s: %w", fullCgroupPath, err)
	}
	defer unix.Close(cgroupFD)

	insts, license, err := devicefilter.DeviceFilter(composeDeviceRules())
	if err != nil {
		return xerrors.Errorf("failed to generate device filter: %w", err)
	}

	_, err = ebpf.LoadAttachCgroupDeviceFilter(insts, license, cgroupFD)
	if err != nil {
		return xerrors.Errorf("failed to attach cgroup device filter: %w", err)
	}

	return nil
}

func composeDeviceRules() []*devices.Rule {
	denyAll := devices.Rule{
		Type:        'a',
		Permissions: "rwm",
		Allow:       false,
	}

	allowFuse := devices.Rule{
		Type:        'c',
		Major:       fuseDeviceMajor,
		Minor:       fuseDeviceMinor,
		Permissions: "rwm",
		Allow:       true,
	}

	deviceRules := make([]*devices.Rule, 0)
	deviceRules = append(deviceRules, &denyAll, &allowFuse)
	for _, device := range specconv.AllowedDevices {
		deviceRules = append(deviceRules, &device.Rule)
	}

	return deviceRules
}
