/*
 * 230-warning-kobject-bug-in-vdpasim-blk-dev-add
 */

#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define GENL_ID_CTRL			16
#define CTRL_CMD_GETFAMILY		3
#define CTRL_ATTR_FAMILY_ID		1
#define CTRL_ATTR_FAMILY_NAME		2

#define VDPA_CMD_DEV_NEW		3
#define VDPA_ATTR_MGMTDEV_DEV_NAME	2
#define VDPA_ATTR_DEV_NAME		4

#define NLA_ALIGN_UP(length)		(((length) + 3) & ~3)

/* struct genlmsghdr, replicated so this file needs no libnl headers. */
struct generic_netlink_header {
	unsigned char command;
	unsigned char version;
	unsigned short reserved;
};

static char netlink_reply_buffer[4096];

static int append_netlink_attribute(char *attribute_slot, int attribute_type,
				    const void *payload, int payload_length)
{
	struct nlattr *attribute = (struct nlattr *)attribute_slot;

	attribute->nla_type = attribute_type;
	attribute->nla_len = sizeof(*attribute) + payload_length;
	memcpy(attribute_slot + sizeof(*attribute), payload, payload_length);
	/* Zero the alignment padding: the kernel sees the whole aligned span. */
	memset(attribute_slot + sizeof(*attribute) + payload_length, 0,
	       NLA_ALIGN_UP(attribute->nla_len) - attribute->nla_len);
	return NLA_ALIGN_UP(attribute->nla_len);
}

static int send_generic_netlink_request(int netlink_fd, int family_id, int command,
					const char *attributes, int attributes_length)
{
	char request[512];
	struct nlmsghdr *request_header = (struct nlmsghdr *)request;
	struct generic_netlink_header *generic_header =
		(struct generic_netlink_header *)(request + NLMSG_HDRLEN);
	struct sockaddr_nl kernel_address;

	memset(request, 0, sizeof(request));
	request_header->nlmsg_len = NLMSG_HDRLEN + sizeof(*generic_header) + attributes_length;
	request_header->nlmsg_type = family_id;
	request_header->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	request_header->nlmsg_seq = 1;
	generic_header->command = command;
	generic_header->version = 1;
	memcpy(request + NLMSG_HDRLEN + sizeof(*generic_header), attributes, attributes_length);

	memset(&kernel_address, 0, sizeof(kernel_address));
	kernel_address.nl_family = AF_NETLINK;
	return sendto(netlink_fd, request, request_header->nlmsg_len, 0,
		      (struct sockaddr *)&kernel_address, sizeof(kernel_address));
}

static int resolve_vdpa_generic_netlink_family_id(int netlink_fd)
{
	char attributes[256];
	int attributes_length;
	int reply_length;
	struct nlmsghdr *reply_header = (struct nlmsghdr *)netlink_reply_buffer;
	char *attribute_cursor, *reply_end;

	attributes_length = append_netlink_attribute(attributes, CTRL_ATTR_FAMILY_NAME,
						     "vdpa", sizeof("vdpa"));
	if (send_generic_netlink_request(netlink_fd, GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
					 attributes, attributes_length) < 0)
		return -1;

	reply_length = recv(netlink_fd, netlink_reply_buffer, sizeof(netlink_reply_buffer), 0);
	if (reply_length <= 0 || reply_header->nlmsg_type != GENL_ID_CTRL)
		return -1;

	attribute_cursor = (char *)NLMSG_DATA(reply_header) + sizeof(struct generic_netlink_header);
	reply_end = netlink_reply_buffer + reply_header->nlmsg_len;
	while (attribute_cursor + sizeof(struct nlattr) <= reply_end) {
		struct nlattr *attribute = (struct nlattr *)attribute_cursor;

		if (attribute->nla_len < sizeof(*attribute))
			return -1;
		if (attribute->nla_type == CTRL_ATTR_FAMILY_ID)
			return *(unsigned short *)(attribute_cursor + sizeof(*attribute));
		attribute_cursor += NLA_ALIGN_UP(attribute->nla_len);
	}
	return -1;
}

int main(void)
{
	char attributes[256];
	int attributes_length = 0;
	int netlink_fd, vdpa_family_id;

	netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (netlink_fd < 0)
		return 1;

	vdpa_family_id = resolve_vdpa_generic_netlink_family_id(netlink_fd);
	if (vdpa_family_id <= 0)
		return 1;

	attributes_length += append_netlink_attribute(attributes + attributes_length,
						      VDPA_ATTR_MGMTDEV_DEV_NAME,
						      "vdpasim_blk", sizeof("vdpasim_blk"));
	attributes_length += append_netlink_attribute(attributes + attributes_length,
						      VDPA_ATTR_DEV_NAME, "", 1);

	send_generic_netlink_request(netlink_fd, vdpa_family_id, VDPA_CMD_DEV_NEW,
				     attributes, attributes_length);
	return 0;
}
