/*
 * vdevadm control — eudev-compatible /run/udev/control socket + vdev extras
 */

#include "vdevadm.h"

#include "libvdev/vdev_ctrl.h"

#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int g_ctrl_log_level;
static int g_ctrl_children_max;
static const char *g_ctrl_env_pair;
static const char *g_ctrl_promote_cfg;

static void control_usage(const char *prog)
{
	fprintf(stderr,
			"Usage: %s control OPTION\n"
			"\n"
			"Send administration commands to vdevd via the eudev-compatible\n"
			"control socket (%s), with signal fallbacks for vdev_settle.\n"
			"\n"
			"Options (eudev-compatible):\n"
			"  -e --exit                 Stop vdevd\n"
			"  -R --reload               Reload rules / configuration\n"
			"  -l --log-priority=LEVEL   Set daemon log level\n"
			"  -s --stop-exec-queue      Pause device event processing\n"
			"  -S --start-exec-queue     Resume device event processing\n"
			"  -p --property=KEY=VALUE   Set daemon environment variable\n"
			"      --env=KEY=VALUE       Alias for --property\n"
			"  -m --children-max=N       Set worker limit (stored; vdev uses one worker)\n"
			"      --timeout=SECONDS     Socket timeout (default: 60)\n"
			"\n"
			"Options (vdev-specific):\n"
			"      --promote[=CONF]      Promote early listener to long-running\n"
			"                            (CONF path/basename; default from JSON)\n"
			"      --print-pid           Print vdevd PID (SO_PEERCRED on control)\n"
			"      --refresh-settle       SIGUSR1 to vdev_settle\n"
			"      --reload-settle        SIGHUP to vdev_settle\n"
			"      --stop-settle          SIGTERM to vdev_settle\n"
			"      --pid-file=PATH        Fallback pid file for signals\n"
			"  -h --help                 Show help\n",
			prog, VDEV_CTRL_DEFAULT_PATH);
}

static int control_read_pid(const char *pidfile, pid_t *pid_out)
{
	FILE *fp;
	long v;

	fp = fopen(pidfile, "r");
	if (!fp)
		return -errno;
	if (fscanf(fp, "%ld", &v) != 1) {
		fclose(fp);
		return -EINVAL;
	}
	fclose(fp);
	if (v <= 0)
		return -EINVAL;
	*pid_out = (pid_t)v;
	return 0;
}

static int control_signal_pidfile(const char *pidfile, int sig)
{
	pid_t pid = 0;
	int rc;

	rc = control_read_pid(pidfile, &pid);
	if (rc != 0) {
		fprintf(stderr, "vdevadm control: cannot read %s: %s\n", pidfile,
				strerror(-rc));
		return 1;
	}
	if (kill(pid, sig) != 0) {
		fprintf(stderr, "vdevadm control: kill(%d, %d): %s\n", (int)pid, sig,
				strerror(errno));
		return 1;
	}
	printf("signalled pid %d (%s)\n", (int)pid, pidfile);
	return 0;
}

static int control_socket_send(struct vdevadm_ctx *ctx, int timeout_sec,
							   int (*send_fn)(struct vdev_ctrl *, int))
{
	struct vdev_ctrl *uctrl;
	int rc;

	if (getuid() != 0) {
		fprintf(stderr, "vdevadm control: root privileges required\n");
		return 1;
	}

	uctrl = vdev_ctrl_new(ctx->ctrl_socket);
	if (!uctrl) {
		fprintf(stderr, "vdevadm control: cannot open control client\n");
		return 1;
	}

	rc = send_fn(uctrl, timeout_sec);
	vdev_ctrl_unref(uctrl);

	if (rc != 0) {
		fprintf(stderr, "vdevadm control: %s: %s\n", ctx->ctrl_socket,
				strerror(-rc));
		return 1;
	}
	return 0;
}

static int control_send_log_level(struct vdev_ctrl *uctrl, int timeout_sec)
{
	return vdev_ctrl_send_set_log_level(uctrl, g_ctrl_log_level, timeout_sec);
}

static int control_send_env(struct vdev_ctrl *uctrl, int timeout_sec)
{
	return vdev_ctrl_send_set_env(uctrl, g_ctrl_env_pair, timeout_sec);
}

static int control_send_children_max(struct vdev_ctrl *uctrl, int timeout_sec)
{
	return vdev_ctrl_send_set_children_max(uctrl, g_ctrl_children_max,
										   timeout_sec);
}

static int control_send_promote(struct vdev_ctrl *uctrl, int timeout_sec)
{
	return vdev_ctrl_send_promote(uctrl, g_ctrl_promote_cfg, timeout_sec);
}

static int control_print_pid(struct vdevadm_ctx *ctx, int timeout_sec)
{
	struct vdev_ctrl *uctrl;
	pid_t pid = 0;
	int rc;

	if (getuid() != 0) {
		fprintf(stderr, "vdevadm control: root privileges required\n");
		return 1;
	}

	uctrl = vdev_ctrl_new(ctx->ctrl_socket);
	if (!uctrl) {
		fprintf(stderr, "vdevadm control: cannot open control client\n");
		return 1;
	}

	rc = vdev_ctrl_peer_pid(uctrl, &pid, timeout_sec);
	vdev_ctrl_unref(uctrl);

	if (rc != 0) {
		fprintf(stderr, "vdevadm control: %s: %s\n", ctx->ctrl_socket,
				strerror(-rc));
		return 1;
	}

	printf("%d\n", (int)pid);
	return 0;
}

int vdevadm_control_main(struct vdevadm_ctx *ctx, int argc, char *argv[])
{
	static const struct option opts[] = {
		{ "exit", no_argument, NULL, 'e' },
		{ "reload", no_argument, NULL, 'R' },
		{ "log-priority", required_argument, NULL, 'l' },
		{ "stop-exec-queue", no_argument, NULL, 's' },
		{ "start-exec-queue", no_argument, NULL, 'S' },
		{ "property", required_argument, NULL, 'p' },
		{ "env", required_argument, NULL, 'E' },
		{ "children-max", required_argument, NULL, 'm' },
		{ "timeout", required_argument, NULL, 'T' },
		{ "promote", optional_argument, NULL, 'M' },
		{ "print-pid", no_argument, NULL, 'i' },
		{ "refresh-settle", no_argument, NULL, 'r' },
		{ "reload-settle", no_argument, NULL, 'L' },
		{ "stop-settle", no_argument, NULL, 't' },
		{ "pid-file", required_argument, NULL, 'P' },
		{ "help", no_argument, NULL, 'h' },
		{}
	};
	char settle_pid[4096];
	int timeout_sec = 60;
	int c;
	int actions = 0;
	int rc = 0;

	if (snprintf(settle_pid, sizeof(settle_pid), "%s/vdev-settle.pid",
				 ctx->state_dir) >= (int)sizeof(settle_pid))
		return 1;

	while ((c = getopt_long(argc, argv, "eRl:sS:p:E:m:T:irLtP:h", opts,
							NULL)) >= 0) {
		switch (c) {
		case 'e':
			actions++;
			rc |= control_socket_send(ctx, timeout_sec, vdev_ctrl_send_exit);
			break;
		case 'R':
			actions++;
			rc |= control_socket_send(ctx, timeout_sec, vdev_ctrl_send_reload);
			break;
		case 'l':
			actions++;
			g_ctrl_log_level = atoi(optarg);
			rc |= control_socket_send(ctx, timeout_sec, control_send_log_level);
			break;
		case 's':
			actions++;
			rc |= control_socket_send(ctx, timeout_sec,
									  vdev_ctrl_send_stop_exec_queue);
			break;
		case 'S':
			actions++;
			rc |= control_socket_send(ctx, timeout_sec,
									  vdev_ctrl_send_start_exec_queue);
			break;
		case 'p':
		case 'E':
			actions++;
			g_ctrl_env_pair = optarg;
			rc |= control_socket_send(ctx, timeout_sec, control_send_env);
			break;
		case 'm':
			actions++;
			g_ctrl_children_max = atoi(optarg);
			if (g_ctrl_children_max < 1) {
				fprintf(stderr,
						"vdevadm control: invalid children-max\n");
				return 1;
			}
			rc |= control_socket_send(ctx, timeout_sec,
									  control_send_children_max);
			break;
		case 'T':
			timeout_sec = atoi(optarg);
			break;
		case 'M':
			actions++;
			g_ctrl_promote_cfg = optarg; /* NULL when --promote with no =CONF */
			rc |= control_socket_send(ctx, timeout_sec, control_send_promote);
			break;
		case 'i':
			actions++;
			rc |= control_print_pid(ctx, timeout_sec);
			break;
		case 'r':
			actions++;
			rc |= control_signal_pidfile(settle_pid, SIGUSR1);
			break;
		case 'L':
			actions++;
			rc |= control_signal_pidfile(settle_pid, SIGHUP);
			break;
		case 't':
			actions++;
			rc |= control_signal_pidfile(settle_pid, SIGTERM);
			break;
		case 'P':
			/* reserved for signal fallback to vdevd */
			break;
		case 'h':
			control_usage(argv[0]);
			return 0;
		default:
			control_usage(argv[0]);
			return 1;
		}
	}

	if (actions == 0) {
		fprintf(stderr, "vdevadm control: no command specified\n");
		control_usage(argv[0]);
		return 1;
	}

	return rc != 0 ? 1 : 0;
}
