// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2013-2015, Michael Ellerman, IBM Corp.
 */

#define _GNU_SOURCE	/* For CPU_ZERO etc. */

#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <link.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <linux/limits.h>

#include "utils.h"

static char auxv[4096];

int read_file(const char *path, char *buf, size_t count, size_t *len)
{
	ssize_t rc;
	int fd;
	int err;
	char eof;

	fd = open(path, O_RDONLY);
	if (fd < 0)
		return -errno;

	rc = read(fd, buf, count);
	if (rc < 0) {
		err = -errno;
		goto out;
	}

	if (len)
		*len = rc;

	/* Overflow if there are still more bytes after filling the buffer */
	if (rc == count) {
		rc = read(fd, &eof, 1);
		if (rc != 0) {
			err = -EOVERFLOW;
			goto out;
		}
	}

	err = 0;

out:
	close(fd);
	errno = -err;
	return err;
}

int read_file_alloc(const char *path, char **buf, size_t *len)
{
	size_t read_offset = 0;
	size_t buffer_len = 0;
	char *buffer = NULL;
	int err;
	int fd;

	fd = open(path, O_RDONLY);
	if (fd < 0)
		return -errno;

	/*
	 * We don't use stat & preallocate st_size because some non-files
	 * report 0 file size. Instead just dynamically grow the buffer
	 * as needed.
	 */
	while (1) {
		ssize_t rc;

		if (read_offset >= buffer_len / 2) {
			char *next_buffer;

			buffer_len = buffer_len ? buffer_len * 2 : 4096;
			next_buffer = realloc(buffer, buffer_len);
			if (!next_buffer) {
				err = -errno;
				goto out;
			}
			buffer = next_buffer;
		}

		rc = read(fd, buffer + read_offset, buffer_len - read_offset);
		if (rc < 0) {
			err = -errno;
			goto out;
		}

		if (rc == 0)
			break;

		read_offset += rc;
	}

	*buf = buffer;
	if (len)
		*len = read_offset;

	err = 0;

out:
	close(fd);
	if (err)
		free(buffer);
	errno = -err;
	return err;
}

int write_file(const char *path, const char *buf, size_t count)
{
	int fd;
	int err;
	ssize_t rc;

	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd < 0)
		return -errno;

	rc = write(fd, buf, count);
	if (rc < 0) {
		err = -errno;
		goto out;
	}

	if (rc != count) {
		err = -EOVERFLOW;
		goto out;
	}

	err = 0;

out:
	close(fd);
	errno = -err;
	return err;
}

int read_auxv(char *buf, ssize_t buf_size)
{
	int err;

	err = read_file("/proc/self/auxv", buf, buf_size, NULL);
	if (err) {
		perror("Error reading /proc/self/auxv");
		return err;
	}

	return 0;
}

int read_debugfs_file(const char *subpath, char *buf, size_t count)
{
	char path[PATH_MAX] = "/sys/kernel/debug/";

	strncat(path, subpath, sizeof(path) - strlen(path) - 1);

	return read_file(path, buf, count, NULL);
}

int write_debugfs_file(const char *subpath, const char *buf, size_t count)
{
	char path[PATH_MAX] = "/sys/kernel/debug/";

	strncat(path, subpath, sizeof(path) - strlen(path) - 1);

	return write_file(path, buf, count);
}

static int validate_int_parse(const char *buffer, size_t count, char *end)
{
	int err = 0;

	/* Require at least one digit */
	if (end == buffer) {
		err = -EINVAL;
		goto out;
	}

	/* Require all remaining characters be whitespace-ish */
	for (; end < buffer + count; end++) {
		if (*end == '\0')
			break;

		if (*end != ' ' && *end != '\n') {
			err = -EINVAL;
			goto out;
		}
	}

out:
	errno = -err;
	return err;
}

static int parse_bounded_int(const char *buffer, size_t count, intmax_t *result,
			     int base, intmax_t min, intmax_t max)
{
	int err;
	char *end;

	errno = 0;
	*result = strtoimax(buffer, &end, base);

	if (errno)
		return -errno;

	err = validate_int_parse(buffer, count, end);
	if (err)
		goto out;

	if (*result < min || *result > max)
		err = -EOVERFLOW;

out:
	errno = -err;
	return err;
}

static int parse_bounded_uint(const char *buffer, size_t count, uintmax_t *result,
			      int base, uintmax_t max)
{
	int err = 0;
	char *end;

	errno = 0;
	*result = strtoumax(buffer, &end, base);

	if (errno)
		return -errno;

	err = validate_int_parse(buffer, count, end);
	if (err)
		goto out;

	if (*result > max)
		err = -EOVERFLOW;

out:
	errno = -err;
	return err;
}

int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base)
{
	return parse_bounded_int(buffer, count, result, base, INTMAX_MIN, INTMAX_MAX);
}

int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base)
{
	return parse_bounded_uint(buffer, count, result, base, UINTMAX_MAX);
}

int parse_int(const char *buffer, size_t count, int *result, int base)
{
	intmax_t parsed;
	int err = parse_bounded_int(buffer, count, &parsed, base, INT_MIN, INT_MAX);

	*result = parsed;
	return err;
}

int parse_uint(const char *buffer, size_t count, unsigned int *result, int base)
{
	uintmax_t parsed;
	int err = parse_bounded_uint(buffer, count, &parsed, base, UINT_MAX);

	*result = parsed;
	return err;
}

int parse_long(const char *buffer, size_t count, long *result, int base)
{
	intmax_t parsed;
	int err = parse_bounded_int(buffer, count, &parsed, base, LONG_MIN, LONG_MAX);

	*result = parsed;
	return err;
}

int parse_ulong(const char *buffer, size_t count, unsigned long *result, int base)
{
	uintmax_t parsed;
	int err = parse_bounded_uint(buffer, count, &parsed, base, ULONG_MAX);

	*result = parsed;
	return err;
}

int read_long(const char *path, long *result, int base)
{
	int err;
	char buffer[32] = {0};

	err = read_file(path, buffer, sizeof(buffer) - 1, NULL);
	if (err)
		return err;

	return parse_long(buffer, sizeof(buffer), result, base);
}

int read_ulong(const char *path, unsigned long *result, int base)
{
	int err;
	char buffer[32] = {0};

	err = read_file(path, buffer, sizeof(buffer) - 1, NULL);
	if (err)
		return err;

	return parse_ulong(buffer, sizeof(buffer), result, base);
}

int write_long(const char *path, long result, int base)
{
	int err;
	int len;
	char buffer[32];

	/* Decimal only for now: no format specifier for signed hex values */
	if (base != 10) {
		err = -EINVAL;
		goto out;
	}

	len = snprintf(buffer, sizeof(buffer), "%ld", result);
	if (len < 0 || len >= sizeof(buffer)) {
		err = -EOVERFLOW;
		goto out;
	}

	err = write_file(path, buffer, len);

out:
	errno = -err;
	return err;
}

int write_ulong(const char *path, unsigned long result, int base)
{
	int err;
	int len;
	char buffer[32];
	char *fmt;

	switch (base) {
	case 10:
		fmt = "%lu";
		break;
	case 16:
		fmt = "%lx";
		break;
	default:
		err = -EINVAL;
		goto out;
	}

	len = snprintf(buffer, sizeof(buffer), fmt, result);
	if (len < 0 || len >= sizeof(buffer)) {
		err = -errno;
		goto out;
	}

	err = write_file(path, buffer, len);

out:
	errno = -err;
	return err;
}

void *find_auxv_entry(int type, char *auxv)
{
	ElfW(auxv_t) *p;

	p = (ElfW(auxv_t) *)auxv;

	while (p->a_type != AT_NULL) {
		if (p->a_type == type)
			return p;

		p++;
	}

	return NULL;
}

void *get_auxv_entry(int type)
{
	ElfW(auxv_t) *p;

	if (read_auxv(auxv, sizeof(auxv)))
		return NULL;

	p = find_auxv_entry(type, auxv);
	if (p)
		return (void *)p->a_un.a_val;

	return NULL;
}

int pick_online_cpu(void)
{
	int ncpus, cpu = -1;
	cpu_set_t *mask;
	size_t size;

	ncpus = get_nprocs_conf();
	size = CPU_ALLOC_SIZE(ncpus);
	mask = CPU_ALLOC(ncpus);
	if (!mask) {
		perror("malloc");
		return -1;
	}

	CPU_ZERO_S(size, mask);

	if (sched_getaffinity(0, size, mask)) {
		perror("sched_getaffinity");
		goto done;
	}

	/* We prefer a primary thread, but skip 0 */
	for (cpu = 8; cpu < ncpus; cpu += 8)
		if (CPU_ISSET_S(cpu, size, mask))
			goto done;

	/* Search for anything, but in reverse */
	for (cpu = ncpus - 1; cpu >= 0; cpu--)
		if (CPU_ISSET_S(cpu, size, mask))
			goto done;

	printf("No cpus in affinity mask?!\n");

done:
	CPU_FREE(mask);
	return cpu;
}

int bind_to_cpu(int cpu)
{
	cpu_set_t mask;
	int err;

	if (cpu == BIND_CPU_ANY) {
		cpu = pick_online_cpu();
		if (cpu < 0)
			return cpu;
	}

	printf("Binding to cpu %d\n", cpu);

	CPU_ZERO(&mask);
	CPU_SET(cpu, &mask);

	err = sched_setaffinity(0, sizeof(mask), &mask);
	if (err)
		return err;

	return cpu;
}

bool is_ppc64le(void)
{
	struct utsname uts;
	int rc;

	errno = 0;
	rc = uname(&uts);
	if (rc) {
		perror("uname");
		return false;
	}

	return strcmp(uts.machine, "ppc64le") == 0;
}

int read_sysfs_file(char *fpath, char *result, size_t result_size)
{
	char path[PATH_MAX] = "/sys/";

	strncat(path, fpath, PATH_MAX - strlen(path) - 1);

	return read_file(path, result, result_size, NULL);
}

int read_debugfs_int(const char *debugfs_file, int *result)
{
	int err;
	char value[16] = {0};

	err = read_debugfs_file(debugfs_file, value, sizeof(value) - 1);
	if (err)
		return err;

	return parse_int(value, sizeof(value), result, 10);
}

int write_debugfs_int(const char *debugfs_file, int result)
{
	char value[16];

	snprintf(value, 16, "%d", result);

	return write_debugfs_file(debugfs_file, value, strlen(value));
}

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

static void perf_event_attr_init(struct perf_event_attr *event_attr,
					unsigned int type,
					unsigned long config)
{
	memset(event_attr, 0, sizeof(*event_attr));

	event_attr->type = type;
	event_attr->size = sizeof(struct perf_event_attr);
	event_attr->config = config;
	event_attr->read_format = PERF_FORMAT_GROUP;
	event_attr->disabled = 1;
	event_attr->exclude_kernel = 1;
	event_attr->exclude_hv = 1;
	event_attr->exclude_guest = 1;
}

int perf_event_open_counter(unsigned int type,
			    unsigned long config, int group_fd)
{
	int fd;
	struct perf_event_attr event_attr;

	perf_event_attr_init(&event_attr, type, config);

	fd = perf_event_open(&event_attr, 0, -1, group_fd, 0);

	if (fd < 0)
		perror("perf_event_open() failed");

	return fd;
}

int perf_event_enable(int fd)
{
	if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
		perror("error while enabling perf events");
		return -1;
	}

	return 0;
}

int perf_event_disable(int fd)
{
	if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
		perror("error disabling perf events");
		return -1;
	}

	return 0;
}

int perf_event_reset(int fd)
{
	if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
		perror("error resetting perf events");
		return -1;
	}

	return 0;
}

int using_hash_mmu(bool *using_hash)
{
	char line[128];
	FILE *f;
	int rc;

	f = fopen("/proc/cpuinfo", "r");
	FAIL_IF(!f);

	rc = 0;
	while (fgets(line, sizeof(line), f) != NULL) {
		if (!strcmp(line, "MMU		: Hash\n") ||
		    !strcmp(line, "platform	: Cell\n") ||
		    !strcmp(line, "platform	: PowerMac\n")) {
			*using_hash = true;
			goto out;
		}

		if (strcmp(line, "MMU		: Radix\n") == 0) {
			*using_hash = false;
			goto out;
		}
	}

	rc = -1;
out:
	fclose(f);
	return rc;
}

struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, void *))
{
	struct sigaction sa;
	struct sigaction old_handler;

	sa.sa_sigaction = fn;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	FAIL_IF_EXIT_MSG(sigaction(sig, &sa, &old_handler),
			 "failed to push signal handler");

	return old_handler;
}

struct sigaction pop_signal_handler(int sig, struct sigaction old_handler)
{
	struct sigaction popped;

	FAIL_IF_EXIT_MSG(sigaction(sig, &old_handler, &popped),
			 "failed to pop signal handler");

	return popped;
}
