// Package generate implements functions generating container config files.
package generate

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"

	rspec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/runtime-tools/generate/seccomp"
	"github.com/opencontainers/runtime-tools/validate"
	"github.com/syndtr/gocapability/capability"
)

var (
	// Namespaces include the names of supported namespaces.
	Namespaces = []string{"network", "pid", "mount", "ipc", "uts", "user", "cgroup"}
)

// Generator represents a generator for a container spec.
type Generator struct {
	spec         *rspec.Spec
	HostSpecific bool
}

// ExportOptions have toggles for exporting only certain parts of the specification
type ExportOptions struct {
	Seccomp bool // seccomp toggles if only seccomp should be exported
}

// New creates a spec Generator with the default spec.
func New() Generator {
	spec := rspec.Spec{
		Version: rspec.Version,
		Root: &rspec.Root{
			Path:     "",
			Readonly: false,
		},
		Process: &rspec.Process{
			Terminal: false,
			User:     rspec.User{},
			Args: []string{
				"sh",
			},
			Env: []string{
				"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
				"TERM=xterm",
			},
			Cwd: "/",
			Capabilities: &rspec.LinuxCapabilities{
				Bounding: []string{
					"CAP_CHOWN",
					"CAP_DAC_OVERRIDE",
					"CAP_FSETID",
					"CAP_FOWNER",
					"CAP_MKNOD",
					"CAP_NET_RAW",
					"CAP_SETGID",
					"CAP_SETUID",
					"CAP_SETFCAP",
					"CAP_SETPCAP",
					"CAP_NET_BIND_SERVICE",
					"CAP_SYS_CHROOT",
					"CAP_KILL",
					"CAP_AUDIT_WRITE",
				},
				Permitted: []string{
					"CAP_CHOWN",
					"CAP_DAC_OVERRIDE",
					"CAP_FSETID",
					"CAP_FOWNER",
					"CAP_MKNOD",
					"CAP_NET_RAW",
					"CAP_SETGID",
					"CAP_SETUID",
					"CAP_SETFCAP",
					"CAP_SETPCAP",
					"CAP_NET_BIND_SERVICE",
					"CAP_SYS_CHROOT",
					"CAP_KILL",
					"CAP_AUDIT_WRITE",
				},
				Inheritable: []string{
					"CAP_CHOWN",
					"CAP_DAC_OVERRIDE",
					"CAP_FSETID",
					"CAP_FOWNER",
					"CAP_MKNOD",
					"CAP_NET_RAW",
					"CAP_SETGID",
					"CAP_SETUID",
					"CAP_SETFCAP",
					"CAP_SETPCAP",
					"CAP_NET_BIND_SERVICE",
					"CAP_SYS_CHROOT",
					"CAP_KILL",
					"CAP_AUDIT_WRITE",
				},
				Effective: []string{
					"CAP_CHOWN",
					"CAP_DAC_OVERRIDE",
					"CAP_FSETID",
					"CAP_FOWNER",
					"CAP_MKNOD",
					"CAP_NET_RAW",
					"CAP_SETGID",
					"CAP_SETUID",
					"CAP_SETFCAP",
					"CAP_SETPCAP",
					"CAP_NET_BIND_SERVICE",
					"CAP_SYS_CHROOT",
					"CAP_KILL",
					"CAP_AUDIT_WRITE",
				},
				Ambient: []string{
					"CAP_CHOWN",
					"CAP_DAC_OVERRIDE",
					"CAP_FSETID",
					"CAP_FOWNER",
					"CAP_MKNOD",
					"CAP_NET_RAW",
					"CAP_SETGID",
					"CAP_SETUID",
					"CAP_SETFCAP",
					"CAP_SETPCAP",
					"CAP_NET_BIND_SERVICE",
					"CAP_SYS_CHROOT",
					"CAP_KILL",
					"CAP_AUDIT_WRITE",
				},
			},
			Rlimits: []rspec.POSIXRlimit{
				{
					Type: "RLIMIT_NOFILE",
					Hard: uint64(1024),
					Soft: uint64(1024),
				},
			},
		},
		Hostname: "mrsdalloway",
		Mounts: []rspec.Mount{
			{
				Destination: "/proc",
				Type:        "proc",
				Source:      "proc",
				Options:     nil,
			},
			{
				Destination: "/dev",
				Type:        "tmpfs",
				Source:      "tmpfs",
				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
			},
			{
				Destination: "/dev/pts",
				Type:        "devpts",
				Source:      "devpts",
				Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
			},
			{
				Destination: "/dev/shm",
				Type:        "tmpfs",
				Source:      "shm",
				Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
			},
			{
				Destination: "/dev/mqueue",
				Type:        "mqueue",
				Source:      "mqueue",
				Options:     []string{"nosuid", "noexec", "nodev"},
			},
			{
				Destination: "/sys",
				Type:        "sysfs",
				Source:      "sysfs",
				Options:     []string{"nosuid", "noexec", "nodev", "ro"},
			},
		},
		Linux: &rspec.Linux{
			Resources: &rspec.LinuxResources{
				Devices: []rspec.LinuxDeviceCgroup{
					{
						Allow:  false,
						Access: "rwm",
					},
				},
			},
			Namespaces: []rspec.LinuxNamespace{
				{
					Type: "pid",
				},
				{
					Type: "network",
				},
				{
					Type: "ipc",
				},
				{
					Type: "uts",
				},
				{
					Type: "mount",
				},
			},
			Devices: []rspec.LinuxDevice{},
		},
	}
	spec.Linux.Seccomp = seccomp.DefaultProfile(&spec)
	return Generator{
		spec: &spec,
	}
}

// NewFromSpec creates a spec Generator from a given spec.
func NewFromSpec(spec *rspec.Spec) Generator {
	return Generator{
		spec: spec,
	}
}

// NewFromFile loads the template specified in a file into a spec Generator.
func NewFromFile(path string) (Generator, error) {
	cf, err := os.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return Generator{}, fmt.Errorf("template configuration at %s not found", path)
		}
	}
	defer cf.Close()

	return NewFromTemplate(cf)
}

// NewFromTemplate loads the template from io.Reader into a spec Generator.
func NewFromTemplate(r io.Reader) (Generator, error) {
	var spec rspec.Spec
	if err := json.NewDecoder(r).Decode(&spec); err != nil {
		return Generator{}, err
	}
	return Generator{
		spec: &spec,
	}, nil
}

// SetSpec sets the spec in the Generator g.
func (g *Generator) SetSpec(spec *rspec.Spec) {
	g.spec = spec
}

// Spec gets the spec in the Generator g.
func (g *Generator) Spec() *rspec.Spec {
	return g.spec
}

// Save writes the spec into w.
func (g *Generator) Save(w io.Writer, exportOpts ExportOptions) (err error) {
	var data []byte

	if g.spec.Linux != nil {
		buf, err := json.Marshal(g.spec.Linux)
		if err != nil {
			return err
		}
		if string(buf) == "{}" {
			g.spec.Linux = nil
		}
	}

	if exportOpts.Seccomp {
		data, err = json.MarshalIndent(g.spec.Linux.Seccomp, "", "\t")
	} else {
		data, err = json.MarshalIndent(g.spec, "", "\t")
	}
	if err != nil {
		return err
	}

	_, err = w.Write(data)
	if err != nil {
		return err
	}

	return nil
}

// SaveToFile writes the spec into a file.
func (g *Generator) SaveToFile(path string, exportOpts ExportOptions) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	return g.Save(f, exportOpts)
}

// SetVersion sets g.spec.Version.
func (g *Generator) SetVersion(version string) {
	g.initSpec()
	g.spec.Version = version
}

// SetRootPath sets g.spec.Root.Path.
func (g *Generator) SetRootPath(path string) {
	g.initSpecRoot()
	g.spec.Root.Path = path
}

// SetRootReadonly sets g.spec.Root.Readonly.
func (g *Generator) SetRootReadonly(b bool) {
	g.initSpecRoot()
	g.spec.Root.Readonly = b
}

// SetHostname sets g.spec.Hostname.
func (g *Generator) SetHostname(s string) {
	g.initSpec()
	g.spec.Hostname = s
}

// ClearAnnotations clears g.spec.Annotations.
func (g *Generator) ClearAnnotations() {
	if g.spec == nil {
		return
	}
	g.spec.Annotations = make(map[string]string)
}

// AddAnnotation adds an annotation into g.spec.Annotations.
func (g *Generator) AddAnnotation(key, value string) {
	g.initSpecAnnotations()
	g.spec.Annotations[key] = value
}

// RemoveAnnotation remove an annotation from g.spec.Annotations.
func (g *Generator) RemoveAnnotation(key string) {
	if g.spec == nil || g.spec.Annotations == nil {
		return
	}
	delete(g.spec.Annotations, key)
}

// SetProcessUID sets g.spec.Process.User.UID.
func (g *Generator) SetProcessUID(uid uint32) {
	g.initSpecProcess()
	g.spec.Process.User.UID = uid
}

// SetProcessGID sets g.spec.Process.User.GID.
func (g *Generator) SetProcessGID(gid uint32) {
	g.initSpecProcess()
	g.spec.Process.User.GID = gid
}

// SetProcessCwd sets g.spec.Process.Cwd.
func (g *Generator) SetProcessCwd(cwd string) {
	g.initSpecProcess()
	g.spec.Process.Cwd = cwd
}

// SetProcessNoNewPrivileges sets g.spec.Process.NoNewPrivileges.
func (g *Generator) SetProcessNoNewPrivileges(b bool) {
	g.initSpecProcess()
	g.spec.Process.NoNewPrivileges = b
}

// SetProcessTerminal sets g.spec.Process.Terminal.
func (g *Generator) SetProcessTerminal(b bool) {
	g.initSpecProcess()
	g.spec.Process.Terminal = b
}

// SetProcessApparmorProfile sets g.spec.Process.ApparmorProfile.
func (g *Generator) SetProcessApparmorProfile(prof string) {
	g.initSpecProcess()
	g.spec.Process.ApparmorProfile = prof
}

// SetProcessArgs sets g.spec.Process.Args.
func (g *Generator) SetProcessArgs(args []string) {
	g.initSpecProcess()
	g.spec.Process.Args = args
}

// ClearProcessEnv clears g.spec.Process.Env.
func (g *Generator) ClearProcessEnv() {
	if g.spec == nil {
		return
	}
	g.spec.Process.Env = []string{}
}

// AddProcessEnv adds name=value into g.spec.Process.Env, or replaces an
// existing entry with the given name.
func (g *Generator) AddProcessEnv(name, value string) {
	g.initSpecProcess()

	env := fmt.Sprintf("%s=%s", name, value)
	for idx := range g.spec.Process.Env {
		if strings.HasPrefix(g.spec.Process.Env[idx], name+"=") {
			g.spec.Process.Env[idx] = env
			return
		}
	}
	g.spec.Process.Env = append(g.spec.Process.Env, env)
}

// AddProcessRlimits adds rlimit into g.spec.Process.Rlimits.
func (g *Generator) AddProcessRlimits(rType string, rHard uint64, rSoft uint64) {
	g.initSpecProcess()
	for i, rlimit := range g.spec.Process.Rlimits {
		if rlimit.Type == rType {
			g.spec.Process.Rlimits[i].Hard = rHard
			g.spec.Process.Rlimits[i].Soft = rSoft
			return
		}
	}

	newRlimit := rspec.POSIXRlimit{
		Type: rType,
		Hard: rHard,
		Soft: rSoft,
	}
	g.spec.Process.Rlimits = append(g.spec.Process.Rlimits, newRlimit)
}

// RemoveProcessRlimits removes a rlimit from g.spec.Process.Rlimits.
func (g *Generator) RemoveProcessRlimits(rType string) error {
	if g.spec == nil {
		return nil
	}
	for i, rlimit := range g.spec.Process.Rlimits {
		if rlimit.Type == rType {
			g.spec.Process.Rlimits = append(g.spec.Process.Rlimits[:i], g.spec.Process.Rlimits[i+1:]...)
			return nil
		}
	}
	return nil
}

// ClearProcessRlimits clear g.spec.Process.Rlimits.
func (g *Generator) ClearProcessRlimits() {
	if g.spec == nil {
		return
	}
	g.spec.Process.Rlimits = []rspec.POSIXRlimit{}
}

// ClearProcessAdditionalGids clear g.spec.Process.AdditionalGids.
func (g *Generator) ClearProcessAdditionalGids() {
	if g.spec == nil {
		return
	}
	g.spec.Process.User.AdditionalGids = []uint32{}
}

// AddProcessAdditionalGid adds an additional gid into g.spec.Process.AdditionalGids.
func (g *Generator) AddProcessAdditionalGid(gid uint32) {
	g.initSpecProcess()
	for _, group := range g.spec.Process.User.AdditionalGids {
		if group == gid {
			return
		}
	}
	g.spec.Process.User.AdditionalGids = append(g.spec.Process.User.AdditionalGids, gid)
}

// SetProcessSelinuxLabel sets g.spec.Process.SelinuxLabel.
func (g *Generator) SetProcessSelinuxLabel(label string) {
	g.initSpecProcess()
	g.spec.Process.SelinuxLabel = label
}

// SetLinuxCgroupsPath sets g.spec.Linux.CgroupsPath.
func (g *Generator) SetLinuxCgroupsPath(path string) {
	g.initSpecLinux()
	g.spec.Linux.CgroupsPath = path
}

// SetLinuxMountLabel sets g.spec.Linux.MountLabel.
func (g *Generator) SetLinuxMountLabel(label string) {
	g.initSpecLinux()
	g.spec.Linux.MountLabel = label
}

// SetProcessOOMScoreAdj sets g.spec.Process.OOMScoreAdj.
func (g *Generator) SetProcessOOMScoreAdj(adj int) {
	g.initSpecProcess()
	g.spec.Process.OOMScoreAdj = &adj
}

// SetLinuxResourcesCPUShares sets g.spec.Linux.Resources.CPU.Shares.
func (g *Generator) SetLinuxResourcesCPUShares(shares uint64) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.Shares = &shares
}

// SetLinuxResourcesCPUQuota sets g.spec.Linux.Resources.CPU.Quota.
func (g *Generator) SetLinuxResourcesCPUQuota(quota int64) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.Quota = &quota
}

// SetLinuxResourcesCPUPeriod sets g.spec.Linux.Resources.CPU.Period.
func (g *Generator) SetLinuxResourcesCPUPeriod(period uint64) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.Period = &period
}

// SetLinuxResourcesCPURealtimeRuntime sets g.spec.Linux.Resources.CPU.RealtimeRuntime.
func (g *Generator) SetLinuxResourcesCPURealtimeRuntime(time int64) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.RealtimeRuntime = &time
}

// SetLinuxResourcesCPURealtimePeriod sets g.spec.Linux.Resources.CPU.RealtimePeriod.
func (g *Generator) SetLinuxResourcesCPURealtimePeriod(period uint64) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.RealtimePeriod = &period
}

// SetLinuxResourcesCPUCpus sets g.spec.Linux.Resources.CPU.Cpus.
func (g *Generator) SetLinuxResourcesCPUCpus(cpus string) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.Cpus = cpus
}

// SetLinuxResourcesCPUMems sets g.spec.Linux.Resources.CPU.Mems.
func (g *Generator) SetLinuxResourcesCPUMems(mems string) {
	g.initSpecLinuxResourcesCPU()
	g.spec.Linux.Resources.CPU.Mems = mems
}

// AddLinuxResourcesHugepageLimit adds or sets g.spec.Linux.Resources.HugepageLimits.
func (g *Generator) AddLinuxResourcesHugepageLimit(pageSize string, limit uint64) {
	hugepageLimit := rspec.LinuxHugepageLimit{
		Pagesize: pageSize,
		Limit:    limit,
	}

	g.initSpecLinuxResources()
	for i, pageLimit := range g.spec.Linux.Resources.HugepageLimits {
		if pageLimit.Pagesize == pageSize {
			g.spec.Linux.Resources.HugepageLimits[i].Limit = limit
			return
		}
	}
	g.spec.Linux.Resources.HugepageLimits = append(g.spec.Linux.Resources.HugepageLimits, hugepageLimit)
}

// DropLinuxResourcesHugepageLimit drops a hugepage limit from g.spec.Linux.Resources.HugepageLimits.
func (g *Generator) DropLinuxResourcesHugepageLimit(pageSize string) error {
	g.initSpecLinuxResources()
	for i, pageLimit := range g.spec.Linux.Resources.HugepageLimits {
		if pageLimit.Pagesize == pageSize {
			g.spec.Linux.Resources.HugepageLimits = append(g.spec.Linux.Resources.HugepageLimits[:i], g.spec.Linux.Resources.HugepageLimits[i+1:]...)
			return nil
		}
	}

	return nil
}

// SetLinuxResourcesMemoryLimit sets g.spec.Linux.Resources.Memory.Limit.
func (g *Generator) SetLinuxResourcesMemoryLimit(limit int64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.Limit = &limit
}

// SetLinuxResourcesMemoryReservation sets g.spec.Linux.Resources.Memory.Reservation.
func (g *Generator) SetLinuxResourcesMemoryReservation(reservation int64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.Reservation = &reservation
}

// SetLinuxResourcesMemorySwap sets g.spec.Linux.Resources.Memory.Swap.
func (g *Generator) SetLinuxResourcesMemorySwap(swap int64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.Swap = &swap
}

// SetLinuxResourcesMemoryKernel sets g.spec.Linux.Resources.Memory.Kernel.
func (g *Generator) SetLinuxResourcesMemoryKernel(kernel int64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.Kernel = &kernel
}

// SetLinuxResourcesMemoryKernelTCP sets g.spec.Linux.Resources.Memory.KernelTCP.
func (g *Generator) SetLinuxResourcesMemoryKernelTCP(kernelTCP int64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.KernelTCP = &kernelTCP
}

// SetLinuxResourcesMemorySwappiness sets g.spec.Linux.Resources.Memory.Swappiness.
func (g *Generator) SetLinuxResourcesMemorySwappiness(swappiness uint64) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.Swappiness = &swappiness
}

// SetLinuxResourcesMemoryDisableOOMKiller sets g.spec.Linux.Resources.Memory.DisableOOMKiller.
func (g *Generator) SetLinuxResourcesMemoryDisableOOMKiller(disable bool) {
	g.initSpecLinuxResourcesMemory()
	g.spec.Linux.Resources.Memory.DisableOOMKiller = &disable
}

// SetLinuxResourcesNetworkClassID sets g.spec.Linux.Resources.Network.ClassID.
func (g *Generator) SetLinuxResourcesNetworkClassID(classid uint32) {
	g.initSpecLinuxResourcesNetwork()
	g.spec.Linux.Resources.Network.ClassID = &classid
}

// AddLinuxResourcesNetworkPriorities adds or sets g.spec.Linux.Resources.Network.Priorities.
func (g *Generator) AddLinuxResourcesNetworkPriorities(name string, prio uint32) {
	g.initSpecLinuxResourcesNetwork()
	for i, netPriority := range g.spec.Linux.Resources.Network.Priorities {
		if netPriority.Name == name {
			g.spec.Linux.Resources.Network.Priorities[i].Priority = prio
			return
		}
	}
	interfacePrio := new(rspec.LinuxInterfacePriority)
	interfacePrio.Name = name
	interfacePrio.Priority = prio
	g.spec.Linux.Resources.Network.Priorities = append(g.spec.Linux.Resources.Network.Priorities, *interfacePrio)
}

// DropLinuxResourcesNetworkPriorities drops one item from g.spec.Linux.Resources.Network.Priorities.
func (g *Generator) DropLinuxResourcesNetworkPriorities(name string) {
	g.initSpecLinuxResourcesNetwork()
	for i, netPriority := range g.spec.Linux.Resources.Network.Priorities {
		if netPriority.Name == name {
			g.spec.Linux.Resources.Network.Priorities = append(g.spec.Linux.Resources.Network.Priorities[:i], g.spec.Linux.Resources.Network.Priorities[i+1:]...)
			return
		}
	}
}

// SetLinuxResourcesPidsLimit sets g.spec.Linux.Resources.Pids.Limit.
func (g *Generator) SetLinuxResourcesPidsLimit(limit int64) {
	g.initSpecLinuxResourcesPids()
	g.spec.Linux.Resources.Pids.Limit = limit
}

// ClearLinuxSysctl clears g.spec.Linux.Sysctl.
func (g *Generator) ClearLinuxSysctl() {
	if g.spec == nil || g.spec.Linux == nil {
		return
	}
	g.spec.Linux.Sysctl = make(map[string]string)
}

// AddLinuxSysctl adds a new sysctl config into g.spec.Linux.Sysctl.
func (g *Generator) AddLinuxSysctl(key, value string) {
	g.initSpecLinuxSysctl()
	g.spec.Linux.Sysctl[key] = value
}

// RemoveLinuxSysctl removes a sysctl config from g.spec.Linux.Sysctl.
func (g *Generator) RemoveLinuxSysctl(key string) {
	if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Sysctl == nil {
		return
	}
	delete(g.spec.Linux.Sysctl, key)
}

// ClearLinuxUIDMappings clear g.spec.Linux.UIDMappings.
func (g *Generator) ClearLinuxUIDMappings() {
	if g.spec == nil || g.spec.Linux == nil {
		return
	}
	g.spec.Linux.UIDMappings = []rspec.LinuxIDMapping{}
}

// AddLinuxUIDMapping adds uidMap into g.spec.Linux.UIDMappings.
func (g *Generator) AddLinuxUIDMapping(hid, cid, size uint32) {
	idMapping := rspec.LinuxIDMapping{
		HostID:      hid,
		ContainerID: cid,
		Size:        size,
	}

	g.initSpecLinux()
	g.spec.Linux.UIDMappings = append(g.spec.Linux.UIDMappings, idMapping)
}

// ClearLinuxGIDMappings clear g.spec.Linux.GIDMappings.
func (g *Generator) ClearLinuxGIDMappings() {
	if g.spec == nil || g.spec.Linux == nil {
		return
	}
	g.spec.Linux.GIDMappings = []rspec.LinuxIDMapping{}
}

// AddLinuxGIDMapping adds gidMap into g.spec.Linux.GIDMappings.
func (g *Generator) AddLinuxGIDMapping(hid, cid, size uint32) {
	idMapping := rspec.LinuxIDMapping{
		HostID:      hid,
		ContainerID: cid,
		Size:        size,
	}

	g.initSpecLinux()
	g.spec.Linux.GIDMappings = append(g.spec.Linux.GIDMappings, idMapping)
}

// SetLinuxRootPropagation sets g.spec.Linux.RootfsPropagation.
func (g *Generator) SetLinuxRootPropagation(rp string) error {
	switch rp {
	case "":
	case "private":
	case "rprivate":
	case "slave":
	case "rslave":
	case "shared":
	case "rshared":
	default:
		return fmt.Errorf("rootfs-propagation must be empty or one of private|rprivate|slave|rslave|shared|rshared")
	}
	g.initSpecLinux()
	g.spec.Linux.RootfsPropagation = rp
	return nil
}

// ClearPreStartHooks clear g.spec.Hooks.Prestart.
func (g *Generator) ClearPreStartHooks() {
	if g.spec == nil {
		return
	}
	if g.spec.Hooks == nil {
		return
	}
	g.spec.Hooks.Prestart = []rspec.Hook{}
}

// AddPreStartHook add a prestart hook into g.spec.Hooks.Prestart.
func (g *Generator) AddPreStartHook(path string, args []string) {
	g.initSpecHooks()
	hook := rspec.Hook{Path: path, Args: args}
	g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook)
}

// ClearPostStopHooks clear g.spec.Hooks.Poststop.
func (g *Generator) ClearPostStopHooks() {
	if g.spec == nil {
		return
	}
	if g.spec.Hooks == nil {
		return
	}
	g.spec.Hooks.Poststop = []rspec.Hook{}
}

// AddPostStopHook adds a poststop hook into g.spec.Hooks.Poststop.
func (g *Generator) AddPostStopHook(path string, args []string) {
	g.initSpecHooks()
	hook := rspec.Hook{Path: path, Args: args}
	g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook)
}

// ClearPostStartHooks clear g.spec.Hooks.Poststart.
func (g *Generator) ClearPostStartHooks() {
	if g.spec == nil {
		return
	}
	if g.spec.Hooks == nil {
		return
	}
	g.spec.Hooks.Poststart = []rspec.Hook{}
}

// AddPostStartHook adds a poststart hook into g.spec.Hooks.Poststart.
func (g *Generator) AddPostStartHook(path string, args []string) {
	g.initSpecHooks()
	hook := rspec.Hook{Path: path, Args: args}
	g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook)
}

// AddTmpfsMount adds a tmpfs mount into g.spec.Mounts.
func (g *Generator) AddTmpfsMount(dest string, options []string) {
	mnt := rspec.Mount{
		Destination: dest,
		Type:        "tmpfs",
		Source:      "tmpfs",
		Options:     options,
	}

	g.initSpec()
	g.spec.Mounts = append(g.spec.Mounts, mnt)
}

// AddCgroupsMount adds a cgroup mount into g.spec.Mounts.
func (g *Generator) AddCgroupsMount(mountCgroupOption string) error {
	switch mountCgroupOption {
	case "ro":
	case "rw":
	case "no":
		return nil
	default:
		return fmt.Errorf("--mount-cgroups should be one of (ro,rw,no)")
	}

	mnt := rspec.Mount{
		Destination: "/sys/fs/cgroup",
		Type:        "cgroup",
		Source:      "cgroup",
		Options:     []string{"nosuid", "noexec", "nodev", "relatime", mountCgroupOption},
	}
	g.initSpec()
	g.spec.Mounts = append(g.spec.Mounts, mnt)

	return nil
}

// AddBindMount adds a bind mount into g.spec.Mounts.
func (g *Generator) AddBindMount(source, dest string, options []string) {
	if len(options) == 0 {
		options = []string{"rw"}
	}

	// We have to make sure that there is a bind option set, otherwise it won't
	// be an actual bindmount.
	foundBindOption := false
	for _, opt := range options {
		if opt == "bind" || opt == "rbind" {
			foundBindOption = true
			break
		}
	}
	if !foundBindOption {
		options = append(options, "bind")
	}

	mnt := rspec.Mount{
		Destination: dest,
		Type:        "bind",
		Source:      source,
		Options:     options,
	}
	g.initSpec()
	g.spec.Mounts = append(g.spec.Mounts, mnt)
}

// SetupPrivileged sets up the privilege-related fields inside g.spec.
func (g *Generator) SetupPrivileged(privileged bool) {
	if privileged { // Add all capabilities in privileged mode.
		var finalCapList []string
		for _, cap := range capability.List() {
			if g.HostSpecific && cap > validate.LastCap() {
				continue
			}
			finalCapList = append(finalCapList, fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())))
		}
		g.initSpecLinux()
		g.initSpecProcessCapabilities()
		g.spec.Process.Capabilities.Bounding = finalCapList
		g.spec.Process.Capabilities.Effective = finalCapList
		g.spec.Process.Capabilities.Inheritable = finalCapList
		g.spec.Process.Capabilities.Permitted = finalCapList
		g.spec.Process.Capabilities.Ambient = finalCapList
		g.spec.Process.SelinuxLabel = ""
		g.spec.Process.ApparmorProfile = ""
		g.spec.Linux.Seccomp = nil
	}
}

// ClearProcessCapabilities clear g.spec.Process.Capabilities.
func (g *Generator) ClearProcessCapabilities() {
	if g.spec == nil {
		return
	}
	g.spec.Process.Capabilities.Bounding = []string{}
	g.spec.Process.Capabilities.Effective = []string{}
	g.spec.Process.Capabilities.Inheritable = []string{}
	g.spec.Process.Capabilities.Permitted = []string{}
	g.spec.Process.Capabilities.Ambient = []string{}
}

// AddProcessCapability adds a process capability into g.spec.Process.Capabilities.
func (g *Generator) AddProcessCapability(c string) error {
	cp := strings.ToUpper(c)
	if err := validate.CapValid(cp, g.HostSpecific); err != nil {
		return err
	}

	g.initSpecProcessCapabilities()

	for _, cap := range g.spec.Process.Capabilities.Bounding {
		if strings.ToUpper(cap) == cp {
			return nil
		}
	}
	g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp)

	for _, cap := range g.spec.Process.Capabilities.Effective {
		if strings.ToUpper(cap) == cp {
			return nil
		}
	}
	g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp)

	for _, cap := range g.spec.Process.Capabilities.Inheritable {
		if strings.ToUpper(cap) == cp {
			return nil
		}
	}
	g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp)

	for _, cap := range g.spec.Process.Capabilities.Permitted {
		if strings.ToUpper(cap) == cp {
			return nil
		}
	}
	g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp)

	for _, cap := range g.spec.Process.Capabilities.Ambient {
		if strings.ToUpper(cap) == cp {
			return nil
		}
	}
	g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp)

	return nil
}

// DropProcessCapability drops a process capability from g.spec.Process.Capabilities.
func (g *Generator) DropProcessCapability(c string) error {
	cp := strings.ToUpper(c)
	if err := validate.CapValid(cp, g.HostSpecific); err != nil {
		return err
	}

	g.initSpecProcessCapabilities()

	for i, cap := range g.spec.Process.Capabilities.Bounding {
		if strings.ToUpper(cap) == cp {
			g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...)
		}
	}

	for i, cap := range g.spec.Process.Capabilities.Effective {
		if strings.ToUpper(cap) == cp {
			g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...)
		}
	}

	for i, cap := range g.spec.Process.Capabilities.Inheritable {
		if strings.ToUpper(cap) == cp {
			g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...)
		}
	}

	for i, cap := range g.spec.Process.Capabilities.Permitted {
		if strings.ToUpper(cap) == cp {
			g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...)
		}
	}

	for i, cap := range g.spec.Process.Capabilities.Ambient {
		if strings.ToUpper(cap) == cp {
			g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...)
		}
	}

	return nil
}

func mapStrToNamespace(ns string, path string) (rspec.LinuxNamespace, error) {
	switch ns {
	case "network":
		return rspec.LinuxNamespace{Type: rspec.NetworkNamespace, Path: path}, nil
	case "pid":
		return rspec.LinuxNamespace{Type: rspec.PIDNamespace, Path: path}, nil
	case "mount":
		return rspec.LinuxNamespace{Type: rspec.MountNamespace, Path: path}, nil
	case "ipc":
		return rspec.LinuxNamespace{Type: rspec.IPCNamespace, Path: path}, nil
	case "uts":
		return rspec.LinuxNamespace{Type: rspec.UTSNamespace, Path: path}, nil
	case "user":
		return rspec.LinuxNamespace{Type: rspec.UserNamespace, Path: path}, nil
	case "cgroup":
		return rspec.LinuxNamespace{Type: rspec.CgroupNamespace, Path: path}, nil
	default:
		return rspec.LinuxNamespace{}, fmt.Errorf("unrecognized namespace %q", ns)
	}
}

// ClearLinuxNamespaces clear g.spec.Linux.Namespaces.
func (g *Generator) ClearLinuxNamespaces() {
	if g.spec == nil || g.spec.Linux == nil {
		return
	}
	g.spec.Linux.Namespaces = []rspec.LinuxNamespace{}
}

// AddOrReplaceLinuxNamespace adds or replaces a namespace inside
// g.spec.Linux.Namespaces.
func (g *Generator) AddOrReplaceLinuxNamespace(ns string, path string) error {
	namespace, err := mapStrToNamespace(ns, path)
	if err != nil {
		return err
	}

	g.initSpecLinux()
	for i, ns := range g.spec.Linux.Namespaces {
		if ns.Type == namespace.Type {
			g.spec.Linux.Namespaces[i] = namespace
			return nil
		}
	}
	g.spec.Linux.Namespaces = append(g.spec.Linux.Namespaces, namespace)
	return nil
}

// RemoveLinuxNamespace removes a namespace from g.spec.Linux.Namespaces.
func (g *Generator) RemoveLinuxNamespace(ns string) error {
	namespace, err := mapStrToNamespace(ns, "")
	if err != nil {
		return err
	}

	if g.spec == nil || g.spec.Linux == nil {
		return nil
	}
	for i, ns := range g.spec.Linux.Namespaces {
		if ns.Type == namespace.Type {
			g.spec.Linux.Namespaces = append(g.spec.Linux.Namespaces[:i], g.spec.Linux.Namespaces[i+1:]...)
			return nil
		}
	}
	return nil
}

// AddDevice - add a device into g.spec.Linux.Devices
func (g *Generator) AddDevice(device rspec.LinuxDevice) {
	g.initSpecLinux()

	for i, dev := range g.spec.Linux.Devices {
		if dev.Path == device.Path {
			g.spec.Linux.Devices[i] = device
			return
		}
		if dev.Type == device.Type && dev.Major == device.Major && dev.Minor == device.Minor {
			fmt.Fprintln(os.Stderr, "WARNING: The same type, major and minor should not be used for multiple devices.")
		}
	}

	g.spec.Linux.Devices = append(g.spec.Linux.Devices, device)
}

// RemoveDevice remove a device from g.spec.Linux.Devices
func (g *Generator) RemoveDevice(path string) error {
	if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Devices == nil {
		return nil
	}

	for i, device := range g.spec.Linux.Devices {
		if device.Path == path {
			g.spec.Linux.Devices = append(g.spec.Linux.Devices[:i], g.spec.Linux.Devices[i+1:]...)
			return nil
		}
	}
	return nil
}

// ClearLinuxDevices clears g.spec.Linux.Devices
func (g *Generator) ClearLinuxDevices() {
	if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Devices == nil {
		return
	}

	g.spec.Linux.Devices = []rspec.LinuxDevice{}
}

// strPtr returns the pointer pointing to the string s.
func strPtr(s string) *string { return &s }

// SetSyscallAction adds rules for syscalls with the specified action
func (g *Generator) SetSyscallAction(arguments seccomp.SyscallOpts) error {
	g.initSpecLinuxSeccomp()
	return seccomp.ParseSyscallFlag(arguments, g.spec.Linux.Seccomp)
}

// SetDefaultSeccompAction sets the default action for all syscalls not defined
// and then removes any syscall rules with this action already specified.
func (g *Generator) SetDefaultSeccompAction(action string) error {
	g.initSpecLinuxSeccomp()
	return seccomp.ParseDefaultAction(action, g.spec.Linux.Seccomp)
}

// SetDefaultSeccompActionForce only sets the default action for all syscalls not defined
func (g *Generator) SetDefaultSeccompActionForce(action string) error {
	g.initSpecLinuxSeccomp()
	return seccomp.ParseDefaultActionForce(action, g.spec.Linux.Seccomp)
}

// SetSeccompArchitecture sets the supported seccomp architectures
func (g *Generator) SetSeccompArchitecture(architecture string) error {
	g.initSpecLinuxSeccomp()
	return seccomp.ParseArchitectureFlag(architecture, g.spec.Linux.Seccomp)
}

// RemoveSeccompRule removes rules for any specified syscalls
func (g *Generator) RemoveSeccompRule(arguments string) error {
	g.initSpecLinuxSeccomp()
	return seccomp.RemoveAction(arguments, g.spec.Linux.Seccomp)
}

// RemoveAllSeccompRules removes all syscall rules
func (g *Generator) RemoveAllSeccompRules() error {
	g.initSpecLinuxSeccomp()
	return seccomp.RemoveAllSeccompRules(g.spec.Linux.Seccomp)
}

// AddLinuxMaskedPaths adds masked paths into g.spec.Linux.MaskedPaths.
func (g *Generator) AddLinuxMaskedPaths(path string) {
	g.initSpecLinux()
	g.spec.Linux.MaskedPaths = append(g.spec.Linux.MaskedPaths, path)
}

// AddLinuxReadonlyPaths adds readonly paths into g.spec.Linux.MaskedPaths.
func (g *Generator) AddLinuxReadonlyPaths(path string) {
	g.initSpecLinux()
	g.spec.Linux.ReadonlyPaths = append(g.spec.Linux.ReadonlyPaths, path)
}
