// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package linux

import (
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/sentry/arch"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/kernel/epoll"
	"gvisor.dev/gvisor/pkg/sentry/syscalls"
	"gvisor.dev/gvisor/pkg/syserror"
	"gvisor.dev/gvisor/pkg/waiter"
)

// LINT.IfChange

// EpollCreate1 implements the epoll_create1(2) linux syscall.
func EpollCreate1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	flags := args[0].Int()
	if flags & ^linux.EPOLL_CLOEXEC != 0 {
		return 0, nil, syserror.EINVAL
	}

	closeOnExec := flags&linux.EPOLL_CLOEXEC != 0
	fd, err := syscalls.CreateEpoll(t, closeOnExec)
	if err != nil {
		return 0, nil, err
	}

	return uintptr(fd), nil, nil
}

// EpollCreate implements the epoll_create(2) linux syscall.
func EpollCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	size := args[0].Int()

	if size <= 0 {
		return 0, nil, syserror.EINVAL
	}

	fd, err := syscalls.CreateEpoll(t, false)
	if err != nil {
		return 0, nil, err
	}

	return uintptr(fd), nil, nil
}

// EpollCtl implements the epoll_ctl(2) linux syscall.
func EpollCtl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	epfd := args[0].Int()
	op := args[1].Int()
	fd := args[2].Int()
	eventAddr := args[3].Pointer()

	// Capture the event state if needed.
	flags := epoll.EntryFlags(0)
	mask := waiter.EventMask(0)
	var data [2]int32
	if op != linux.EPOLL_CTL_DEL {
		var e linux.EpollEvent
		if _, err := e.CopyIn(t, eventAddr); err != nil {
			return 0, nil, err
		}

		if e.Events&linux.EPOLLONESHOT != 0 {
			flags |= epoll.OneShot
		}

		if e.Events&linux.EPOLLET != 0 {
			flags |= epoll.EdgeTriggered
		}

		mask = waiter.EventMaskFromLinux(e.Events)
		data = e.Data
	}

	// Perform the requested operations.
	switch op {
	case linux.EPOLL_CTL_ADD:
		// See fs/eventpoll.c.
		mask |= waiter.EventHUp | waiter.EventErr
		return 0, nil, syscalls.AddEpoll(t, epfd, fd, flags, mask, data)
	case linux.EPOLL_CTL_DEL:
		return 0, nil, syscalls.RemoveEpoll(t, epfd, fd)
	case linux.EPOLL_CTL_MOD:
		// Same as EPOLL_CTL_ADD.
		mask |= waiter.EventHUp | waiter.EventErr
		return 0, nil, syscalls.UpdateEpoll(t, epfd, fd, flags, mask, data)
	default:
		return 0, nil, syserror.EINVAL
	}
}

// EpollWait implements the epoll_wait(2) linux syscall.
func EpollWait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	epfd := args[0].Int()
	eventsAddr := args[1].Pointer()
	maxEvents := int(args[2].Int())
	timeout := int(args[3].Int())

	r, err := syscalls.WaitEpoll(t, epfd, maxEvents, timeout)
	if err != nil {
		return 0, nil, syserror.ConvertIntr(err, syserror.EINTR)
	}

	if len(r) != 0 {
		if _, err := linux.CopyEpollEventSliceOut(t, eventsAddr, r); err != nil {
			return 0, nil, err
		}
	}

	return uintptr(len(r)), nil, nil
}

// EpollPwait implements the epoll_pwait(2) linux syscall.
func EpollPwait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	maskAddr := args[4].Pointer()
	maskSize := uint(args[5].Uint())

	if maskAddr != 0 {
		mask, err := copyInSigSet(t, maskAddr, maskSize)
		if err != nil {
			return 0, nil, err
		}

		oldmask := t.SignalMask()
		t.SetSignalMask(mask)
		t.SetSavedSignalMask(oldmask)
	}

	return EpollWait(t, args)
}

// LINT.ThenChange(vfs2/epoll.go)