#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <stdint.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

static void xmkdir(const char *path)
{
	if (mkdir(path, 0755) && errno != EEXIST) {
		perror(path);
		exit(1);
	}
}

static void maybe_mount(const char *src, const char *target, const char *fstype)
{
	if (mount(src, target, fstype, 0, NULL) && errno != EBUSY) {
		fprintf(stderr, "mount %s on %s failed: %s\n",
			fstype, target, strerror(errno));
		exit(1);
	}
}

static void dump_trace_error_log(void)
{
	int fd = open("/sys/kernel/tracing/error_log", O_RDONLY | O_CLOEXEC);
	char buf[2048];
	ssize_t n;

	if (fd < 0)
		return;
	while ((n = read(fd, buf, sizeof(buf))) > 0)
		write(STDERR_FILENO, buf, n);
	close(fd);
}

static void write_file(const char *path, const char *s, int trunc)
{
	int flags = O_WRONLY | O_CLOEXEC;
	int fd;
	ssize_t want = (ssize_t)strlen(s);

	flags |= trunc ? O_TRUNC : O_APPEND;
	fd = open(path, flags);
	if (fd < 0) {
		fprintf(stderr, "open %s failed: %s\n", path, strerror(errno));
		exit(1);
	}
	if (want && write(fd, s, want) != want) {
		fprintf(stderr, "write %s failed: %s\n", path, strerror(errno));
		dump_trace_error_log();
		exit(1);
	}
	close(fd);
}

static long read_long_file(const char *path)
{
	char buf[64];
	int fd = open(path, O_RDONLY | O_CLOEXEC);
	ssize_t n;

	if (fd < 0) {
		fprintf(stderr, "open %s failed: %s\n", path, strerror(errno));
		exit(1);
	}
	n = read(fd, buf, sizeof(buf) - 1);
	if (n <= 0) {
		fprintf(stderr, "read %s failed: %s\n", path, strerror(errno));
		exit(1);
	}
	close(fd);
	buf[n] = '\0';
	return strtol(buf, NULL, 0);
}

static int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu,
			   int group_fd, unsigned long flags)
{
	return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}

int main(void)
{
	struct perf_event_attr attr;
	cpu_set_t set;
	char event[1024];
	char *longstr;
	long id;
	int pfd;

	xmkdir("/proc");
	xmkdir("/sys");
	xmkdir("/sys/kernel");
	xmkdir("/sys/kernel/tracing");
	maybe_mount("proc", "/proc", "proc");
	maybe_mount("sysfs", "/sys", "sysfs");
	maybe_mount("tracefs", "/sys/kernel/tracing", "tracefs");

	CPU_ZERO(&set);
	CPU_SET(0, &set);
	if (sched_setaffinity(0, sizeof(set), &set))
		perror("sched_setaffinity");

	longstr = malloc(4096);
	if (!longstr) {
		perror("malloc");
		return 1;
	}
	memset(longstr, 'R', 4095);
	longstr[4095] = '\0';

	snprintf(event, sizeof(event),
		 "r:kretbig __x64_sys_getpid "
		 "a0=\\0x%lx:ustring a1=\\0x%lx:ustring "
		 "a2=\\0x%lx:ustring a3=\\0x%lx:ustring\n",
		 (unsigned long)longstr, (unsigned long)longstr,
		 (unsigned long)longstr, (unsigned long)longstr);

	write_file("/sys/kernel/tracing/kprobe_events", "", 1);
	write_file("/sys/kernel/tracing/kprobe_events", event, 0);
	id = read_long_file("/sys/kernel/tracing/events/kprobes/kretbig/id");

	memset(&attr, 0, sizeof(attr));
	attr.type = PERF_TYPE_TRACEPOINT;
	attr.size = sizeof(attr);
	attr.config = id;
	attr.sample_period = 1;
	attr.sample_type = PERF_SAMPLE_RAW;
	attr.disabled = 0;
	attr.wakeup_events = 1;

	pfd = perf_event_open(&attr, -1, 0, -1, PERF_FLAG_FD_CLOEXEC);
	if (pfd < 0) {
		fprintf(stderr, "perf_event_open failed: %s\n", strerror(errno));
		return 1;
	}

	printf("triggering kretprobe perf oversized ustring event id %ld\n", id);
	for (int i = 0; i < 16; i++)
		(void)getpid();
	printf("getpid returned without WARN\n");
	close(pfd);
	sync();
	reboot(RB_POWER_OFF);
	return 0;
}
