#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>

#include "bitmask.h"
#include "cpuset.h"
#include "common.h"
#include "cpuinfo.h"

#if HAVE_LINUX_MEMPOLICY_H

#define CPUINFO_FILE		"/proc/cpuinfo"
#define SCHEDSTAT_FILE		"/proc/schedstat"
#define CGROUPINFO_FILE		"/proc/cgroups"
#define SYS_CPU_DIR		"/sys/devices/system/cpu"
#define LIST_PRESENT_CPU_FILE	"/sys/devices/system/cpu/present"
#define LIST_ONLINE_CPU_FILE	"/sys/devices/system/cpu/online"

struct cpuinfo *cpus;
int ncpus;
int cpus_nbits;

/* get cpu_baseinfo from /proc/cpuinfo */
static int get_cpu_baseinfo(void)
{
	FILE *fp = NULL;
	char buf[BUFFSIZE];
	char *istr = NULL, *valstr = NULL, *saveptr = NULL;
	int ci = 0;
	int data = 0;

	/* get the number of cpus including offline cpus */
	if (!ncpus) {
		ncpus = get_ncpus();
		if (ncpus <= 0)
			return -1;
	}

	if (cpus != NULL) {
		free(cpus);
		cpus = NULL;
	}

	/* allocate the memory space for cpus */
	cpus = (struct cpuinfo *)malloc(sizeof(*cpus) * ncpus);
	if (cpus == NULL)
		return -1;
	memset(cpus, 0, sizeof(*cpus) * ncpus);

	/* open file /proc/cpuinfo */
	if ((fp = fopen(CPUINFO_FILE, "r")) == NULL)
		return -1;

	/* get cpuinfo */
	while (fgets(buf, sizeof(buf), fp) != NULL) {
		istr = strtok_r(buf, "\t", &saveptr);
		valstr = index(saveptr, ':');
		if (valstr == NULL)
			continue;
		valstr++;
		sscanf(valstr, " %d\n", &data);
		if (!strcmp(istr, "processor")) {
			if (data >= ncpus) {
				warnx("Warn: wrong cpu index");
				fclose(fp);
				return -1;
			}
			ci = data;
			cpus[ci].online = 1;
		}
	}

	fclose(fp);
	return 0;
}

/*
 * get the cpu bitmask of the online processors
 *
 * return value: 0  - success
 *               -1 - failed
 */
int online_cpumask(struct bitmask *cpumask)
{
	FILE *fp = NULL;
	char buf[BUFFSIZE];
	int i;

	if (cpumask == NULL)
		return -1;
	/*
	 * open file /sys/devices/system/cpu/online and get online
	 * cpulist.
	 */
	if ((fp = fopen(LIST_ONLINE_CPU_FILE, "r")) == NULL) {
		if (get_cpu_baseinfo() != 0)
			return -1;
		for (i = 0; i < ncpus; i++) {
			if (cpus[i].online)
				bitmask_setbit(cpumask, i);
		}
	} else {
		if (fgets(buf, sizeof(buf), fp) == NULL) {
			fclose(fp);
			return -1;
		}
		fclose(fp);

		/* parse present cpu list to bitmap */
		buf[strlen(buf) - 1] = '\0';
		if (bitmask_parselist(buf, cpumask) != 0)
			return -1;
	}

	return 0;
}

/*
 * get the cpu bitmask of the present processors including offline CPUs
 *
 * return value: 0  - success
 *               -1 - failed
 */
int present_cpumask(struct bitmask *cpumask)
{
	FILE *fp = NULL;
	char buf[BUFFSIZE];
	char c_relpath[PATH_MAX];
	int cpu = -1;

	if (cpumask == NULL)
		return -1;
	/*
	 * open file /sys/devices/system/cpu/present and get present
	 * cpulist.
	 */
	if ((fp = fopen(LIST_PRESENT_CPU_FILE, "r")) == NULL) {
		while_each_childdir(SYS_CPU_DIR, "/", c_relpath,
				    sizeof(c_relpath)) {
			if (!strncmp(c_relpath + 1, "cpu", 3)
			    && sscanf(c_relpath + 4, "%d", &cpu) > 0) {
				if (cpu >= 0)
					bitmask_setbit(cpumask, cpu);
			}
		}
	end_while_each_childdir} else {
		if (fgets(buf, sizeof(buf), fp) == NULL) {
			fclose(fp);
			return -1;
		}
		fclose(fp);

		/* parse present cpu list to bitmap */
		buf[strlen(buf) - 1] = '\0';
		if (bitmask_parselist(buf, cpumask) != 0)
			return -1;
	}

	return 0;
}

/*
 * get the number of the processors including offline CPUs
 * We get this number from /sys/devices/system/cpu/present.
 * By analyzing the present cpu list, we get the number of all cpus
 */
int get_ncpus(void)
{
	struct bitmask *bmp = NULL;
	int n = 0;

	/* get the bitmask's len */
	cpus_nbits = cpuset_cpus_nbits();
	if (cpus_nbits <= 0)
		return -1;

	/* allocate the space for bitmask */
	bmp = bitmask_alloc(cpus_nbits);
	if (bmp == NULL)
		return -1;

	if (present_cpumask(bmp)) {
		bitmask_free(bmp);
		return -1;
	}

	/* Number of highest set bit +1 is the number of the CPUs */
	n = bitmask_last(bmp) + 1;
	bitmask_free(bmp);

	return n;
}

/* get the sched domain's info for each cpu */
static int get_sched_domains(void)
{
	FILE *fp = NULL;
	char buf[BUFFSIZE];
	char str1[20], str2[BUFFSIZE];
	int ci = 0;

	/* get the bitmask's len */
	if (!cpus_nbits) {
		cpus_nbits = cpuset_cpus_nbits();
		if (cpus_nbits <= 0) {
			warnx("get cpus nbits failed.");
			return -1;
		}
	}

	/* open file /proc/schedstat */
	if ((fp = fopen(SCHEDSTAT_FILE, "r")) == NULL)
		return -1;

	/* get cpuinfo */
	while (fgets(buf, sizeof(buf), fp) != NULL) {
		sscanf(buf, "%s %s", str1, str2);
		if (!strncmp(str1, "cpu", 3)) {
			ci = atoi(str1 + 3);
			if (ci < 0 || ci >= ncpus) {
				fprintf(stderr, "Warn: wrong cpu index");
				fclose(fp);
				return -1;
			}
		} else if (!strncmp(str1, "domain", 6)) {
			if (!cpus[ci].sched_domain) {
				cpus[ci].sched_domain =
				    bitmask_alloc(cpus_nbits);
				if (!cpus[ci].sched_domain)
					return -1;
			}
			if (bitmask_parsehex(str2, cpus[ci].sched_domain))
				return -1;
		}
	}

	fclose(fp);
	return 0;
}

int getcpuinfo(void)
{
	int i;
	int node = -1;

	/* get the number of cpus including offline cpus */
	if (!ncpus) {
		ncpus = get_ncpus();
		if (ncpus <= 0)
			return -1;
	}

	if (cpus == NULL) {
		if (get_cpu_baseinfo() != 0) {
			warn("get base infomation of cpus from /proc/cpuinfo "
			     "failed.");
			return -1;
		}
	}

	/* which node is every cpu belong to? */
	for (i = 0; i < ncpus; i++) {
		node = cpuset_cpu2node(i);
		if (node == -1)
			warnx("cpu2node failed(cpu = %d)", i);
		cpus[i].nodeid = node;
	}

	/* get sched domain's infomation for each cpu */
	if (get_sched_domains()) {
		warnx("get sched domain's info for each cpu failed.");
		return -1;
	}

	return 0;
}

/* get the number of the cpuset groups */
static int get_num_cpusets(void)
{
	FILE *fp = NULL;
	char buf[BUFFSIZE];
	char subsys_name[BUFFSIZE];
	int num_cgroups = 0;
	int hierarchy;
	int enabled;

	/* open file /proc/cgroups and get num cpusets */
	if ((fp = fopen(CGROUPINFO_FILE, "r")) == NULL)
		return -1;

	while (fgets(buf, sizeof(buf), fp) != NULL) {
		if (!strncmp(buf, "cpuset", 6)) {
			sscanf(buf, "%s\t%d\t%d\t%d\n", subsys_name,
			       &hierarchy, &num_cgroups, &enabled);
		}
	}

	fclose(fp);

	return num_cgroups;
}

static struct cpuset **cpusets;
static int ncpusets;

static int find_domain_cpusets(char *relpath)
{
	struct cpuset *cp = NULL;
	char c_relpath[PATH_MAX];
	int ret = 0;

	if (relpath == NULL) {
		errno = -EFAULT;
		return -1;
	}

	cp = cpuset_alloc();
	if (cp == NULL) {
		errno = -ENOMEM;
		return -1;
	}

	if (cpuset_query(cp, relpath)) {
		cpuset_free(cp);
		return -1;
	}

	if (cpuset_cpus_weight(cp) == 0)
		return 0;

	if (cpuset_cpus_weight(cp) > 0
	    && cpuset_get_iopt(cp, "sched_load_balance") == 1) {
		cpusets[ncpusets] = cp;
		ncpusets++;
		return 0;
	}

	while_each_childdir(cpuset_mountpoint(), relpath, c_relpath,
			    sizeof(c_relpath)) {
		if ((ret = find_domain_cpusets(c_relpath)))
			break;
	}
	end_while_each_childdir;

	return ret;
}

struct bitmask **domains;
int ndomains;

int partition_domains(void)
{
	int num_cpusets = 0;
	int i, j;
	struct bitmask *cpusa = NULL, *cpusb = NULL, *cpusc = NULL;
	int *flg = NULL;
	int ret = 0;

	num_cpusets = get_num_cpusets();
	if (num_cpusets == 0) {
		warnx("cpuset subsystem is't compiled into kernel.");
		return -1;
	}

	if (!cpus_nbits) {
		cpus_nbits = cpuset_cpus_nbits();
		if (!cpus_nbits) {
			warnx("nbits of cpus is wrong.");
			return -1;
		}
	}

	cpusa = bitmask_alloc(cpus_nbits);
	if (cpusa == NULL) {
		warnx("bitmask_alloc for partition domains failed.");
		return -1;
	}

	cpusb = bitmask_alloc(cpus_nbits);
	if (cpusb == NULL) {
		warnx("bitmask_alloc for partition domains failed.");
		ret = -1;
		goto errcpusb;
	}

	cpusc = bitmask_alloc(cpus_nbits);
	if (cpusb == NULL) {
		warnx("bitmask_alloc for partition domains failed.");
		ret = -1;
		goto errcpusc;
	}

	cpusets = (struct cpuset **)malloc(num_cpusets * sizeof(*cpusets));
	if (cpusets == NULL) {
		warnx("alloc cpusets space failed.");
		ret = -1;
		goto errcpusets;
	}

	if ((ret = find_domain_cpusets("/"))) {
		warnx("find domain cpusets failed.");
		goto errfindcpusets;
	}

	flg = (int *)malloc(num_cpusets * sizeof(int));
	if (flg == NULL) {
		warnx("alloc flg failed.");
		ret = -1;
		goto errfindcpusets;
	}
	memset(flg, 0, num_cpusets * sizeof(int));

	ndomains = ncpusets;
restart:
	for (i = 0; i < ncpusets; i++) {
		struct cpuset *cpa = cpusets[i];

		if (flg[i])
			continue;

		cpuset_getcpus(cpa, cpusa);

		for (j = i + 1; j < ncpusets; j++) {
			struct cpuset *cpb = cpusets[j];

			if (flg[j])
				continue;

			cpuset_getcpus(cpb, cpusb);
			if (bitmask_intersects(cpusa, cpusb)) {
				bitmask_or(cpusc, cpusa, cpusb);
				cpuset_setcpus(cpa, cpusc);
				flg[j] = 1;
				ndomains--;
				goto restart;
			}
		}
	}

	domains = (struct bitmask **)malloc(ndomains * sizeof(*domains));
	if (domains == NULL) {
		warnx("alloc domains space failed.");
		ret = -1;
		goto errdomains;
	}

	for (i = 0, j = 0; i < ncpusets; i++) {
		if (flg[i])
			continue;
		domains[j] = bitmask_alloc(cpus_nbits);
		if (cpuset_getcpus(cpusets[i], domains[j])) {
			warnx("cpuset getcpus failed.");
			ret = -1;
			goto errgetdomains;
		}
		j++;
	}
	goto errdomains;

errgetdomains:
	for (i = 0; i < j; i++)
		bitmask_free(domains[i]);
	free(domains);
	domains = NULL;
errdomains:
	free(flg);
errfindcpusets:
	for (i = 0; i < ncpusets; i++)
		cpuset_free(cpusets[i]);
	free(cpusets);
	cpusets = NULL;
	ncpusets = 0;
errcpusets:
	bitmask_free(cpusc);
errcpusc:
	bitmask_free(cpusb);
errcpusb:
	bitmask_free(cpusa);
	return ret;
}

#endif
