/*	$NetBSD: sys_eventfd.c,v 1.11 2023/11/19 17:16:00 riastradh Exp $	*/

/*-
 * Copyright (c) 2020 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_eventfd.c,v 1.11 2023/11/19 17:16:00 riastradh Exp $");

/*
 * eventfd
 *
 * Eventfd objects present a simple counting object associated with a
 * file descriptor.  Writes and reads to this file descriptor increment
 * and decrement the count, respectively.  When the count is non-zero,
 * the descriptor is considered "readable", and when less than the max
 * value (EVENTFD_MAXVAL), is considered "writable".
 *
 * This implementation is API compatible with the Linux eventfd(2)
 * interface.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/condvar.h>
#include <sys/eventfd.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/kauth.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/syscallargs.h>
#include <sys/uio.h>

struct eventfd {
	kmutex_t	efd_lock;
	kcondvar_t	efd_read_wait;
	kcondvar_t	efd_write_wait;
	struct selinfo	efd_read_sel;
	struct selinfo	efd_write_sel;
	eventfd_t	efd_val;
	int64_t		efd_nwaiters;
	bool		efd_restarting;
	bool		efd_is_semaphore;

	/*
	 * Information kept for stat(2).
	 */
	struct timespec efd_btime;	/* time created */
	struct timespec	efd_mtime;	/* last write */
	struct timespec	efd_atime;	/* last read */
};

#define	EVENTFD_MAXVAL	(UINT64_MAX - 1)

/*
 * eventfd_create:
 *
 *	Create an eventfd object.
 */
static struct eventfd *
eventfd_create(unsigned int const val, int const flags)
{
	struct eventfd * const efd = kmem_zalloc(sizeof(*efd), KM_SLEEP);

	mutex_init(&efd->efd_lock, MUTEX_DEFAULT, IPL_NONE);
	cv_init(&efd->efd_read_wait, "efdread");
	cv_init(&efd->efd_write_wait, "efdwrite");
	selinit(&efd->efd_read_sel);
	selinit(&efd->efd_write_sel);
	efd->efd_val = val;
	efd->efd_is_semaphore = !!(flags & EFD_SEMAPHORE);
	getnanotime(&efd->efd_btime);

	/* Caller deals with EFD_CLOEXEC and EFD_NONBLOCK. */

	return efd;
}

/*
 * eventfd_destroy:
 *
 *	Destroy an eventfd object.
 */
static void
eventfd_destroy(struct eventfd * const efd)
{

	KASSERT(efd->efd_nwaiters == 0);

	cv_destroy(&efd->efd_read_wait);
	cv_destroy(&efd->efd_write_wait);

	seldestroy(&efd->efd_read_sel);
	seldestroy(&efd->efd_write_sel);

	mutex_destroy(&efd->efd_lock);

	kmem_free(efd, sizeof(*efd));
}

/*
 * eventfd_wait:
 *
 *	Block on an eventfd.  Handles non-blocking, as well as
 *	the restart cases.
 */
static int
eventfd_wait(struct eventfd * const efd, int const fflag, bool const is_write)
{
	kcondvar_t *waitcv;
	int error;

	if (fflag & FNONBLOCK) {
		return EAGAIN;
	}

	/*
	 * We're going to block.  Check if we need to return ERESTART.
	 */
	if (efd->efd_restarting) {
		return ERESTART;
	}

	if (is_write) {
		waitcv = &efd->efd_write_wait;
	} else {
		waitcv = &efd->efd_read_wait;
	}

	efd->efd_nwaiters++;
	KASSERT(efd->efd_nwaiters > 0);
	error = cv_wait_sig(waitcv, &efd->efd_lock);
	efd->efd_nwaiters--;
	KASSERT(efd->efd_nwaiters >= 0);

	/*
	 * If a restart was triggered while we were asleep, we need
	 * to return ERESTART if no other error was returned.
	 */
	if (efd->efd_restarting) {
		if (error == 0) {
			error = ERESTART;
		}
	}

	return error;
}

/*
 * eventfd_wake:
 *
 *	Wake LWPs block on an eventfd.
 */
static void
eventfd_wake(struct eventfd * const efd, bool const is_write)
{
	kcondvar_t *waitcv = NULL;
	struct selinfo *sel;
	int pollev;

	if (is_write) {
		waitcv = &efd->efd_read_wait;
		sel = &efd->efd_read_sel;
		pollev = POLLIN | POLLRDNORM;
	} else {
		waitcv = &efd->efd_write_wait;
		sel = &efd->efd_write_sel;
		pollev = POLLOUT | POLLWRNORM;
	}
	cv_broadcast(waitcv);
	selnotify(sel, pollev, NOTE_SUBMIT);
}

/*
 * eventfd file operations
 */

static int
eventfd_fop_read(file_t * const fp, off_t * const offset,
    struct uio * const uio, kauth_cred_t const cred, int const flags)
{
	struct eventfd * const efd = fp->f_eventfd;
	int const fflag = fp->f_flag;
	eventfd_t return_value;
	int error;

	if (uio->uio_resid < sizeof(eventfd_t)) {
		return EINVAL;
	}

	mutex_enter(&efd->efd_lock);

	while (efd->efd_val == 0) {
		if ((error = eventfd_wait(efd, fflag, false)) != 0) {
			mutex_exit(&efd->efd_lock);
			return error;
		}
	}

	if (efd->efd_is_semaphore) {
		return_value = 1;
		efd->efd_val--;
	} else {
		return_value = efd->efd_val;
		efd->efd_val = 0;
	}

	getnanotime(&efd->efd_atime);
	eventfd_wake(efd, false);

	mutex_exit(&efd->efd_lock);

	error = uiomove(&return_value, sizeof(return_value), uio);

	return error;
}

static int
eventfd_fop_write(file_t * const fp, off_t * const offset,
    struct uio * const uio, kauth_cred_t const cred, int const flags)
{
	struct eventfd * const efd = fp->f_eventfd;
	int const fflag = fp->f_flag;
	eventfd_t write_value;
	int error;

	if (uio->uio_resid < sizeof(eventfd_t)) {
		return EINVAL;
	}

	if ((error = uiomove(&write_value, sizeof(write_value), uio)) != 0) {
		return error;
	}

	if (write_value > EVENTFD_MAXVAL) {
		error = EINVAL;
		goto out;
	}

	mutex_enter(&efd->efd_lock);

	KASSERT(efd->efd_val <= EVENTFD_MAXVAL);
	while ((EVENTFD_MAXVAL - efd->efd_val) < write_value) {
		if ((error = eventfd_wait(efd, fflag, true)) != 0) {
			mutex_exit(&efd->efd_lock);
			goto out;
		}
	}

	efd->efd_val += write_value;
	KASSERT(efd->efd_val <= EVENTFD_MAXVAL);

	getnanotime(&efd->efd_mtime);
	eventfd_wake(efd, true);

	mutex_exit(&efd->efd_lock);

 out:
	if (error) {
		/*
		 * Undo the effect of uiomove() so that the error
		 * gets reported correctly; see dofilewrite().
		 */
		uio->uio_resid += sizeof(write_value);
	}
	return error;
}

static int
eventfd_ioctl(file_t * const fp, u_long const cmd, void * const data)
{
	struct eventfd * const efd = fp->f_eventfd;

	switch (cmd) {
	case FIONBIO:
		return 0;
	
	case FIONREAD:
		mutex_enter(&efd->efd_lock);
		*(int *)data = efd->efd_val != 0 ? sizeof(eventfd_t) : 0;
		mutex_exit(&efd->efd_lock);
		return 0;
	
	case FIONWRITE:
		*(int *)data = 0;
		return 0;

	case FIONSPACE:
		/*
		 * FIONSPACE doesn't really work for eventfd, because the
		 * writability depends on the contents (value) being written.
		 */
		break;

	default:
		break;
	}

	return EPASSTHROUGH;
}

static int
eventfd_fop_poll(file_t * const fp, int const events)
{
	struct eventfd * const efd = fp->f_eventfd;
	int revents = 0;

	/*
	 * Note that Linux will return POLLERR if the eventfd count
	 * overflows, but that is not possible in the normal read/write
	 * API, only with Linux kernel-internal interfaces.  So, this
	 * implementation never returns POLLERR.
	 *
	 * Also note that the Linux eventfd(2) man page does not
	 * specifically discuss returning POLLRDNORM, but we check
	 * for that event in addition to POLLIN.
	 */

	mutex_enter(&efd->efd_lock);

	if (events & (POLLIN | POLLRDNORM)) {
		if (efd->efd_val != 0) {
			revents |= events & (POLLIN | POLLRDNORM);
		} else {
			selrecord(curlwp, &efd->efd_read_sel);
		}
	}

	if (events & (POLLOUT | POLLWRNORM)) {
		if (efd->efd_val < EVENTFD_MAXVAL) {
			revents |= events & (POLLOUT | POLLWRNORM);
		} else {
			selrecord(curlwp, &efd->efd_write_sel);
		}
	}

	mutex_exit(&efd->efd_lock);

	return revents;
}

static int
eventfd_fop_stat(file_t * const fp, struct stat * const st)
{
	struct eventfd * const efd = fp->f_eventfd;

	memset(st, 0, sizeof(*st));

	mutex_enter(&efd->efd_lock);
	st->st_size = (off_t)efd->efd_val;
	st->st_blksize = sizeof(eventfd_t);
	st->st_mode = S_IFIFO | S_IRUSR | S_IWUSR;
	st->st_blocks = 1;
	st->st_birthtimespec = st->st_ctimespec = efd->efd_btime;
	st->st_atimespec = efd->efd_atime;
	st->st_mtimespec = efd->efd_mtime;
	st->st_uid = kauth_cred_geteuid(fp->f_cred);
	st->st_gid = kauth_cred_getegid(fp->f_cred);
	mutex_exit(&efd->efd_lock);

	return 0;
}

static int
eventfd_fop_close(file_t * const fp)
{
	struct eventfd * const efd = fp->f_eventfd;

	fp->f_eventfd = NULL;
	eventfd_destroy(efd);

	return 0;
}

static void
eventfd_filt_read_detach(struct knote * const kn)
{
	struct eventfd * const efd = ((file_t *)kn->kn_obj)->f_eventfd;

	mutex_enter(&efd->efd_lock);
	KASSERT(kn->kn_hook == efd);
	selremove_knote(&efd->efd_read_sel, kn);
	mutex_exit(&efd->efd_lock);
}

static int
eventfd_filt_read(struct knote * const kn, long const hint)
{
	struct eventfd * const efd = ((file_t *)kn->kn_obj)->f_eventfd;
	int rv;

	if (hint & NOTE_SUBMIT) {
		KASSERT(mutex_owned(&efd->efd_lock));
	} else {
		mutex_enter(&efd->efd_lock);
	}

	kn->kn_data = (int64_t)efd->efd_val;
	rv = (eventfd_t)kn->kn_data > 0;

	if ((hint & NOTE_SUBMIT) == 0) {
		mutex_exit(&efd->efd_lock);
	}

	return rv;
}

static const struct filterops eventfd_read_filterops = {
	.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
	.f_detach = eventfd_filt_read_detach,
	.f_event = eventfd_filt_read,
};

static void
eventfd_filt_write_detach(struct knote * const kn)
{
	struct eventfd * const efd = ((file_t *)kn->kn_obj)->f_eventfd;

	mutex_enter(&efd->efd_lock);
	KASSERT(kn->kn_hook == efd);
	selremove_knote(&efd->efd_write_sel, kn);
	mutex_exit(&efd->efd_lock);
}

static int
eventfd_filt_write(struct knote * const kn, long const hint)
{
	struct eventfd * const efd = ((file_t *)kn->kn_obj)->f_eventfd;
	int rv;

	if (hint & NOTE_SUBMIT) {
		KASSERT(mutex_owned(&efd->efd_lock));
	} else {
		mutex_enter(&efd->efd_lock);
	}

	kn->kn_data = (int64_t)efd->efd_val;
	rv = (eventfd_t)kn->kn_data < EVENTFD_MAXVAL;

	if ((hint & NOTE_SUBMIT) == 0) {
		mutex_exit(&efd->efd_lock);
	}

	return rv;
}

static const struct filterops eventfd_write_filterops = {
	.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
	.f_detach = eventfd_filt_write_detach,
	.f_event = eventfd_filt_write,
};

static int
eventfd_fop_kqfilter(file_t * const fp, struct knote * const kn)
{
	struct eventfd * const efd = ((file_t *)kn->kn_obj)->f_eventfd;
	struct selinfo *sel;

	switch (kn->kn_filter) {
	case EVFILT_READ:
		sel = &efd->efd_read_sel;
		kn->kn_fop = &eventfd_read_filterops;
		break;

	case EVFILT_WRITE:
		sel = &efd->efd_write_sel;
		kn->kn_fop = &eventfd_write_filterops;
		break;

	default:
		return EINVAL;
	}

	kn->kn_hook = efd;

	mutex_enter(&efd->efd_lock);
	selrecord_knote(sel, kn);
	mutex_exit(&efd->efd_lock);

	return 0;
}

static void
eventfd_fop_restart(file_t * const fp)
{
	struct eventfd * const efd = fp->f_eventfd;

	/*
	 * Unblock blocked reads/writes in order to allow close() to complete.
	 * System calls return ERESTART so that the fd is revalidated.
	 */

	mutex_enter(&efd->efd_lock);

	if (efd->efd_nwaiters != 0) {
		efd->efd_restarting = true;
		cv_broadcast(&efd->efd_read_wait);
		cv_broadcast(&efd->efd_write_wait);
	}

	mutex_exit(&efd->efd_lock);
}

static const struct fileops eventfd_fileops = {
	.fo_name = "eventfd",
	.fo_read = eventfd_fop_read,
	.fo_write = eventfd_fop_write,
	.fo_ioctl = eventfd_ioctl,
	.fo_fcntl = fnullop_fcntl,
	.fo_poll = eventfd_fop_poll,
	.fo_stat = eventfd_fop_stat,
	.fo_close = eventfd_fop_close,
	.fo_kqfilter = eventfd_fop_kqfilter,
	.fo_restart = eventfd_fop_restart,
};

/*
 * eventfd(2) system call
 */
int
do_eventfd(struct lwp * const l, unsigned int const val, int const flags,
    register_t *retval)
{
	file_t *fp;
	int fd, error;

	if (flags & ~(EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE)) {
		return EINVAL;
	}

	if ((error = fd_allocfile(&fp, &fd)) != 0) {
		return error;
	}

	fp->f_flag = FREAD | FWRITE;
	if (flags & EFD_NONBLOCK) {
		fp->f_flag |= FNONBLOCK;
	}
	fp->f_type = DTYPE_EVENTFD;
	fp->f_ops = &eventfd_fileops;
	fp->f_eventfd = eventfd_create(val, flags);
	fd_set_exclose(l, fd, !!(flags & EFD_CLOEXEC));
	fd_affix(curproc, fp, fd);

	*retval = fd;
	return 0;
}

int
sys_eventfd(struct lwp *l, const struct sys_eventfd_args *uap,
    register_t *retval)
{
	/* {
		syscallarg(unsigned int) val;
		syscallarg(int) flags;
	} */

	return do_eventfd(l, SCARG(uap, val), SCARG(uap, flags), retval);
}