/*
 * vdevadm device resolution — vdev udev-compat device IDs
 */

#include "vdevadm.h"

#include "common.h"

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

static int vdevadm_id_from_stat(mode_t mode, dev_t rdev, char *id_out,
								size_t id_len)
{
	unsigned maj = major(rdev);
	unsigned min = minor(rdev);
	const char *prefix = "c";

	if (S_ISBLK(mode))
		prefix = "b";
	else if (!S_ISCHR(mode))
		return -EINVAL;

	if (snprintf(id_out, id_len, "%s%u:%u", prefix, maj, min) >= (int)id_len)
		return -ENAMETOOLONG;
	return 0;
}

/* udev DB key for netdevs is n{ifindex}, not +net:sysname. */
static int vdevadm_id_from_net_syspath(const char *syspath, char *id_out,
									   size_t id_len)
{
	char ifindex_path[PATH_MAX];
	char buf[64];
	char *end = NULL;
	FILE *fp;
	unsigned long ifindex;

	if (!syspath || !syspath[0])
		return -EINVAL;
	if (snprintf(ifindex_path, sizeof(ifindex_path), "%s/ifindex", syspath) >=
		(int)sizeof(ifindex_path))
		return -ENAMETOOLONG;

	fp = fopen(ifindex_path, "re");
	if (!fp)
		return -errno;
	if (!fgets(buf, sizeof(buf), fp)) {
		int err = ferror(fp) ? errno : EINVAL;

		fclose(fp);
		return -err;
	}
	fclose(fp);

	ifindex = strtoul(buf, &end, 10);
	if (end == buf || ifindex == 0)
		return -EINVAL;
	if (snprintf(id_out, id_len, "n%lu", ifindex) >= (int)id_len)
		return -ENAMETOOLONG;
	return 0;
}

static int vdevadm_id_from_devpath(const char *devpath, char *id_out,
								   size_t id_len)
{
	const char *sub;
	const char *sysname;
	char subsys[64];

	if (!devpath || devpath[0] != '/')
		return -EINVAL;

	sub = strstr(devpath, "/subsystem/");
	if (sub) {
		const char *end;

		sub += strlen("/subsystem/");
		end = strchr(sub, '/');
		if (!end || (size_t)(end - sub) >= sizeof(subsys))
			return -EINVAL;
		memcpy(subsys, sub, (size_t)(end - sub));
		subsys[end - sub] = '\0';
		sysname = strrchr(devpath, '/');
		if (!sysname)
			return -EINVAL;
		sysname++;
	} else {
		/* Heuristic: last path component is sysname; walk up for subsystem. */
		sysname = strrchr(devpath, '/');
		if (!sysname)
			return -EINVAL;
		sysname++;
		if (strstr(devpath, "/block/") != NULL)
			strncpy(subsys, "block", sizeof(subsys) - 1);
		else if (strstr(devpath, "/input/") != NULL)
			strncpy(subsys, "input", sizeof(subsys) - 1);
		else if (strstr(devpath, "/net/") != NULL)
			strncpy(subsys, "net", sizeof(subsys) - 1);
		else
			return -ENOENT;
		subsys[sizeof(subsys) - 1] = '\0';
	}

	if (snprintf(id_out, id_len, "+%s:%s", subsys, sysname) >= (int)id_len)
		return -ENAMETOOLONG;
	return 0;
}

int vdevadm_device_id_from_path(struct vdevadm_ctx *ctx, const char *path,
								char *id_out, size_t id_len)
{
	struct stat st;
	char *syspath = NULL;
	size_t syspath_len = 0;
	char devpath[PATH_MAX];
	const char *p = path;
	int rc;

	if (!path || !path[0])
		return -EINVAL;

	/* Already a database key (b8:5, c13:64, n2, +block:sda). */
	if (path[0] == 'b' || path[0] == 'c' || path[0] == 'n' || path[0] == '+') {
		if (strlen(path) >= id_len)
			return -ENAMETOOLONG;
		strcpy(id_out, path);
		return 0;
	}

	if (strncmp(path, ctx->mountpoint, strlen(ctx->mountpoint)) == 0 &&
		path[strlen(ctx->mountpoint)] == '/') {
		p = path + strlen(ctx->mountpoint);
		while (*p == '/')
			p++;
		if (stat(path, &st) == 0)
			return vdevadm_id_from_stat(st.st_mode, st.st_rdev, id_out,
										id_len);
	}

	if (stat(path, &st) == 0 && (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
		return vdevadm_id_from_stat(st.st_mode, st.st_rdev, id_out, id_len);

	if (strncmp(path, "/dev/", 5) == 0) {
		char full[PATH_MAX];

		if (snprintf(full, sizeof(full), "%s/%s", ctx->mountpoint,
					 path + 5) >= (int)sizeof(full))
			return -ENAMETOOLONG;
		if (stat(full, &st) == 0 &&
			(S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
			return vdevadm_id_from_stat(st.st_mode, st.st_rdev, id_out,
										id_len);
	}

	if (strncmp(path, ctx->sysfs, strlen(ctx->sysfs)) == 0) {
		const char *rel = path + strlen(ctx->sysfs);

		while (*rel == '/')
			rel++;
		/* Net devices: prefer n{ifindex} (matches udev / vdev_device_id). */
		if (strstr(path, "/net/") != NULL || strstr(path, "/class/net/") != NULL) {
			rc = vdevadm_id_from_net_syspath(path, id_out, id_len);
			if (rc == 0)
				return 0;
		}
		if (snprintf(devpath, sizeof(devpath), "/%s", rel) >=
			(int)sizeof(devpath))
			return -ENAMETOOLONG;
		return vdevadm_id_from_devpath(devpath, id_out, id_len);
	}

	if (path[0] == '/') {
		if (stat(path, &st) == 0 &&
			(S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
			return vdevadm_id_from_stat(st.st_mode, st.st_rdev, id_out,
										id_len);
		if (strstr(path, "/net/") != NULL || strstr(path, "/class/net/") != NULL) {
			char syspath[PATH_MAX];

			if (strncmp(path, "/devices/", 9) == 0) {
				if (snprintf(syspath, sizeof(syspath), "%s%s", ctx->sysfs,
							 path) >= (int)sizeof(syspath))
					return -ENAMETOOLONG;
				rc = vdevadm_id_from_net_syspath(syspath, id_out, id_len);
			} else {
				rc = vdevadm_id_from_net_syspath(path, id_out, id_len);
			}
			if (rc == 0)
				return 0;
		}
		if (snprintf(devpath, sizeof(devpath), "%s", path) >=
			(int)sizeof(devpath))
			return -ENAMETOOLONG;
		rc = vdevadm_id_from_devpath(devpath, id_out, id_len);
		if (rc == 0)
			return 0;
	}

	if (stat(path, &st) == 0 &&
		(S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))) {
		rc = vdev_sysfs_get_syspath_from_device(
			ctx->sysfs, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
			&syspath, &syspath_len);
		if (rc == 0 && syspath) {
			if (snprintf(devpath, sizeof(devpath), "%s", syspath) >=
				(int)sizeof(devpath)) {
				free(syspath);
				return -ENAMETOOLONG;
			}
			free(syspath);
			return vdevadm_id_from_devpath(devpath, id_out, id_len);
		}
		free(syspath);
		return vdevadm_id_from_stat(st.st_mode, st.st_rdev, id_out, id_len);
	}

	return -ENOENT;
}

int vdevadm_device_db_path(struct vdevadm_ctx *ctx, const char *device_id,
						   char *path_out, size_t path_len)
{
	if (snprintf(path_out, path_len, "%s/data/%s", ctx->udev_root,
				 device_id) >= (int)path_len)
		return -ENAMETOOLONG;
	return 0;
}

int vdevadm_fifo_cmd(const char *ctl_path, const char *rsp_path,
					 const char *command, char *buf, size_t buflen)
{
	int ctl_fd = -1;
	int rsp_fd = -1;
	ssize_t n;
	size_t total = 0;
	char line[256];

	if (!ctl_path || !rsp_path || !command)
		return -EINVAL;

	rsp_fd = open(rsp_path, O_RDONLY);
	if (rsp_fd < 0)
		return -errno;

	ctl_fd = open(ctl_path, O_WRONLY);
	if (ctl_fd < 0) {
		close(rsp_fd);
		return -errno;
	}

	if (snprintf(line, sizeof(line), "%s\n", command) >= (int)sizeof(line)) {
		close(ctl_fd);
		close(rsp_fd);
		return -ENAMETOOLONG;
	}

	if (write(ctl_fd, line, strlen(line)) < 0) {
		int err = errno;

		close(ctl_fd);
		close(rsp_fd);
		return -err;
	}
	close(ctl_fd);

	while (total + 1 < buflen) {
		n = read(rsp_fd, buf + total, buflen - 1 - total);
		if (n < 0) {
			int err = errno;

			close(rsp_fd);
			return -err;
		}
		if (n == 0)
			break;
		total += (size_t)n;
	}
	close(rsp_fd);
	buf[total] = '\0';
	return (int)total;
}
