package seccomp // import "github.com/docker/docker/profiles/seccomp"

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/opencontainers/runtime-spec/specs-go"
)

// Seccomp represents the config for a seccomp profile for syscall restriction.
type Seccomp struct {
	DefaultAction specs.LinuxSeccompAction `json:"defaultAction"`
	// Architectures is kept to maintain backward compatibility with the old
	// seccomp profile.
	Architectures []specs.Arch   `json:"architectures,omitempty"`
	ArchMap       []Architecture `json:"archMap,omitempty"`
	Syscalls      []*Syscall     `json:"syscalls"`
}

// Architecture is used to represent a specific architecture
// and its sub-architectures
type Architecture struct {
	Arch      specs.Arch   `json:"architecture"`
	SubArches []specs.Arch `json:"subArchitectures"`
}

// Filter is used to conditionally apply Seccomp rules
type Filter struct {
	Caps   []string `json:"caps,omitempty"`
	Arches []string `json:"arches,omitempty"`

	// MinKernel describes the minimum kernel version the rule must be applied
	// on, in the format "<kernel version>.<major revision>" (e.g. "3.12").
	//
	// When matching the kernel version of the host, minor revisions, and distro-
	// specific suffixes are ignored, which means that "3.12.25-gentoo", "3.12-1-amd64",
	// "3.12", and "3.12-rc5" are considered equal (kernel 3, major revision 12).
	MinKernel *KernelVersion `json:"minKernel,omitempty"`
}

// Syscall is used to match a group of syscalls in Seccomp
type Syscall struct {
	Name     string                   `json:"name,omitempty"`
	Names    []string                 `json:"names,omitempty"`
	Action   specs.LinuxSeccompAction `json:"action"`
	ErrnoRet *uint                    `json:"errnoRet,omitempty"`
	Args     []*specs.LinuxSeccompArg `json:"args"`
	Comment  string                   `json:"comment"`
	Includes Filter                   `json:"includes"`
	Excludes Filter                   `json:"excludes"`
}

// KernelVersion holds information about the kernel.
type KernelVersion struct {
	Kernel uint64 // Version of the Kernel (i.e., the "4" in "4.1.2-generic")
	Major  uint64 // Major revision of the Kernel (i.e., the "1" in "4.1.2-generic")
}

// String implements fmt.Stringer for KernelVersion
func (k *KernelVersion) String() string {
	if k.Kernel > 0 || k.Major > 0 {
		return fmt.Sprintf("%d.%d", k.Kernel, k.Major)
	}
	return ""
}

// MarshalJSON implements json.Unmarshaler for KernelVersion
func (k *KernelVersion) MarshalJSON() ([]byte, error) {
	return json.Marshal(k.String())
}

// UnmarshalJSON implements json.Marshaler for KernelVersion
func (k *KernelVersion) UnmarshalJSON(version []byte) error {
	var (
		ver string
		err error
	)

	// make sure we have a string
	if err = json.Unmarshal(version, &ver); err != nil {
		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
	}
	if ver == "" {
		return nil
	}
	parts := strings.SplitN(ver, ".", 3)
	if len(parts) != 2 {
		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>"`, string(version))
	}
	if k.Kernel, err = strconv.ParseUint(parts[0], 10, 8); err != nil {
		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
	}
	if k.Major, err = strconv.ParseUint(parts[1], 10, 8); err != nil {
		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
	}
	if k.Kernel == 0 && k.Major == 0 {
		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": version cannot be 0.0`, string(version))
	}
	return nil
}
