// Copyright 2024 The Capability Authors.
// Copyright 2013 Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package capability

import (
	"syscall"
	"unsafe"
)

type capHeader struct {
	version uint32
	pid     int32
}

type capData struct {
	effective   uint32
	permitted   uint32
	inheritable uint32
}

func capget(hdr *capHeader, data *capData) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

func capset(hdr *capHeader, data *capData) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// not yet in syscall
const (
	pr_CAP_AMBIENT           = 47
	pr_CAP_AMBIENT_IS_SET    = uintptr(1)
	pr_CAP_AMBIENT_RAISE     = uintptr(2)
	pr_CAP_AMBIENT_LOWER     = uintptr(3)
	pr_CAP_AMBIENT_CLEAR_ALL = uintptr(4)
)

func prctl(option int, arg2, arg3 uintptr) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_PRCTL, uintptr(option), arg2, arg3)
	if e1 != 0 {
		err = e1
	}
	return
}

func prctlRetInt(option int, arg2, arg3 uintptr) (int, error) {
	ret, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, uintptr(option), arg2, arg3)
	if err != 0 {
		return 0, err
	}
	return int(ret), nil
}

const (
	vfsXattrName = "security.capability"

	vfsCapVerMask = 0xff000000
	vfsCapVer1    = 0x01000000
	vfsCapVer2    = 0x02000000

	vfsCapFlagMask      = ^vfsCapVerMask
	vfsCapFlageffective = 0x000001

	vfscapDataSizeV1 = 4 * (1 + 2*1)
	vfscapDataSizeV2 = 4 * (1 + 2*2)
)

type vfscapData struct {
	magic uint32
	data  [2]struct {
		permitted   uint32
		inheritable uint32
	}
	effective [2]uint32
	version   int8
}

var _vfsXattrName *byte

func init() {
	_vfsXattrName, _ = syscall.BytePtrFromString(vfsXattrName)
}

func getVfsCap(path string, dest *vfscapData) (err error) {
	var _p0 *byte
	_p0, err = syscall.BytePtrFromString(path)
	if err != nil {
		return
	}
	r0, _, e1 := syscall.RawSyscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(dest)), vfscapDataSizeV2, 0, 0)
	if e1 != 0 {
		if e1 == syscall.ENODATA {
			dest.version = 2
			return
		}
		err = e1
	}
	switch dest.magic & vfsCapVerMask {
	case vfsCapVer1:
		dest.version = 1
		if r0 != vfscapDataSizeV1 {
			return syscall.EINVAL
		}
		dest.data[1].permitted = 0
		dest.data[1].inheritable = 0
	case vfsCapVer2:
		dest.version = 2
		if r0 != vfscapDataSizeV2 {
			return syscall.EINVAL
		}
	default:
		return syscall.EINVAL
	}
	if dest.magic&vfsCapFlageffective != 0 {
		dest.effective[0] = dest.data[0].permitted | dest.data[0].inheritable
		dest.effective[1] = dest.data[1].permitted | dest.data[1].inheritable
	} else {
		dest.effective[0] = 0
		dest.effective[1] = 0
	}
	return
}

func setVfsCap(path string, data *vfscapData) (err error) {
	var _p0 *byte
	_p0, err = syscall.BytePtrFromString(path)
	if err != nil {
		return
	}
	var size uintptr
	if data.version == 1 {
		data.magic = vfsCapVer1
		size = vfscapDataSizeV1
	} else if data.version == 2 {
		data.magic = vfsCapVer2
		if data.effective[0] != 0 || data.effective[1] != 0 {
			data.magic |= vfsCapFlageffective
		}
		size = vfscapDataSizeV2
	} else {
		return syscall.EINVAL
	}
	_, _, e1 := syscall.RawSyscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(data)), size, 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}
