/*
 * DF-5151 PoC: dm pdev dedup key silently truncated to DM_MAX_DEV_NAME-1
 * (31) chars -> table loaded with path T binds to the pdev of a previously
 * loaded >=32-char path L when strncmp(T, L, 31) collides.
 *
 * Setup (as root, see run.sh):
 *   T = /tmp/dmtr/aaaaaaaaaaaaaaaaaaaaa   (exactly 31 chars) -> /dev/md0   (Y)
 *   L = /tmp/dmtr/aaaaaaaaaaaaaaaaaaaaabb (33 chars)        -> /dev/md0s0 (X)
 *
 * Expected (correct kernel):
 *   reload dfa with L  -> pdev on md0s0 (X)   [name stored TRUNCATED to T]
 *   reload dfb with T  -> pdev on md0    (Y)  [T resolves to md0]
 *   deps(dfa) != deps(dfb)
 *
 * Buggy kernel:
 *   dm_pdev_insert(T) strcmp-matches the *truncated* stored name T of L
 *   -> dfb silently binds to md0s0 (X): deps(dfb) == deps(dfa) != udev(md0)
 *
 * Control:
 *   reload dfc with a nonexistent path that matches no stored name
 *   -> load must FAIL (kernel rejects unknown paths normally).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

struct plistref {
	void   *pref_plist;
	size_t  pref_len;
};
#define NETBSD_DM_IOCTL _IOWR(0xfd, 0, struct plistref)

#define T_PATH "/tmp/dmtr/aaaaaaaaaaaaaaaaaaaaa"     /* 31 chars -> /dev/md0   */
#define L_PATH "/tmp/dmtr/aaaaaaaaaaaaaaaaaaaaabb"   /* 33 chars -> /dev/md0s0 */

static int ctlfd = -1;

static int
dm_ioctl_xml(const char *xml, char **out_xml, size_t *out_len)
{
	struct plistref pr;
	char *buf;
	int r;

	buf = strdup(xml);
	pr.pref_plist = buf;
	pr.pref_len = strlen(buf) + 1;	/* NUL included, like proplib does */

	r = ioctl(ctlfd, NETBSD_DM_IOCTL, &pr);
	free(buf);
	if (r != 0)
		return (-1);
	if (pr.pref_plist != NULL && pr.pref_len > 0) {
		/* kernel mmap'd + copyout'd a NUL-terminated response */
		*out_xml = strndup(pr.pref_plist, pr.pref_len);
		*out_len = strlen(*out_xml);
	} else {
		*out_xml = NULL;
		*out_len = 0;
	}
	return (0);
}

static void
print_resp(const char *tag, const char *xml)
{
	printf("---- %s response ----\n%s\n", tag, xml ? xml : "(none)");
}

/* pull the first <integer> inside cmd_data's <array> of a deps response */
static int
deps_first_u64(const char *xml, unsigned long long *val)
{
	const char *p, *arr, *intg, *close;

	p = strstr(xml, "<key>cmd_data</key>");
	if (p == NULL)
		return (-1);
	arr = strstr(p, "<array");
	if (arr == NULL)
		return (-1);
	if (strncmp(arr, "<array/>", 8) == 0)
		return (-1);	/* empty: no deps in this table */
	close = strstr(arr, "</array>");
	intg = strstr(arr, "<integer>");
	if (intg == NULL || (close && intg > close))
		return (-1);
	*val = strtoull(intg + 9, NULL, 0);
	return (0);
}

static int
dm_resume(const char *name)
{
	char xml[512];
	char *resp;
	size_t rlen;
	int r;

	snprintf(xml, sizeof(xml),
	    "<plist><dict>"
	    "<key>command</key><string>resume</string>"
	    "<key>version</key><array><integer>4</integer><integer>16</integer><integer>0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0</integer>"
	    "</dict></plist>", name);

	r = dm_ioctl_xml(xml, &resp, &rlen);
	if (r != 0) {
		printf("resume %s: ioctl failed: %s\n", name, strerror(errno));
		return (-1);
	}
	print_resp("resume", resp);
	free(resp);
	return (0);
}

static int
dm_create(const char *name)
{
	char xml[512];
	char *resp;
	size_t rlen;
	int r;

	snprintf(xml, sizeof(xml),
	    "<plist><dict>"
	    "<key>command</key><string>create</string>"
	    "<key>version</key><array><integer>4</integer><integer>16</integer><integer>0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0</integer>"
	    "</dict></plist>", name);

	r = dm_ioctl_xml(xml, &resp, &rlen);
	if (r != 0) {
		printf("create %s: ioctl failed: %s\n", name, strerror(errno));
		return (-1);
	}
	print_resp("create", resp);
	free(resp);
	return (0);
}

static int
dm_reload_linear(const char *name, const char *path)
{
	char xml[1024];
	char *resp;
	size_t rlen;
	int r;

	snprintf(xml, sizeof(xml),
	    "<plist><dict>"
	    "<key>command</key><string>reload</string>"
	    "<key>version</key><array><integer>4</integer><integer>16</integer><integer>0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0</integer>"
	    "<key>cmd_data</key><array><dict>"
	    "<key>type</key><string>linear</string>"
	    "<key>start</key><integer>0</integer>"
	    "<key>length</key><integer>32</integer>"
	    "<key>params</key><string>%s 0</string>"
	    "</dict></array>"
	    "</dict></plist>", name, path);

	r = dm_ioctl_xml(xml, &resp, &rlen);
	if (r != 0) {
		printf("reload %s (%s): ioctl FAILED with errno=%d (%s)\n",
		    name, path, errno, strerror(errno));
		return (-1);
	}
	print_resp("reload", resp);
	free(resp);
	return (0);
}

static int
dm_deps(const char *name, unsigned long long *val)
{
	char xml[512];
	char *resp;
	size_t rlen;
	int r;

	snprintf(xml, sizeof(xml),
	    "<plist><dict>"
	    "<key>command</key><string>deps</string>"
	    "<key>version</key><array><integer>4</integer><integer>16</integer><integer>0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0</integer>"
	    "</dict></plist>", name);

	r = dm_ioctl_xml(xml, &resp, &rlen);
	if (r != 0) {
		printf("deps %s: ioctl failed: %s\n", name, strerror(errno));
		return (-1);
	}
	print_resp("deps", resp);
	r = deps_first_u64(resp, val);
	free(resp);
	return (r);
}

static int
dm_remove(const char *name)
{
	char xml[512];
	char *resp;
	size_t rlen;
	int r;

	snprintf(xml, sizeof(xml),
	    "<plist><dict>"
	    "<key>command</key><string>remove</string>"
	    "<key>version</key><array><integer>4</integer><integer>16</integer><integer>0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0</integer>"
	    "</dict></plist>", name);

	r = dm_ioctl_xml(xml, &resp, &rlen);
	if (r != 0)
		printf("remove %s: ioctl failed: %s\n", name, strerror(errno));
	free(resp);
	return (0);	/* best effort cleanup */
}

int
main(int argc, char **argv)
{
	unsigned long long dep_a = 0, dep_b = 0, expect_y = 0;
	int have_a = 0, have_b = 0, misdirected = 0;

	/* argv[1]: expected udev (hex) of T's target device (e.g. /dev/md0) */
	if (argc > 1)
		expect_y = strtoull(argv[1], NULL, 16);

	printf("sizeof(plistref)=%zu (kernel expects 16)\n", sizeof(struct plistref));
	printf("strlen(T_PATH)=%zu  (must be 31)\n", strlen(T_PATH));
	printf("strlen(L_PATH)=%zu  (stored pdev name := first 31 chars of L)\n",
	    strlen(L_PATH));
	if (expect_y)
		printf("expected deps(dfb) if correct = 0x%llx\n", expect_y);

	ctlfd = open("/dev/mapper/control", O_RDWR);
	if (ctlfd < 0) {
		perror("open /dev/mapper/control");
		return (2);
	}

	/* 1. three dm devices */
	if (dm_create("dfa") || dm_create("dfb") || dm_create("dfc"))
		return (2);

	/* 2. dfa loaded with the LONG path L (-> /dev/md0s0) */
	if (dm_reload_linear("dfa", L_PATH) == 0)
		have_a = 1;
	else
		printf("dfa reload with 33-char path was REJECTED by kernel\n"
		    "(expected behaviour with DF-5151 fix applied)\n");

	/* 3. dfb loaded with T (31 chars, resolves -> /dev/md0) */
	if (dm_reload_linear("dfb", T_PATH) == 0)
		have_b = 1;

	/* 4. control: nonexistent path must be rejected */
	printf("==== control: reload with nonexistent path (expect FAILURE) ====\n");
	if (dm_reload_linear("dfc", "/tmp/dmtr/no-such-device-node") == 0) {
		printf("CONTROL FAILED: nonexistent path was accepted!\n");
	}

	/* 4.5 activate the tables (reload fills the inactive slot) */
	if (have_a)
		dm_resume("dfa");
	if (have_b)
		dm_resume("dfb");

	/* 5. compare deps */
	if (have_a)
		dm_deps("dfa", &dep_a);
	if (have_b)
		dm_deps("dfb", &dep_b);

	printf("\n==== RESULT ====\n");
	if (have_a)
		printf("deps(dfa [path L -> /dev/md0s0]) = 0x%llx\n", dep_a);
	if (have_b)
		printf("deps(dfb [path T -> /dev/md0   ]) = 0x%llx\n", dep_b);

	if (have_a && have_b) {
		if (dep_a == dep_b) {
			printf("\nMISDIRECTION PROVEN (BUG PRESENT): dfb's table (loaded\n"
			       "with a path resolving to /dev/md0) is bound to the pdev\n"
			       "of dfa (/dev/md0s0). The truncated 31-char dedup key\n"
			       "strcmp-matched.\n");
			misdirected = 1;
		} else {
			printf("\nNOT MISDIRECTED: dfb got its own pdev.\n");
		}
	} else if (!have_a && have_b) {
		if (expect_y && dep_b == expect_y) {
			printf("\nFIX CONFIRMED: long path rejected loudly, short path\n"
			       "bound to its own device (deps == expected 0x%llx).\n",
			       expect_y);
			misdirected = 0;
		} else {
			printf("\nAMBIGUOUS: dfa rejected; dfb deps=0x%llx "
			    "(expected 0x%llx)\n", dep_b, expect_y);
		}
	}

	/* cleanup */
	dm_remove("dfa");
	dm_remove("dfb");
	dm_remove("dfc");
	close(ctlfd);
	return (misdirected ? 0 : 1);
}
