/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * This file is part of libmount from util-linux project.
 *
 * Copyright (C) 2014-2025 Karel Zak <kzak@redhat.com>
 *
 * libmount is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * libmount kernel mount table monitor (based on /proc/self/mountinfo epoll)
 */

#include "mountP.h"
#include "monitor.h"

#include "pathnames.h"

#include <sys/epoll.h>

static int mountinfo_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)),
				   struct monitor_entry *me)
{
	assert(me);

	if (me->fd >= 0)
		close(me->fd);
	me->fd = -1;
	return 0;
}

static int mountinfo_get_fd(struct libmnt_monitor *mn,
				 struct monitor_entry *me)
{
	int rc;

	if (!me || me->enabled == 0)	/* not-initialized or disabled */
		return -EINVAL;
	if (me->fd >= 0)
		return me->fd;		/* already initialized */

	assert(me->path);
	DBG(MONITOR, ul_debugobj(mn, " open kernel monitor for %s", me->path));

	me->fd = open(me->path, O_RDONLY|O_CLOEXEC);
	if (me->fd < 0)
		goto err;

	return me->fd;
err:
	rc = -errno;
	DBG(MONITOR, ul_debugobj(mn, "failed to create kernel  monitor [rc=%d]", rc));
	return rc;
}

/* Returns: <0 error; 0 success; 1 nothing */
static int mountinfo_process_event(struct libmnt_monitor *mn,
			        struct monitor_entry *me)
{
	if (!mn || !me || me->fd < 0)
		return -EINVAL;

	if (mn->kernel_veiled && access(MNT_PATH_UTAB ".act", F_OK) == 0) {
		DBG(MONITOR, ul_debugobj(mn, "kernel event veiled"));
		return 1;
	}
	return 0;
}

/*
 * kernel monitor operations
 */
static const struct monitor_opers mountinfo_opers = {
	.op_get_fd		= mountinfo_get_fd,
	.op_close_fd		= mountinfo_close_fd,
	.op_process_event	= mountinfo_process_event
};

/**
 * mnt_monitor_enable_mountinfo:
 * @mn: monitor
 * @enable: 0 or 1
 *
 * Enables or disables kernel VFS monitoring based on /proc/self/mountinfo. If
 * the monitor does not exist and enable=1 then allocates new resources. This
 * monitor only informs that any change has been made to the kernel mount
 * table, but does not return details on what was changed and how.  See also
 * mnt_monitor_enable_fanotify().
 *
 * If the monitor does not exist and enable=1 then allocates new resources
 * necessary for the monitor.
 *
 * If the top-level monitor has been already created (by mnt_monitor_get_fd()
 * or mnt_monitor_wait()) then it's updated according to @enable.
 *
 * Return: 0 on success and <0 on error
 *
 * Since: v2.42
 */
int mnt_monitor_enable_mountinfo(struct libmnt_monitor *mn, int enable)
{
	struct monitor_entry *me;
	int rc = 0;

	if (!mn)
		return -EINVAL;

	me = monitor_get_entry(mn, MNT_MONITOR_TYPE_MOUNTINFO, -1);
	if (me) {
		rc = monitor_modify_epoll(mn, me, enable);
		if (!enable)
			mountinfo_close_fd(mn, me);
		return rc;
	}
	if (!enable)
		return 0;

	DBG(MONITOR, ul_debugobj(mn, "allocate new kernel monitor"));

	/* create a new entry */
	me = monitor_new_entry(mn);
	if (!me)
		goto err;

	/* If you want to use epoll FD in another epoll then top level
	 * epoll_wait() will drain all events from low-level FD if the
	 * low-level FD is not added with EPOLLIN. It means without EPOLLIN it
	 * it's impossible to detect which low-level FD has been active.
	 *
	 * Unfortunately, use EPOLLIN for mountinfo is tricky because in this
	 * case kernel returns events all time (we don't read from the FD).
	 * The solution is to use also edge-triggered (EPOLLET) flag, then
	 * kernel generate events on mountinfo changes only. The disadvantage is
	 * that we have to drain initial event generated by EPOLLIN after
	 * epoll_ctl(ADD). See monitor_modify_epoll().
	 */
	me->events = EPOLLIN | EPOLLET;

	me->type = MNT_MONITOR_TYPE_MOUNTINFO;
	me->opers = &mountinfo_opers;
	me->path = strdup(_PATH_PROC_MOUNTINFO);
	if (!me->path)
		goto err;

	return monitor_modify_epoll(mn, me, TRUE);
err:
	rc = -errno;
	free_monitor_entry(me);
	DBG(MONITOR, ul_debugobj(mn, "failed to allocate kernel monitor [rc=%d]", rc));
	return rc;
}

/**
 * mnt_monitor_enable_kernel:
 * @mn: monitor
 * @enable: 0 or 1
 *
 * Deprecated alias to mnt_monitor_enable_mountinfo().
 *
 * Return: 0 on success and <0 on error
 */
int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable)
		 __attribute__((alias("mnt_monitor_enable_mountinfo")));

/**
 * mnt_monitor_veil_kernel:
 * @mn: monitor instance
 * @enable: 1 or 0
 *
 * Force monitor to ignore kernel events if the same mount/umount operation
 * will generate an userspace event later. The kernel-only mount operation will
 * be not affected.
 *
 * Return: 0 on success and <0 on error.
 *
 * Since: 2.40
 */
int mnt_monitor_veil_kernel(struct libmnt_monitor *mn, int enable)
{
	if (!mn)
		return -EINVAL;

	mn->kernel_veiled = enable ? 1 : 0;
	return 0;
}
