// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2019 Cyril Hrubis <chrubis@suse.cz>
 */

/*
 * Very simple uevent netlink socket test.
 *
 * We fork a child that listens for a kernel events while parents creates and
 * removes a virtual mouse which produces add and remove event for the device
 * itself and for two event handlers called eventX and mouseY.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/sysmacros.h>
#include <linux/uinput.h>
#include "tst_test.h"
#include "tst_uinput.h"
#include "uevent.h"

static int mouse_fd;

static void create_uinput_mouse(void)
{
	mouse_fd = open_uinput();
	setup_mouse_events(mouse_fd);
	create_input_device(mouse_fd);
}

static void destroy_uinput_mouse(void)
{
	destroy_input_device(mouse_fd);
}

static void get_minor_major(char *device, char *minor, char *major, size_t buf_sizes)
{
	char path[1024];
	struct stat stbuf;

	snprintf(path, sizeof(path), "/dev/input/%s", device);

	SAFE_STAT(path, &stbuf);

	snprintf(major, buf_sizes, "MAJOR=%i", major(stbuf.st_rdev));
	snprintf(minor, buf_sizes, "MINOR=%i", minor(stbuf.st_rdev));
}

#define MINOR_MAJOR_SIZE 32

static void verify_uevent(void)
{
	int pid, fd;
	char add_msg[1024];
	char rem_msg[1024];
	char dev_path[1024];
	char add_msg_event1[1024];
	char rem_msg_event1[1024];
	char dev_path_event1[1024];
	char add_msg_event2[1024];
	char rem_msg_event2[1024];
	char dev_path_event2[1024];
	char dev_name1[1024];
	char dev_name2[1024];

	char minor_event1[MINOR_MAJOR_SIZE];
	char minor_event2[MINOR_MAJOR_SIZE];
	char major_event1[MINOR_MAJOR_SIZE];
	char major_event2[MINOR_MAJOR_SIZE];

	char *handlers, *handler1, *handler2, *sysname;
	struct uevent_desc add = {
		.msg = add_msg,
		.value_cnt = 7,
		.values = (const char*[]) {
			"ACTION=add",
			dev_path,
			"SUBSYSTEM=input",
			"NAME=\"virtual-device-ltp\"",
			"PROP=0",
			"EV=7",
			"REL=3",
		}
	};

	struct uevent_desc add_event1 = {
		.msg = add_msg_event1,
		.value_cnt = 6,
		.values = (const char*[]) {
			"ACTION=add",
			"SUBSYSTEM=input",
			dev_name1,
			dev_path_event1,
			minor_event1,
			major_event1,
		}
	};

	struct uevent_desc add_event2 = {
		.msg = add_msg_event2,
		.value_cnt = 6,
		.values = (const char*[]) {
			"ACTION=add",
			"SUBSYSTEM=input",
			dev_name2,
			dev_path_event2,
			minor_event2,
			major_event2,
		}
	};

	struct uevent_desc rem_event1 = {
		.msg = rem_msg_event1,
		.value_cnt = 6,
		.values = (const char*[]) {
			"ACTION=remove",
			"SUBSYSTEM=input",
			dev_name1,
			dev_path_event1,
			minor_event1,
			major_event1,
		}
	};

	struct uevent_desc rem_event2 = {
		.msg = rem_msg_event2,
		.value_cnt = 6,
		.values = (const char*[]) {
			"ACTION=remove",
			"SUBSYSTEM=input",
			dev_name2,
			dev_path_event2,
			minor_event2,
			major_event2,
		}
	};

	struct uevent_desc rem = {
		.msg = rem_msg,
		.value_cnt = 7,
		.values = (const char*[]) {
			"ACTION=remove",
			dev_path,
			"SUBSYSTEM=input",
			"NAME=\"virtual-device-ltp\"",
			"PROP=0",
			"EV=7",
			"REL=3",
		}
	};

	const struct uevent_desc *const uevents[] = {
		&add,
		&add_event1,
		&add_event2,
		&rem_event1,
		&rem_event2,
		&rem,
		NULL
	};

	fd = open_uevent_netlink();

	create_uinput_mouse();

	sysname = get_input_field_value('S');
	handlers = get_input_field_value('H');

	if (!sysname)
		tst_brk(TBROK, "Expected /devices/virtual/input/inputN sysname!");

	tst_res(TINFO, "Sysname: %s", sysname);
	tst_res(TINFO, "Handlers: %s", handlers);

	handler1 = strtok(handlers, " ");
	if (!handler1)
		tst_brk(TBROK, "Expected mouseX and eventY handlers!");

	get_minor_major(handler1, minor_event1, major_event1, MINOR_MAJOR_SIZE);

	handler2 = strtok(NULL, " ");
	if (!handler2)
		tst_brk(TBROK, "Expected mouseX and eventY handlers!");

	get_minor_major(handler2, minor_event2, major_event2, MINOR_MAJOR_SIZE);

	destroy_uinput_mouse();

	snprintf(add_msg, sizeof(add_msg), "add@%s", sysname);

	snprintf(rem_msg, sizeof(rem_msg), "remove@%s", sysname);

	snprintf(dev_path, sizeof(dev_path), "DEVPATH=%s", sysname);

	snprintf(add_msg_event1, sizeof(add_msg_event1),
		"add@%s/%s", sysname, handler1);

	snprintf(rem_msg_event1, sizeof(rem_msg_event1),
		"remove@%s/%s", sysname, handler1);

	snprintf(dev_path_event1, sizeof(dev_path_event1),
		"DEVPATH=%s/%s", sysname, handler1);

	snprintf(dev_name1, sizeof(dev_name1),
		"DEVNAME=input/%s", handler1);


	snprintf(add_msg_event2, sizeof(add_msg_event2),
		"add@%s/%s", sysname, handler2);

	snprintf(rem_msg_event2, sizeof(rem_msg_event2),
		"remove@%s/%s", sysname, handler2);

	snprintf(dev_path_event2, sizeof(dev_path_event2),
		"DEVPATH=%s/%s", sysname, handler2);

	snprintf(dev_name2, sizeof(dev_name2),
		"DEVNAME=input/%s", handler2);

	free(sysname);
	free(handlers);

	pid = SAFE_FORK();
	if (!pid) {
		wait_for_uevents(fd, uevents);
		exit(0);
	}

	SAFE_CLOSE(fd);
	wait_for_pid(pid);
}

static struct tst_test test = {
	.test_all = verify_uevent,
	.forks_child = 1,
	.needs_checkpoints = 1,
	.needs_drivers = (const char *const[]) {
		"uinput",
		NULL
	},
	.needs_root = 1,
};
