/*
 * vdevadm monitor — listen for udev/kernel device events
 *
 * Derived from udevadm-monitor.c in eudev (systemd/eudev project).
 * Copyright (C) 2004-2010 Kay Sievers <kay@vrfy.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * vdev modifications:
 * Copyright (C) 2026  Aitor C.Z. <aitor_czr@gnuinos.org>
 *
 * Integrated into vdevadm so a single binary can replace udevadm on vdev
 * systems (see tools/vdevadm/README.md).  Uses libudev / libudev-compat for
 * the monitor transport (eventfs-backed "udev" source on vdev).
 */

#include "vdevadm.h"

#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <libudev.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define VDEVADM_MONITOR_MAX_MATCH 32
#define VDEVADM_MONITOR_NAME_SZ 256

static volatile sig_atomic_t monitor_exit;

static void monitor_sig_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM)
		monitor_exit = 1;
}

static void monitor_print_device(struct udev_device *device, const char *source,
								 bool prop)
{
	struct timespec ts;
	struct udev_list_entry *entry;
	const char *action;
	const char *devpath;
	const char *subsystem;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	action = udev_device_get_action(device);
	devpath = udev_device_get_devpath(device);
	subsystem = udev_device_get_subsystem(device);
	printf("%-6s[%" PRId64 ".%06ld] %-8s %s (%s)\n", source,
		   (int64_t)ts.tv_sec, ts.tv_nsec / 1000L,
		   action ? action : "(null)", devpath ? devpath : "(null)",
		   subsystem ? subsystem : "(null)");
	if (!prop)
		return;
	udev_list_entry_foreach(entry,
							udev_device_get_properties_list_entry(device))
		printf("%s=%s\n", udev_list_entry_get_name(entry),
			   udev_list_entry_get_value(entry));
	printf("\n");
}

static void monitor_usage(const char *prog)
{
	fprintf(stderr,
			"Usage: %s monitor [OPTIONS]\n\n"
			"Listen to kernel and udev events (libudev / libudev-compat).\n\n"
			"  -p --property            Print event properties\n"
			"  -k --kernel              Print kernel uevents\n"
			"  -u --udev                Print udev events (vdev eventfs on vdev)\n"
			"  -s --subsystem-match=S   Filter by subsystem[/devtype]\n"
			"  -t --tag-match=TAG       Filter by tag\n"
			"  -h --help                Show help\n",
			prog);
}

static int monitor_setup_udev(struct udev *udev, char subsys[][VDEVADM_MONITOR_NAME_SZ],
							char devtype[][VDEVADM_MONITOR_NAME_SZ], int n_sub,
							char tags[][VDEVADM_MONITOR_NAME_SZ], int n_tag,
							struct udev_monitor **mon_out, int *fd_out)
{
	struct udev_monitor *mon;
	int fd;
	int i;

	mon = udev_monitor_new_from_netlink(udev, "udev");
	if (!mon) {
		fprintf(stderr, "vdevadm monitor: cannot open udev monitor\n");
		return 1;
	}
	udev_monitor_set_receive_buffer_size(mon, 128 * 1024 * 1024);

	for (i = 0; i < n_sub; i++) {
		const char *dt = devtype[i][0] ? devtype[i] : NULL;

		if (udev_monitor_filter_add_match_subsystem_devtype(mon, subsys[i],
															dt) < 0) {
			fprintf(stderr,
					"vdevadm monitor: subsystem filter '%s' failed\n",
					subsys[i]);
			udev_monitor_unref(mon);
			return 1;
		}
	}
	for (i = 0; i < n_tag; i++) {
		if (udev_monitor_filter_add_match_tag(mon, tags[i]) < 0) {
			fprintf(stderr, "vdevadm monitor: tag filter '%s' failed\n",
					tags[i]);
			udev_monitor_unref(mon);
			return 1;
		}
	}
	udev_monitor_filter_update(mon);

	if (udev_monitor_enable_receiving(mon) < 0) {
		fprintf(stderr, "vdevadm monitor: cannot enable udev monitor\n");
		udev_monitor_unref(mon);
		return 1;
	}

	fd = udev_monitor_get_fd(mon);
	if (fd < 0) {
		udev_monitor_unref(mon);
		return 1;
	}

	*mon_out = mon;
	*fd_out = fd;
	return 0;
}

static int monitor_setup_kernel(struct udev *udev,
								char subsys[][VDEVADM_MONITOR_NAME_SZ], int n_sub,
								struct udev_monitor **mon_out, int *fd_out)
{
	struct udev_monitor *mon;
	int fd;
	int i;

	mon = udev_monitor_new_from_netlink(udev, "kernel");
	if (!mon) {
		fprintf(stderr, "vdevadm monitor: cannot open kernel monitor\n");
		return 1;
	}
	udev_monitor_set_receive_buffer_size(mon, 128 * 1024 * 1024);

	for (i = 0; i < n_sub; i++) {
		if (udev_monitor_filter_add_match_subsystem_devtype(mon, subsys[i],
															NULL) < 0) {
			fprintf(stderr,
					"vdevadm monitor: kernel filter '%s' failed\n",
					subsys[i]);
			udev_monitor_unref(mon);
			return 1;
		}
	}
	udev_monitor_filter_update(mon);

	if (udev_monitor_enable_receiving(mon) < 0) {
		fprintf(stderr, "vdevadm monitor: cannot enable kernel monitor\n");
		udev_monitor_unref(mon);
		return 1;
	}

	fd = udev_monitor_get_fd(mon);
	if (fd < 0) {
		udev_monitor_unref(mon);
		return 1;
	}

	*mon_out = mon;
	*fd_out = fd;
	return 0;
}

int vdevadm_monitor_main(struct vdevadm_ctx *ctx, int argc, char *argv[])
{
	static const struct option opts[] = {
		{ "property", no_argument, NULL, 'p' },
		{ "environment", no_argument, NULL, 'e' },
		{ "kernel", no_argument, NULL, 'k' },
		{ "udev", no_argument, NULL, 'u' },
		{ "subsystem-match", required_argument, NULL, 's' },
		{ "tag-match", required_argument, NULL, 't' },
		{ "help", no_argument, NULL, 'h' },
		{}
	};
	struct udev *udev = NULL;
	struct udev_monitor *udev_mon = NULL;
	struct udev_monitor *kernel_mon = NULL;
	struct sigaction act = { 0 };
	struct pollfd pfds[2];
	int nfds = 0;
	bool prop = false;
	bool print_kernel = false;
	bool print_udev = false;
	char subsys[VDEVADM_MONITOR_MAX_MATCH][VDEVADM_MONITOR_NAME_SZ];
	char devtype[VDEVADM_MONITOR_MAX_MATCH][VDEVADM_MONITOR_NAME_SZ];
	char tags[VDEVADM_MONITOR_MAX_MATCH][VDEVADM_MONITOR_NAME_SZ];
	int n_sub = 0;
	int n_tag = 0;
	int c;
	int rc = 0;

	(void)ctx;

	while ((c = getopt_long(argc, argv, "pekus:t:h", opts, NULL)) >= 0) {
		switch (c) {
		case 'p':
		case 'e':
			prop = true;
			break;
		case 'k':
			print_kernel = true;
			break;
		case 'u':
			print_udev = true;
			break;
		case 's': {
			char tmp[VDEVADM_MONITOR_NAME_SZ];
			char *slash;

			if (n_sub >= VDEVADM_MONITOR_MAX_MATCH) {
				fprintf(stderr, "vdevadm monitor: too many filters\n");
				return 1;
			}
			snprintf(tmp, sizeof(tmp), "%s", optarg);
			slash = strchr(tmp, '/');
			if (slash) {
				*slash++ = '\0';
				snprintf(devtype[n_sub], sizeof(devtype[n_sub]), "%s",
						 slash);
			} else {
				devtype[n_sub][0] = '\0';
			}
			snprintf(subsys[n_sub], sizeof(subsys[n_sub]), "%s", tmp);
			n_sub++;
			break;
		}
		case 't':
			if (n_tag >= VDEVADM_MONITOR_MAX_MATCH) {
				fprintf(stderr, "vdevadm monitor: too many tags\n");
				return 1;
			}
			snprintf(tags[n_tag], sizeof(tags[n_tag]), "%s", optarg);
			n_tag++;
			break;
		case 'h':
			monitor_usage(argv[0]);
			return 0;
		default:
			monitor_usage(argv[0]);
			return 1;
		}
	}

	if (!print_kernel && !print_udev) {
		print_kernel = true;
		print_udev = true;
	}

	act.sa_handler = monitor_sig_handler;
	act.sa_flags = SA_RESTART;
	sigaction(SIGINT, &act, NULL);
	sigaction(SIGTERM, &act, NULL);
	setvbuf(stdout, NULL, _IOLBF, 0);

	udev = udev_new();
	if (!udev) {
		fprintf(stderr, "vdevadm monitor: udev_new() failed\n");
		return 1;
	}

	printf("monitor will print the received events for:\n");

	if (print_udev) {
		if (monitor_setup_udev(udev, subsys, devtype, n_sub, tags, n_tag,
							   &udev_mon, &pfds[nfds].fd) != 0) {
			rc = 1;
			goto out;
		}
		pfds[nfds].events = POLLIN;
		nfds++;
		printf("UDEV - the event which vdev sends after rule processing\n");
	}

	if (print_kernel) {
		if (monitor_setup_kernel(udev, subsys, n_sub, &kernel_mon,
								 &pfds[nfds].fd) != 0) {
			rc = 1;
			goto out;
		}
		pfds[nfds].events = POLLIN;
		nfds++;
		printf("KERNEL - the kernel uevent\n");
	}
	printf("\n");

	while (!monitor_exit) {
		int i;
		int prc = poll(pfds, (nfds_t)nfds, -1);

		if (prc < 0) {
			if (errno != EINTR)
				fprintf(stderr, "vdevadm monitor: poll: %s\n",
						strerror(errno));
			continue;
		}

		for (i = 0; i < nfds; i++) {
			struct udev_monitor *mon = NULL;
			struct udev_device *dev;

			if (!(pfds[i].revents & POLLIN))
				continue;
			if (udev_mon && pfds[i].fd == udev_monitor_get_fd(udev_mon))
				mon = udev_mon;
			else if (kernel_mon &&
					 pfds[i].fd == udev_monitor_get_fd(kernel_mon))
				mon = kernel_mon;
			if (!mon)
				continue;
			dev = udev_monitor_receive_device(mon);
			if (!dev)
				continue;
			monitor_print_device(dev, mon == kernel_mon ? "KERNEL" : "UDEV",
								 prop);
			udev_device_unref(dev);
		}
	}

out:
	if (kernel_mon)
		udev_monitor_unref(kernel_mon);
	if (udev_mon)
		udev_monitor_unref(udev_mon);
	if (udev)
		udev_unref(udev);
	return rc;
}
