// +build linux

package seccomp

import (
	"errors"
	"syscall"
)

const labelTemplate = "lb-%d-%d"

// Action is the type of action that will be taken when a
// syscall is performed.
type Action int

const (
	Kill  Action = iota - 3 // Kill the calling process of the syscall.
	Trap                    // Trap and coredump the calling process of the syscall.
	Allow                   // Allow the syscall to be completed.
)

// Syscall is the specified syscall, action, and any type of arguments
// to filter on.
type Syscall struct {
	// Value is the syscall number.
	Value uint32
	// Action is the action to perform when the specified syscall is made.
	Action Action
	// Args are filters that can be specified on the arguments to the syscall.
	Args Args
}

func (s *Syscall) scmpAction() uint32 {
	switch s.Action {
	case Allow:
		return retAllow
	case Trap:
		return retTrap
	case Kill:
		return retKill
	}
	return actionErrno(uint32(s.Action))
}

// Arg represents an argument to the syscall with the argument's index,
// the operator to apply when matching, and the argument's value at that time.
type Arg struct {
	Index uint32   // index of args which start from zero
	Op    Operator // operation, such as EQ/NE/GE/LE
	Value uint     // the value of arg
}

type Args [][]Arg

var (
	ErrUnresolvedLabel      = errors.New("seccomp: unresolved label")
	ErrDuplicateLabel       = errors.New("seccomp: duplicate label use")
	ErrUnsupportedOperation = errors.New("seccomp: unsupported operation for argument")
)

// Error returns an Action that will be used to send the calling
// process the specified errno when the syscall is made.
func Error(code syscall.Errno) Action {
	return Action(code)
}

// New returns a new syscall context for use.
func New() *Context {
	return &Context{
		syscalls: make(map[uint32]*Syscall),
	}
}

// Context holds syscalls for the current process to limit the type of
// actions the calling process can make.
type Context struct {
	syscalls map[uint32]*Syscall
}

// Add will add the specified syscall, action, and arguments to the seccomp
// Context.
func (c *Context) Add(s *Syscall) {
	c.syscalls[s.Value] = s
}

// Remove removes the specified syscall configuration from the Context.
func (c *Context) Remove(call uint32) {
	delete(c.syscalls, call)
}

// Load will apply the Context to the calling process makeing any secccomp process changes
// apply after the context is loaded.
func (c *Context) Load() error {
	filter, err := c.newFilter()
	if err != nil {
		return err
	}
	if err := prctl(prSetNoNewPrivileges, 1, 0, 0, 0); err != nil {
		return err
	}
	prog := newSockFprog(filter)
	return prog.set()
}

func (c *Context) newFilter() ([]sockFilter, error) {
	var (
		labels bpfLabels
		f      = newFilter()
	)
	for _, s := range c.syscalls {
		f.addSyscall(s, &labels)
	}
	f.allow()
	// process args for the syscalls
	for _, s := range c.syscalls {
		if err := f.addArguments(s, &labels); err != nil {
			return nil, err
		}
	}
	// apply labels for arguments
	idx := int32(len(*f) - 1)
	for ; idx >= 0; idx-- {
		lf := &(*f)[idx]
		if lf.code != (syscall.BPF_JMP + syscall.BPF_JA) {
			continue
		}
		rel := int32(lf.jt)<<8 | int32(lf.jf)
		if ((jumpJT << 8) | jumpJF) == rel {
			if labels[lf.k].location == 0xffffffff {
				return nil, ErrUnresolvedLabel
			}
			lf.k = labels[lf.k].location - uint32(idx+1)
			lf.jt = 0
			lf.jf = 0
		} else if ((labelJT << 8) | labelJF) == rel {
			if labels[lf.k].location != 0xffffffff {
				return nil, ErrDuplicateLabel
			}
			labels[lf.k].location = uint32(idx)
			lf.k = 0
			lf.jt = 0
			lf.jf = 0
		}
	}
	return *f, nil
}
