// 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 fasync provides FIOASYNC related functionality.
package fasync

import (
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/waiter"
)

// Table to convert waiter event masks into si_band siginfo codes.
// Taken from fs/fcntl.c:band_table.
var bandTable = map[waiter.EventMask]int64{
	// POLL_IN
	waiter.EventIn: linux.EPOLLIN | linux.EPOLLRDNORM,
	// POLL_OUT
	waiter.EventOut: linux.EPOLLOUT | linux.EPOLLWRNORM | linux.EPOLLWRBAND,
	// POLL_ERR
	waiter.EventErr: linux.EPOLLERR,
	// POLL_PRI
	waiter.EventPri: linux.EPOLLPRI | linux.EPOLLRDBAND,
	// POLL_HUP
	waiter.EventHUp: linux.EPOLLHUP | linux.EPOLLERR,
}

// New returns a function that creates a new vfs.FileAsync with the given
// file descriptor.
func New(fd int) func() vfs.FileAsync {
	return func() vfs.FileAsync {
		return &FileAsync{fd: fd}
	}
}

// FileAsync sends signals when the registered file is ready for IO.
//
// +stateify savable
type FileAsync struct {
	// e is immutable after first use (which is protected by mu below).
	e waiter.Entry

	// fd is the file descriptor to notify about.
	// It is immutable, set at allocation time. This matches Linux semantics in
	// fs/fcntl.c:fasync_helper.
	// The fd value is passed to the signal recipient in siginfo.si_fd.
	fd int

	// regMu protects registeration and unregistration actions on e.
	//
	// regMu must be held while registration decisions are being made
	// through the registration action itself.
	//
	// Lock ordering: regMu, mu.
	regMu regMutex `state:"nosave"`

	// mu protects all following fields.
	//
	// Lock ordering: e.mu, mu.
	mu         fileMutex `state:"nosave"`
	requester  *auth.Credentials
	registered bool
	// signal is the signal to deliver upon I/O being available.
	// The default value ("zero signal") means the default SIGIO signal will be
	// delivered.
	signal linux.Signal

	// Only one of the following is allowed to be non-nil.
	recipientPG *kernel.ProcessGroup
	recipientTG *kernel.ThreadGroup
	recipientT  *kernel.Task
}

// NotifyEvent implements waiter.EventListener.NotifyEvent.
func (a *FileAsync) NotifyEvent(mask waiter.EventMask) {
	a.mu.Lock()
	if !a.registered {
		a.mu.Unlock()
		return
	}
	// Read all the required fields which are lock protected from FileAsync
	// and release the lock.
	t := a.recipientT
	tg := a.recipientTG
	creds := a.requester
	sig := a.signal
	if a.recipientPG != nil {
		tg = a.recipientPG.Originator()
	}
	a.mu.Unlock()

	if tg != nil {
		t = tg.Leader()
	}
	if t == nil {
		// No recipient has been registered.
		return
	}
	tCreds := t.Credentials()
	// Logic from sigio_perm in fs/fcntl.c.
	permCheck := (creds.EffectiveKUID == 0 ||
		creds.EffectiveKUID == tCreds.SavedKUID ||
		creds.EffectiveKUID == tCreds.RealKUID ||
		creds.RealKUID == tCreds.SavedKUID ||
		creds.RealKUID == tCreds.RealKUID)
	if !permCheck {
		return
	}
	signalInfo := &linux.SignalInfo{
		Signo: int32(linux.SIGIO),
		Code:  linux.SI_KERNEL,
	}
	if sig != 0 {
		signalInfo.Signo = int32(sig)
		signalInfo.SetFD(uint32(a.fd))
		var band int64
		for m, bandCode := range bandTable {
			if m&mask != 0 {
				band |= bandCode
			}
		}
		signalInfo.SetBand(band)
	}
	if tg != nil {
		t.SendGroupSignal(signalInfo)
	} else {
		t.SendSignal(signalInfo)
	}
}

// Register sets the file which will be monitored for IO events.
//
// The file must not be currently registered.
func (a *FileAsync) Register(w waiter.Waitable) error {
	a.regMu.Lock()
	defer a.regMu.Unlock()
	a.mu.Lock()
	if a.registered {
		a.mu.Unlock()
		panic("registering already registered file")
	}
	a.e.Init(a, waiter.ReadableEvents|waiter.WritableEvents|waiter.EventErr|waiter.EventHUp)
	a.registered = true
	a.mu.Unlock()
	return w.EventRegister(&a.e)
}

// Unregister stops monitoring a file.
//
// The file must be currently registered.
func (a *FileAsync) Unregister(w waiter.Waitable) {
	a.regMu.Lock()
	defer a.regMu.Unlock()
	a.mu.Lock()

	if !a.registered {
		a.mu.Unlock()
		panic("unregistering unregistered file")
	}

	a.registered = false

	a.mu.Unlock()
	w.EventUnregister(&a.e)
}

// Owner returns who is currently getting signals. All return values will be
// nil if no one is set to receive signals.
func (a *FileAsync) Owner() (*kernel.Task, *kernel.ThreadGroup, *kernel.ProcessGroup) {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.recipientT, a.recipientTG, a.recipientPG
}

// SetOwnerTask sets the owner (who will receive signals) to a specified task.
// Only this owner will receive signals.
func (a *FileAsync) SetOwnerTask(requester *kernel.Task, recipient *kernel.Task) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.requester = requester.Credentials()
	a.recipientT = recipient
	a.recipientTG = nil
	a.recipientPG = nil
}

// SetOwnerThreadGroup sets the owner (who will receive signals) to a specified
// thread group. Only this owner will receive signals.
func (a *FileAsync) SetOwnerThreadGroup(requester *kernel.Task, recipient *kernel.ThreadGroup) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.requester = requester.Credentials()
	a.recipientT = nil
	a.recipientTG = recipient
	a.recipientPG = nil
}

// SetOwnerProcessGroup sets the owner (who will receive signals) to a
// specified process group. Only this owner will receive signals.
func (a *FileAsync) SetOwnerProcessGroup(requester *kernel.Task, recipient *kernel.ProcessGroup) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.requester = requester.Credentials()
	a.recipientT = nil
	a.recipientTG = nil
	a.recipientPG = recipient
}

// ClearOwner unsets the current signal recipient.
func (a *FileAsync) ClearOwner() {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.requester = nil
	a.recipientT = nil
	a.recipientTG = nil
	a.recipientPG = nil
}

// Signal returns which signal will be sent to the signal recipient.
// A value of zero means the signal to deliver wasn't customized, which means
// the default signal (SIGIO) will be delivered.
func (a *FileAsync) Signal() linux.Signal {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.signal
}

// SetSignal overrides which signal to send when I/O is available.
// The default behavior can be reset by specifying signal zero, which means
// to send SIGIO.
func (a *FileAsync) SetSignal(signal linux.Signal) error {
	if signal != 0 && !signal.IsValid() {
		return linuxerr.EINVAL
	}
	a.mu.Lock()
	defer a.mu.Unlock()
	a.signal = signal
	return nil
}
