/*
 * vdev_settle.c — libinput device monitor for input settle
 *
 * Copyright (C) 2026  Aitor C.Z. <aitor@genuen.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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * See the COPYING file.
 */

#include "libvdev/misc.h"
#include "libvdev/sglib.h"
#include "libvdev/util.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <libinput.h>
#include <libudev.h>

const char *progname = "vdev_settle";

static volatile sig_atomic_t stop_flag;
static volatile sig_atomic_t refresh_flag;
static volatile sig_atomic_t reload_flag;

struct settle_state {
	struct sglib_cstr_vector expected;
	struct sglib_cstr_vector seen;
	const char *metadata_path;
	const char *state_dir;
	char fifo_ctl_path[PATH_MAX];
	char fifo_rsp_path[PATH_MAX];
	int fifo_ctl_fd;
	int fifo_rsp_fd;
	bool fifo_enabled;
};

static int settle_load_metadata(struct settle_state *st);
static void settle_reconcile_seen(struct settle_state *st);

static void on_stop(int ignored)
{
	(void)ignored;
	stop_flag = 1;
}

static void on_refresh(int ignored)
{
	(void)ignored;
	refresh_flag = 1;
}

static void on_reload(int ignored)
{
	(void)ignored;
	reload_flag = 1;
}

static int seen_line_sysname(const char *line, char *sysname, size_t sysname_len)
{
	const char *tab = strchr(line, '\t');

	if (!tab || (size_t)(tab - line) >= sysname_len)
		return -1;
	memcpy(sysname, line, (size_t)(tab - line));
	sysname[tab - line] = '\0';
	return 0;
}

static int seen_contains_sysname(struct sglib_cstr_vector *vec, const char *sysname)
{
	char existing[64];

	for (size_t i = 0; i < sglib_cstr_vector_size(vec); i++) {
		char *line = sglib_cstr_vector_at(vec, i);
		if (!line)
			continue;
		if (seen_line_sysname(line, existing, sizeof(existing)) == 0 &&
			strcmp(existing, sysname) == 0)
			return 1;
	}
	return 0;
}

static int seen_push_device(struct sglib_cstr_vector *vec, const char *sysname,
							const char *name)
{
	char line[PATH_MAX + 256];
	char *copy;

	if (seen_contains_sysname(vec, sysname))
		return 0;

	if (snprintf(line, sizeof(line), "%s\t%s", sysname, name) >= (int)sizeof(line))
		return -ENAMETOOLONG;

	copy = vdev_strdup_or_null(line);
	if (!copy)
		return -ENOMEM;

	if (sglib_cstr_vector_push_back(vec, copy) < 0) {
		free(copy);
		return -ENOMEM;
	}
	return 0;
}

static void settle_mark_expected_found(struct settle_state *st, const char *name)
{
	for (size_t i = 0; i < sglib_cstr_vector_size(&st->expected); i++) {
		char *current = sglib_cstr_vector_at(&st->expected, i);
		if (current && strcmp(current, name) == 0) {
			free(current);
			sglib_cstr_vector_set(&st->expected, NULL, i);
			break;
		}
	}
}

static int settle_pending_count(struct settle_state *st)
{
	int pending = 0;

	for (size_t i = 0; i < sglib_cstr_vector_size(&st->expected); i++) {
		if (sglib_cstr_vector_at(&st->expected, i) != NULL)
			pending++;
	}
	return pending;
}

static int open_restricted(const char *path, int flags, void *user_data)
{
	(void)user_data;
	int fd = open(path, flags);

	if (fd < 0)
		vdev_error("Failed to open %s (%s)\n", path, strerror(errno));

	return fd < 0 ? -errno : fd;
}

static void close_restricted(int fd, void *user_data)
{
	(void)user_data;
	close(fd);
}

static const struct libinput_interface interface = {
	.open_restricted	= open_restricted,
	.close_restricted	= close_restricted,
};

static int write_file_atomic(const char *final_path, const char *content)
{
	char tmp_path[PATH_MAX];
	FILE *fp;
	size_t len;

	if (snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", final_path) >=
		(int)sizeof(tmp_path))
		return -ENAMETOOLONG;

	fp = fopen(tmp_path, "we");
	if (!fp)
		return -errno;

	len = strlen(content);
	if (fwrite(content, 1, len, fp) != len) {
		int err = errno;
		fclose(fp);
		unlink(tmp_path);
		return -err;
	}
	if (fflush(fp) != 0 || fclose(fp) != 0) {
		int err = errno;
		unlink(tmp_path);
		return -err;
	}
	if (rename(tmp_path, final_path) != 0) {
		int err = errno;
		unlink(tmp_path);
		return -err;
	}
	return 0;
}

static int write_lines_atomic(const char *final_path, struct sglib_cstr_vector *vec)
{
	char tmp_path[PATH_MAX];
	FILE *fp;
	int rc = 0;

	if (snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", final_path) >=
		(int)sizeof(tmp_path))
		return -ENAMETOOLONG;

	fp = fopen(tmp_path, "we");
	if (!fp)
		return -errno;

	for (size_t i = 0; i < sglib_cstr_vector_size(vec); i++) {
		char *line = sglib_cstr_vector_at(vec, i);
		if (!line)
			continue;
		if (fprintf(fp, "%s\n", line) < 0) {
			rc = -errno;
			break;
		}
	}

	if (rc == 0 && fflush(fp) != 0)
		rc = -errno;
	if (fclose(fp) != 0 && rc == 0)
		rc = -errno;
	if (rc != 0) {
		unlink(tmp_path);
		return rc;
	}
	if (rename(tmp_path, final_path) != 0) {
		int err = errno;
		unlink(tmp_path);
		return -err;
	}
	return 0;
}

static int settle_publish_state(struct settle_state *st)
{
	char status_path[PATH_MAX];
	char seen_path[PATH_MAX];
	char pending_path[PATH_MAX];
	char status_buf[64];
	int pending = settle_pending_count(st);
	int rc;

	if (snprintf(status_path, sizeof(status_path), "%s/vdev-settle.status",
				 st->state_dir) >= (int)sizeof(status_path) ||
		snprintf(seen_path, sizeof(seen_path), "%s/vdev-settle.seen",
				 st->state_dir) >= (int)sizeof(seen_path) ||
		snprintf(pending_path, sizeof(pending_path), "%s/vdev-settle.pending",
				 st->state_dir) >= (int)sizeof(pending_path))
		return -ENAMETOOLONG;

	if (sglib_cstr_vector_size(&st->expected) == 0)
		snprintf(status_buf, sizeof(status_buf), "seen %zu",
				 sglib_cstr_vector_size(&st->seen));
	else
		snprintf(status_buf, sizeof(status_buf), "%s",
				 pending == 0 ? "ready" : "pending");

	rc = write_file_atomic(status_path, status_buf);
	if (rc != 0)
		return rc;

	rc = write_lines_atomic(seen_path, &st->seen);
	if (rc != 0)
		return rc;

	return write_lines_atomic(pending_path, &st->expected);
}

static int fifo_write_response(int rsp_fd, const char *body)
{
	ssize_t n;
	size_t len;

	if (rsp_fd < 0)
		return -EBADF;

	len = strlen(body);
	n = write(rsp_fd, body, len);
	if (n < 0) {
		if (errno == EAGAIN || errno == EWOULDBLOCK)
			return 0;
		return -errno;
	}
	return 0;
}

static int fifo_format_status(struct settle_state *st, char *buf, size_t buflen)
{
	int pending = settle_pending_count(st);

	if (sglib_cstr_vector_size(&st->expected) == 0)
		return snprintf(buf, buflen, "seen %zu\n",
						sglib_cstr_vector_size(&st->seen));
	if (pending == 0)
		return snprintf(buf, buflen, "ready\n");
	return snprintf(buf, buflen, "pending %d\n", pending);
}

static int fifo_format_list(struct settle_state *st, char *buf, size_t buflen)
{
	size_t used = 0;

	for (size_t i = 0; i < sglib_cstr_vector_size(&st->seen); i++) {
		char *line = sglib_cstr_vector_at(&st->seen, i);
		int n;

		if (!line)
			continue;
		n = snprintf(buf + used, buflen - used, "%s\n", line);
		if (n < 0 || (size_t)n >= buflen - used)
			return -ENAMETOOLONG;
		used += (size_t)n;
	}
	if (used == 0) {
		buf[0] = '\n';
		used = 1;
	}
	return (int)used;
}

static int fifo_format_list_pending(struct settle_state *st, char *buf,
									size_t buflen)
{
	size_t used = 0;

	for (size_t i = 0; i < sglib_cstr_vector_size(&st->expected); i++) {
		char *line = sglib_cstr_vector_at(&st->expected, i);
		int n;

		if (!line)
			continue;
		n = snprintf(buf + used, buflen - used, "%s\n", line);
		if (n < 0 || (size_t)n >= buflen - used)
			return -ENAMETOOLONG;
		used += (size_t)n;
	}
	if (used == 0) {
		buf[0] = '\n';
		used = 1;
	}
	return (int)used;
}

static int fifo_handle_command(struct settle_state *st, const char *cmd)
{
	char response[65536];
	int rc;

	if (strcmp(cmd, "status") == 0) {
		rc = fifo_format_status(st, response, sizeof(response));
	} else if (strcmp(cmd, "list") == 0) {
		rc = fifo_format_list(st, response, sizeof(response));
	} else if (strcmp(cmd, "list-pending") == 0 ||
			   strcmp(cmd, "list pending") == 0) {
		rc = fifo_format_list_pending(st, response, sizeof(response));
	} else if (strcmp(cmd, "reload") == 0) {
		rc = settle_load_metadata(st);
		if (rc != 0)
			snprintf(response, sizeof(response), "error %s\n",
					 strerror(-rc));
		else {
			settle_reconcile_seen(st);
			rc = settle_publish_state(st);
			if (rc != 0)
				snprintf(response, sizeof(response), "error %s\n",
						 strerror(-rc));
			else
				snprintf(response, sizeof(response), "ok\n");
		}
		return fifo_write_response(st->fifo_rsp_fd, response);
	} else {
		snprintf(response, sizeof(response),
				 "error unknown command (try: status, list, list-pending, reload)\n");
		return fifo_write_response(st->fifo_rsp_fd, response);
	}

	if (rc < 0) {
		snprintf(response, sizeof(response), "error %s\n", strerror(-rc));
		return fifo_write_response(st->fifo_rsp_fd, response);
	}
	return fifo_write_response(st->fifo_rsp_fd, response);
}

static int settle_fifo_init(struct settle_state *st)
{
	struct stat sb;

	if (snprintf(st->fifo_ctl_path, sizeof(st->fifo_ctl_path), "%s/vdev-settle.ctl",
				 st->state_dir) >= (int)sizeof(st->fifo_ctl_path) ||
		snprintf(st->fifo_rsp_path, sizeof(st->fifo_rsp_path), "%s/vdev-settle.rsp",
				 st->state_dir) >= (int)sizeof(st->fifo_rsp_path))
		return -ENAMETOOLONG;

	if (mkfifo(st->fifo_ctl_path, 0600) != 0 && errno != EEXIST)
		return -errno;
	if (mkfifo(st->fifo_rsp_path, 0600) != 0 && errno != EEXIST)
		return -errno;

	st->fifo_ctl_fd = open(st->fifo_ctl_path, O_RDWR | O_NONBLOCK);
	if (st->fifo_ctl_fd < 0)
		return -errno;

	if (fstat(st->fifo_ctl_fd, &sb) != 0 || !S_ISFIFO(sb.st_mode)) {
		close(st->fifo_ctl_fd);
		st->fifo_ctl_fd = -1;
		return -EINVAL;
	}

	/* Hold rsp O_RDWR so clients can open it for read before writing ctl
	 * (avoids FIFO open deadlock). Responses are written to this fd. */
	st->fifo_rsp_fd = open(st->fifo_rsp_path, O_RDWR | O_NONBLOCK);
	if (st->fifo_rsp_fd < 0) {
		int err = -errno;

		close(st->fifo_ctl_fd);
		st->fifo_ctl_fd = -1;
		return err;
	}

	if (fstat(st->fifo_rsp_fd, &sb) != 0 || !S_ISFIFO(sb.st_mode)) {
		close(st->fifo_rsp_fd);
		st->fifo_rsp_fd = -1;
		close(st->fifo_ctl_fd);
		st->fifo_ctl_fd = -1;
		return -EINVAL;
	}
	return 0;
}

static void settle_fifo_shutdown(struct settle_state *st)
{
	if (st->fifo_rsp_fd >= 0) {
		close(st->fifo_rsp_fd);
		st->fifo_rsp_fd = -1;
	}
	if (st->fifo_ctl_fd >= 0) {
		close(st->fifo_ctl_fd);
		st->fifo_ctl_fd = -1;
	}
}

static int settle_fifo_drain(struct settle_state *st)
{
	char buf[256];
	ssize_t n;
	char *nl;
	int rc = 0;

	for (;;) {
		n = read(st->fifo_ctl_fd, buf, sizeof(buf) - 1);
		if (n < 0) {
			if (errno == EAGAIN)
				return rc;
			return -errno;
		}
		if (n == 0)
			return rc;

		buf[n] = '\0';
		nl = strpbrk(buf, "\r\n");
		if (nl)
			*nl = '\0';

		if (buf[0] == '\0')
			continue;

		rc = fifo_handle_command(st, buf);
		if (rc != 0)
			vdev_error("%s: fifo command '%s': %s\n", progname, buf,
					   strerror(-rc));
	}
}

static int settle_load_metadata(struct settle_state *st)
{
	FILE *fp;
	char line[1024];
	int rc = 0;

	vdev_cstr_vector_free_all(&st->expected);
	sglib_cstr_vector_clear(&st->expected);

	if (!st->metadata_path || st->metadata_path[0] == '\0' ||
		access(st->metadata_path, F_OK) != 0)
		return 0;

	fp = fopen(st->metadata_path, "r");
	if (!fp)
		return -errno;

	while (fgets(line, sizeof(line), fp)) {
		char *copy;

		line[strcspn(line, "\r\n")] = '\0';
		if (line[0] == '\0')
			continue;

		copy = vdev_strdup_or_null(line);
		if (!copy) {
			rc = -ENOMEM;
			break;
		}
		if (sglib_cstr_vector_push_back(&st->expected, copy) < 0) {
			free(copy);
			rc = -ENOMEM;
			break;
		}
	}
	fclose(fp);
	return rc;
}

static void settle_reconcile_seen(struct settle_state *st)
{
	char sysname[64];
	char *tab;

	for (size_t i = 0; i < sglib_cstr_vector_size(&st->seen); i++) {
		char *line = sglib_cstr_vector_at(&st->seen, i);
		if (!line)
			continue;
		tab = strchr(line, '\t');
		if (!tab)
			continue;
		if (seen_line_sysname(line, sysname, sizeof(sysname)) != 0)
			continue;
		*tab = '\0';
		settle_mark_expected_found(st, tab + 1);
		*tab = '\t';
	}
}

static int settle_handle_device_added(struct settle_state *st,
									  struct libinput_device *dev)
{
	const char *sysname = libinput_device_get_sysname(dev);
	const char *name = libinput_device_get_name(dev);
	int rc;

	if (!sysname || sysname[0] == '\0' || !name || name[0] == '\0')
		return 0;

	rc = seen_push_device(&st->seen, sysname, name);
	if (rc != 0)
		return rc;

	settle_mark_expected_found(st, name);
	return settle_publish_state(st);
}

static void usage(const char *argv0)
{
	const char *p = argv0 && argv0[0] ? argv0 : progname;

	fprintf(stderr,
			"Usage: %s [options]\n"
			"\n"
			"Monitor libinput (seat0) and publish settle state for shell scripts.\n"
			"Shell code can compare sysfs /sys/class/input/event* nodes against the\n"
			"device list and trigger uevent \"add\" on nodes not yet seen here.\n"
			"\n"
			"  --metadata PATH   Optional expected device names file\n"
			"  --state-dir DIR   Runtime directory (default: /run/vdev)\n"
			"  --pid-file PATH   PID file (default: DIR/vdev-settle.pid)\n"
			"  --fifo            Enable command FIFOs (default: on)\n"
			"  --no-fifo         Disable command FIFOs\n"
			"  -f, --foreground  Do not daemonize\n"
			"  -h, --help        Show this help\n"
			"\n"
			"Status files (updated on libinput events, SIGUSR1, and after reload):\n"
			"  vdev-settle.status   ready | pending | seen N\n"
			"  vdev-settle.seen     lines: eventN<TAB>libinput-name\n"
			"  vdev-settle.pending  expected names still missing (if --metadata)\n"
			"\n"
			"FIFO commands (open vdev-settle.rsp for read before writing ctl):\n"
			"  status        ready | pending N | seen N\n"
			"  list          eventN<TAB>name per line (libinput-visible devices)\n"
			"  list-pending  expected names still missing\n"
			"  reload        reload --metadata\n"
			"\n"
			"  $state-dir/vdev-settle.ctl   request\n"
			"  $state-dir/vdev-settle.rsp   response\n"
			"\n"
			"Signals: SIGUSR1 refresh files; SIGHUP reload metadata; SIGTERM stop\n"
			"\n"
			"Example:\n"
			"  exec 3< /run/vdev/vdev-settle.rsp\n"
			"  printf 'list\\n' > /run/vdev/vdev-settle.ctl\n"
			"  cat <&3\n",
			p);
}

static int daemonize(void)
{
	pid_t pid = fork();

	if (pid < 0)
		return -errno;
	if (pid > 0)
		_exit(EXIT_SUCCESS);

	if (setsid() < 0)
		return -errno;

	pid = fork();
	if (pid < 0)
		return -errno;
	if (pid > 0)
		_exit(EXIT_SUCCESS);

	if (chdir("/") != 0)
		return -errno;

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
	return 0;
}

static int write_pid_file(const char *pid_path)
{
	FILE *fp = fopen(pid_path, "we");
	if (!fp)
		return -errno;
	fprintf(fp, "%d\n", (int)getpid());
	if (fflush(fp) != 0 || fclose(fp) != 0)
		return -errno;
	return 0;
}

static int settle_run(struct settle_state *st)
{
	struct udev *udev = NULL;
	struct libinput *li = NULL;
	int rc = 0;

	udev = udev_new();
	if (!udev) {
		vdev_error("%s: udev_new failed\n", progname);
		return EXIT_FAILURE;
	}

	li = libinput_udev_create_context(&interface, NULL, udev);
	if (!li) {
		vdev_error("%s: libinput_udev_create_context failed\n", progname);
		rc = EXIT_FAILURE;
		goto out;
	}

	if (libinput_udev_assign_seat(li, "seat0") != 0) {
		vdev_error("%s: libinput_udev_assign_seat(seat0) failed\n", progname);
		rc = EXIT_FAILURE;
		goto out;
	}

	if (st->fifo_enabled) {
		rc = settle_fifo_init(st);
		if (rc != 0) {
			vdev_error("%s: fifo init: %s\n", progname, strerror(-rc));
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	rc = settle_publish_state(st);
	if (rc != 0) {
		vdev_error("%s: settle_publish_state: %s\n", progname, strerror(-rc));
		rc = EXIT_FAILURE;
		goto out;
	}

	while (!stop_flag) {
		struct pollfd pfds[2];
		int nfds = 1;
		int timeout_ms = 500;

		pfds[0].fd = libinput_get_fd(li);
		pfds[0].events = POLLIN;

		if (st->fifo_enabled && st->fifo_ctl_fd >= 0) {
			pfds[1].fd = st->fifo_ctl_fd;
			pfds[1].events = POLLIN;
			nfds = 2;
		}

		if (refresh_flag || reload_flag)
			timeout_ms = 0;

		(void)poll(pfds, nfds, timeout_ms);

		if (reload_flag) {
			reload_flag = 0;
			rc = settle_load_metadata(st);
			if (rc != 0) {
				vdev_error("%s: reload metadata: %s\n", progname,
						   strerror(-rc));
			} else {
				settle_reconcile_seen(st);
				rc = settle_publish_state(st);
				if (rc != 0)
					vdev_error("%s: settle_publish_state: %s\n", progname,
							   strerror(-rc));
			}
		}

		if (refresh_flag) {
			refresh_flag = 0;
			rc = settle_publish_state(st);
			if (rc != 0)
				vdev_error("%s: settle_publish_state: %s\n", progname,
						   strerror(-rc));
		}

		if (st->fifo_enabled && nfds > 1 && (pfds[1].revents & POLLIN)) {
			int fifo_rc = settle_fifo_drain(st);
			if (fifo_rc != 0)
				vdev_error("%s: fifo drain: %s\n", progname,
						   strerror(-fifo_rc));
		}

		libinput_dispatch(li);

		struct libinput_event *ev;
		while ((ev = libinput_get_event(li))) {
			if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) {
				struct libinput_device *dev =
					libinput_event_get_device(ev);
				int add_rc = settle_handle_device_added(st, dev);
				if (add_rc != 0)
					vdev_error("%s: device add: %s\n", progname,
							   strerror(-add_rc));
			}
			libinput_event_destroy(ev);
		}
	}

	rc = EXIT_SUCCESS;

out:
	settle_fifo_shutdown(st);
	if (li)
		libinput_unref(li);
	if (udev)
		udev_unref(udev);
	return rc;
}

int main(int argc, char **argv)
{
	struct settle_state st;
	const char *metadata_path = NULL;
	const char *state_dir = "/run/vdev";
	const char *pid_file = NULL;
	char pid_path[PATH_MAX];
	bool foreground = false;
	bool fifo_enabled = true;
	int rc = 0;

	memset(&st, 0, sizeof(st));
	st.metadata_path = metadata_path;
	st.state_dir = state_dir;
	st.fifo_ctl_fd = -1;
	st.fifo_rsp_fd = -1;
	st.fifo_enabled = fifo_enabled;

	sglib_cstr_vector_init(&st.expected);
	sglib_cstr_vector_init(&st.seen);

	for (int i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
			usage(argv[0]);
			rc = 0;
			goto cleanup;
		} else if (strcmp(argv[i], "-f") == 0 ||
				   strcmp(argv[i], "--foreground") == 0) {
			foreground = true;
		} else if (strcmp(argv[i], "--fifo") == 0) {
			fifo_enabled = true;
			st.fifo_enabled = true;
		} else if (strcmp(argv[i], "--no-fifo") == 0) {
			fifo_enabled = false;
			st.fifo_enabled = false;
		} else if (strcmp(argv[i], "--metadata") == 0) {
			if (i + 1 >= argc) {
				usage(argv[0]);
				rc = EXIT_FAILURE;
				goto cleanup;
			}
			metadata_path = argv[++i];
			st.metadata_path = metadata_path;
		} else if (strcmp(argv[i], "--state-dir") == 0) {
			if (i + 1 >= argc) {
				usage(argv[0]);
				rc = EXIT_FAILURE;
				goto cleanup;
			}
			state_dir = argv[++i];
			st.state_dir = state_dir;
		} else if (strcmp(argv[i], "--pid-file") == 0) {
			if (i + 1 >= argc) {
				usage(argv[0]);
				rc = EXIT_FAILURE;
				goto cleanup;
			}
			pid_file = argv[++i];
		} else {
			usage(argv[0]);
			rc = EXIT_FAILURE;
			goto cleanup;
		}
	}

	if (mkdir(st.state_dir, 0755) != 0 && errno != EEXIST) {
		vdev_error("%s: mkdir('%s'): %s\n", progname, st.state_dir,
				   strerror(errno));
		rc = EXIT_FAILURE;
		goto cleanup;
	}

	rc = settle_load_metadata(&st);
	if (rc != 0) {
		vdev_error("%s: load metadata: %s\n", progname, strerror(-rc));
		rc = EXIT_FAILURE;
		goto cleanup;
	}

	signal(SIGTERM, on_stop);
	signal(SIGINT, on_stop);
	signal(SIGUSR1, on_refresh);
	signal(SIGHUP, on_reload);

	if (!foreground) {
		rc = daemonize();
		if (rc != 0) {
			vdev_error("%s: daemonize: %s\n", progname, strerror(-rc));
			rc = EXIT_FAILURE;
			goto cleanup;
		}
	}

	if (!pid_file) {
		if (snprintf(pid_path, sizeof(pid_path), "%s/vdev-settle.pid",
					 st.state_dir) >= (int)sizeof(pid_path)) {
			rc = EXIT_FAILURE;
			goto cleanup;
		}
		pid_file = pid_path;
	}

	rc = write_pid_file(pid_file);
	if (rc != 0) {
		vdev_error("%s: write pid file: %s\n", progname, strerror(-rc));
		rc = EXIT_FAILURE;
		goto cleanup;
	}

	rc = settle_run(&st);

cleanup:
	settle_fifo_shutdown(&st);
	vdev_cstr_vector_free_all(&st.seen);
	sglib_cstr_vector_free(&st.seen);
	vdev_cstr_vector_free_all(&st.expected);
	sglib_cstr_vector_free(&st.expected);

	return rc;
}
