/*
 * BSD LICENSE
 *
 * Copyright(c) 2017-2019 Intel Corporation. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <dirent.h>

#include "log.h"
#include "types.h"
#include "cap.h"
#include "resctrl_alloc.h"

/**
 * ---------------------------------------
 * Local data structures
 * ---------------------------------------
 */
static const struct pqos_cpuinfo *m_cpu = NULL;

/*
 * COS file names on resctrl file system
 */
static const char *rctl_cpus = "cpus";
static const char *rctl_schemata = "schemata";
static const char *rctl_tasks = "tasks";

int
resctrl_alloc_init(const struct pqos_cpuinfo *cpu, const struct pqos_cap *cap)
{
        if (cpu == NULL || cap == NULL)
		return PQOS_RETVAL_PARAM;

	m_cpu = cpu;

        return PQOS_RETVAL_OK;
}

int
resctrl_alloc_fini(void)
{
        m_cpu = NULL;
        return PQOS_RETVAL_OK;
}

int
resctrl_alloc_get_grps_num(const struct pqos_cap *cap, unsigned *grps_num)
{
	unsigned i;
	unsigned max_rctl_grps = 0;
	int ret = PQOS_RETVAL_OK;

	ASSERT(cap != NULL);
	ASSERT(grps_num != NULL);

	/*
	 * Loop through all caps that have OS support
	 * Find max COS supported by all
	 */
	for (i = 0; i < cap->num_cap; i++) {
		unsigned num_cos = 0;
		const struct pqos_capability *p_cap = &cap->capabilities[i];

		/* get L3 CAT COS num */
		if (p_cap->type == PQOS_CAP_TYPE_L3CA) {
			ret = pqos_l3ca_get_cos_num(cap, &num_cos);
			if (ret != PQOS_RETVAL_OK)
				return ret;

			if (max_rctl_grps == 0)
				max_rctl_grps = num_cos;
			else if (num_cos < max_rctl_grps)
				max_rctl_grps = num_cos;
		}
		/* get L2 CAT COS num */
		if (p_cap->type == PQOS_CAP_TYPE_L2CA) {
			ret = pqos_l2ca_get_cos_num(cap, &num_cos);
			if (ret != PQOS_RETVAL_OK)
				return ret;

			if (max_rctl_grps == 0)
				max_rctl_grps = num_cos;
			else if (num_cos < max_rctl_grps)
				max_rctl_grps = num_cos;
		}
		/* get MBA COS num */
		if (p_cap->type == PQOS_CAP_TYPE_MBA) {
			ret = pqos_mba_get_cos_num(cap, &num_cos);
			if (ret != PQOS_RETVAL_OK)
				return ret;

			if (max_rctl_grps == 0)
				max_rctl_grps = num_cos;
			else if (num_cos < max_rctl_grps)
				max_rctl_grps = num_cos;
		}
	}
	*grps_num = max_rctl_grps;
	return PQOS_RETVAL_OK;
}

/**
 * @brief Converts string into 64-bit unsigned number.
 *
 * Numbers can be in decimal or hexadecimal format.
 *
 * @param [in] s string to be converted into 64-bit unsigned number
 * @param [in] base Numerical base
 * @param [out] Numeric value of the string representing the number
 *
 * @return Operational status
 * @retval PQOS_RETVAL_OK on success
 */
static int
strtouint64(const char *s, int base, uint64_t *value)
{
	char *endptr = NULL;

	ASSERT(s != NULL);
	if (strncasecmp(s, "0x", 2) == 0) {
		base = 16;
		s += 2;
	}

	*value = strtoull(s, &endptr, base);
	if (!(*s != '\0' && (*endptr == '\0' || *endptr == '\n')))
		return PQOS_RETVAL_ERROR;

	return PQOS_RETVAL_OK;
}

FILE *
resctrl_alloc_fopen(const unsigned class_id, const char *name, const char *mode)
{
	FILE *fd;
	char buf[128];
	int result;

	ASSERT(name != NULL);
	ASSERT(mode != NULL);

	memset(buf, 0, sizeof(buf));
	if (class_id == 0)
		result = snprintf(buf, sizeof(buf) - 1,
				  "%s/%s", RESCTRL_PATH, name);
	else
		result = snprintf(buf, sizeof(buf) - 1, "%s/COS%u/%s",
				  RESCTRL_PATH, class_id, name);

	if (result < 0)
		return NULL;

	fd = fopen(buf, mode);
	if (fd == NULL)
		LOG_ERROR("Could not open %s file %s for COS %u\n",
			  name, buf, class_id);

	return fd;
}

/**
 * @brief Closes COS file in resctl filesystem
 *
 * @param[in] fd File descriptor
 *
 * @return Operational status
 * @retval PQOS_RETVAL_OK on success
 */
static int
resctrl_alloc_fclose(FILE *fd)
{
	if (fd == NULL)
		return PQOS_RETVAL_PARAM;

	if (fclose(fd) == 0)
		return PQOS_RETVAL_OK;

	switch (errno) {
	case EBADF:
		LOG_ERROR("Invalid file descriptor!\n");
		break;
	case EINVAL:
		LOG_ERROR("Invalid file arguments!\n");
		break;
	default:
		LOG_ERROR("Error closing file!\n");
	}

	return PQOS_RETVAL_ERROR;
}

/*
 * ---------------------------------------
 * CPU mask structures and utility functions
 * ---------------------------------------
 */


int
resctrl_alloc_cpumask_write(const unsigned class_id,
	                    const struct resctrl_cpumask *mask)
{
	int ret = PQOS_RETVAL_OK;
	FILE *fd;

	fd = resctrl_alloc_fopen(class_id, rctl_cpus, "w");
	if (fd == NULL)
		return PQOS_RETVAL_ERROR;

	ret = resctrl_cpumask_write(fd, mask);
	if (ret == PQOS_RETVAL_OK)
		ret = resctrl_alloc_fclose(fd);
	else
		resctrl_alloc_fclose(fd);

	return ret;
}

int
resctrl_alloc_cpumask_read(const unsigned class_id,
			   struct resctrl_cpumask *mask)
{
	int ret;
	FILE *fd;

	fd = resctrl_alloc_fopen(class_id, rctl_cpus, "r");
	if (fd == NULL)
		return PQOS_RETVAL_ERROR;

	ret = resctrl_cpumask_read(fd, mask);

	if (resctrl_alloc_fclose(fd) != PQOS_RETVAL_OK)
		return PQOS_RETVAL_ERROR;

	return ret;
}

/*
 * ---------------------------------------
 * Schemata structures and utility functions
 * ---------------------------------------
 */

void
resctrl_alloc_schemata_fini(struct resctrl_alloc_schemata *schemata)
{
	if (schemata->l2ca != NULL) {
		free(schemata->l2ca);
		schemata->l2ca = NULL;
	}
	if (schemata->l3ca != NULL) {
		free(schemata->l3ca);
		schemata->l3ca = NULL;
	}
	if (schemata->mba != NULL) {
		free(schemata->mba);
		schemata->mba = NULL;
	}
}

int
resctrl_alloc_schemata_init(const unsigned class_id,
			    const struct pqos_cap *cap,
			    const struct pqos_cpuinfo *cpu,
			    struct resctrl_alloc_schemata *schemata)
{
	int ret = PQOS_RETVAL_OK;
	int retval;
	unsigned num_cos, num_ids, i;

	ASSERT(schemata != NULL);

	memset(schemata, 0, sizeof(struct resctrl_alloc_schemata));

	/* L2 */
	retval = pqos_l2ca_get_cos_num(cap, &num_cos);
	if (retval == PQOS_RETVAL_OK && class_id < num_cos) {
		unsigned *l2ids = NULL;
		int cdp_enabled;

		l2ids = pqos_cpu_get_l2ids(cpu, &num_ids);
		if (l2ids == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		free(l2ids);

		schemata->l2ca_num = num_ids;
		schemata->l2ca = calloc(num_ids, sizeof(struct pqos_l2ca));
		if (schemata->l2ca == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		ret = pqos_l2ca_cdp_enabled(cap, NULL, &cdp_enabled);
		if (ret != PQOS_RETVAL_OK)
			goto resctrl_alloc_schemata_init_exit;

		/* fill class_id */
		for (i = 0; i < num_ids; i++) {
			schemata->l2ca[i].class_id = class_id;
			schemata->l2ca[i].cdp = cdp_enabled;
		}
	}

	/* L3 */
	retval = pqos_l3ca_get_cos_num(cap, &num_cos);
	if (retval == PQOS_RETVAL_OK && class_id < num_cos) {
		unsigned *sockets = NULL;
		int cdp_enabled;

		sockets = pqos_cpu_get_sockets(cpu, &num_ids);
		if (sockets == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		free(sockets);

		schemata->l3ca_num = num_ids;
		schemata->l3ca = calloc(num_ids, sizeof(struct pqos_l3ca));
		if (schemata->l3ca == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		ret = pqos_l3ca_cdp_enabled(cap, NULL, &cdp_enabled);
		if (ret != PQOS_RETVAL_OK)
			goto resctrl_alloc_schemata_init_exit;

		/* fill class_id and cdp values */
		for (i = 0; i < num_ids; i++) {
			schemata->l3ca[i].class_id = class_id;
			schemata->l3ca[i].cdp = cdp_enabled;
		}
	}

	/* MBA */
	retval = pqos_mba_get_cos_num(cap, &num_cos);
	if (retval == PQOS_RETVAL_OK && class_id < num_cos) {
		unsigned *sockets = NULL;
		int ctrl_enabled;

		sockets = pqos_cpu_get_sockets(cpu, &num_ids);
		if (sockets == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		free(sockets);

		schemata->mba_num = num_ids;
		schemata->mba = calloc(num_ids, sizeof(struct pqos_mba));
		if (schemata->mba == NULL) {
			ret = PQOS_RETVAL_ERROR;
			goto resctrl_alloc_schemata_init_exit;
		}

		ret = pqos_mba_ctrl_enabled(cap, NULL, &ctrl_enabled);
		if (ret != PQOS_RETVAL_OK)
			goto resctrl_alloc_schemata_init_exit;

		/* fill ctrl and class_id */
		for (i = 0; i < num_ids; i++) {
			schemata->mba[i].ctrl = ctrl_enabled;
			schemata->mba[i].class_id = class_id;
		}
	}

 resctrl_alloc_schemata_init_exit:
	/* Deallocate memory in case of error */
	if (ret != PQOS_RETVAL_OK)
		resctrl_alloc_schemata_fini(schemata);

	return ret;
}

/**
 * @brief Schemata type
 */
enum resctrl_alloc_schemata_type {
	RESCTRL_ALLOC_SCHEMATA_TYPE_NONE,   /**< unknown */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L2,     /**< L2 CAT without CDP */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L2CODE, /**< L2 CAT code */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L2DATA, /**< L2 CAT data */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L3,     /**< L3 CAT without CDP */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L3CODE, /**< L3 CAT code */
	RESCTRL_ALLOC_SCHEMATA_TYPE_L3DATA, /**< L3 CAT data */
	RESCTRL_ALLOC_SCHEMATA_TYPE_MB,     /**< MBA data */
};

/**
 * @brief Determine allocation type
 *
 * @param [in] str resctrl label
 *
 * @return Allocation type
 */
static int
resctrl_alloc_schemata_type_get(const char *str)
{
	int type = RESCTRL_ALLOC_SCHEMATA_TYPE_NONE;

	if (strcasecmp(str, "L2") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L2;
	else if (strcasecmp(str, "L2CODE") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L2CODE;
	else if (strcasecmp(str, "L2DATA") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L2DATA;
	else if (strcasecmp(str, "L3") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L3;
	else if (strcasecmp(str, "L3CODE") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L3CODE;
	else if (strcasecmp(str, "L3DATA") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_L3DATA;
	else if (strcasecmp(str, "MB") == 0)
		type = RESCTRL_ALLOC_SCHEMATA_TYPE_MB;

	return type;
}

/**
 * @brief Fill schemata structure
 *
 * @param [in] res_id Resource id
 * @param [in] value Ways mask/Memory B/W rate
 * @param [in] type Schemata type
 * @param [out] schemata Schemata structure
 *
 * @return Operational status
 * @retval PQOS_RETVAL_OK on success
 */
static int
resctrl_alloc_schemata_set(const unsigned res_id,
	     const uint64_t value,
	     const int type,
	     struct resctrl_alloc_schemata *schemata)
{
	if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L2) {
		if (schemata->l2ca_num <= res_id)
			return PQOS_RETVAL_ERROR;
		schemata->l2ca[res_id].u.ways_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L2CODE) {
		if (schemata->l2ca_num <= res_id || !schemata->l2ca[res_id].cdp)
			return PQOS_RETVAL_ERROR;
		schemata->l2ca[res_id].u.s.code_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L2DATA) {
		if (schemata->l2ca_num <= res_id || !schemata->l2ca[res_id].cdp)
			return PQOS_RETVAL_ERROR;
		schemata->l2ca[res_id].u.s.data_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L3) {
		if (schemata->l3ca_num <= res_id || schemata->l3ca[res_id].cdp)
			return PQOS_RETVAL_ERROR;
		schemata->l3ca[res_id].u.ways_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L3CODE) {
		if (schemata->l3ca_num <= res_id || !schemata->l3ca[res_id].cdp)
			return PQOS_RETVAL_ERROR;
		schemata->l3ca[res_id].u.s.code_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_L3DATA) {
		if (schemata->l3ca_num <= res_id || !schemata->l3ca[res_id].cdp)
			return PQOS_RETVAL_ERROR;
		schemata->l3ca[res_id].u.s.data_mask = value;

	} else if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_MB) {
		if (schemata->mba_num <= res_id)
			return PQOS_RETVAL_ERROR;
		schemata->mba[res_id].mb_max = value;
	}

	return PQOS_RETVAL_OK;
}

int
resctrl_alloc_schemata_read(const unsigned class_id,
			    struct resctrl_alloc_schemata *schemata)
{
	int ret = PQOS_RETVAL_OK;
	FILE *fd = NULL;
	int type = RESCTRL_ALLOC_SCHEMATA_TYPE_NONE;
	char *p = NULL, *q = NULL, *saveptr = NULL;
	const size_t buf_size = 16 * 1024;
	char *buf = calloc(buf_size, sizeof(*buf));

	if (buf == NULL) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_read_exit;
	}

	ASSERT(schemata != NULL);

	fd = resctrl_alloc_fopen(class_id, rctl_schemata, "r");
	if (fd == NULL) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_read_exit;
	}

	if ((schemata->l3ca_num > 0 && schemata->l3ca == NULL)
	    || (schemata->l2ca_num > 0 && schemata->l2ca == NULL)
	    || (schemata->mba_num > 0 && schemata->mba == NULL)) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_read_exit;
	}

	while (fgets(buf, buf_size, fd) != NULL) {
		q = buf;
		/**
		 * Trim white spaces
		 */
		while (isspace(*q))
			q++;

		/**
		 * Determine allocation type
		 */
		p = strchr(q, ':');
		if (p == NULL) {
			ret = PQOS_RETVAL_ERROR;
			break;
		}
		*p = '\0';
		type = resctrl_alloc_schemata_type_get(q);

		/* Skip unknown label */
		if (type == RESCTRL_ALLOC_SCHEMATA_TYPE_NONE)
			continue;

		/**
		 * Parse COS masks
		 */
		for (++p; ; p = NULL) {
			char *token = NULL;
			uint64_t id = 0;
			uint64_t value = 0;
			unsigned base = (type == RESCTRL_ALLOC_SCHEMATA_TYPE_MB
				         ? 10 : 16);

			token = strtok_r(p, ";", &saveptr);
			if (token == NULL)
				break;

			q = strchr(token, '=');
			if (q == NULL) {
				ret = PQOS_RETVAL_ERROR;
				goto resctrl_alloc_schemata_read_exit;
			}
			*q = '\0';

			ret = strtouint64(token, 10, &id);
			if (ret != PQOS_RETVAL_OK)
				goto resctrl_alloc_schemata_read_exit;

			ret = strtouint64(q + 1, base, &value);
			if (ret != PQOS_RETVAL_OK)
				goto resctrl_alloc_schemata_read_exit;

			ret = resctrl_alloc_schemata_set(id,
				                         value,
				                         type,
				                         schemata);
			if (ret != PQOS_RETVAL_OK)
				goto resctrl_alloc_schemata_read_exit;
		}
	}


 resctrl_alloc_schemata_read_exit:

	if (buf != NULL)
		free(buf);

	/* check if error occurred */
	if (ret == PQOS_RETVAL_OK)
		ret = resctrl_alloc_fclose(fd);
	else if (fd)
		resctrl_alloc_fclose(fd);

	return ret;
}

int
resctrl_alloc_schemata_write(const unsigned class_id,
                             const struct resctrl_alloc_schemata *schemata)
{
	int ret = PQOS_RETVAL_OK;
	unsigned i;
	FILE *fd = NULL;
	const size_t buf_size = 16 * 1024;
	char *buf = calloc(buf_size, sizeof(*buf));

	if (buf == NULL) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_write_exit;
	}

	ASSERT(schemata != NULL);

	fd = resctrl_alloc_fopen(class_id, rctl_schemata, "w");
	if (fd == NULL) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_write_exit;
	}

	/* Enable fully buffered output. File won't be flushed until 16kB
	 * buffer is full */
	if (setvbuf(fd, buf, _IOFBF, buf_size) != 0) {
		ret = PQOS_RETVAL_ERROR;
		goto resctrl_alloc_schemata_write_exit;
	}

	/* L2 without CDP */
	if (schemata->l2ca_num > 0 && !schemata->l2ca[0].cdp) {
		fprintf(fd, "L2:");
		for (i = 0; i < schemata->l2ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
			        schemata->l2ca[i].u.ways_mask);
		}
		fprintf(fd, "\n");
	}

	/* L2 with CDP */
	if (schemata->l2ca_num > 0 && schemata->l2ca[0].cdp) {
		fprintf(fd, "L2CODE:");
		for (i = 0; i < schemata->l2ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
				schemata->l2ca[i].u.s.code_mask);
		}
		fprintf(fd, "\nL2DATA:");
		for (i = 0; i < schemata->l2ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
				schemata->l2ca[i].u.s.data_mask);
		}
		fprintf(fd, "\n");
	}

	/* L3 without CDP */
	if (schemata->l3ca_num > 0 && !schemata->l3ca[0].cdp) {
		fprintf(fd, "L3:");
		for (i = 0; i < schemata->l3ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
				schemata->l3ca[i].u.ways_mask);
		}
		fprintf(fd, "\n");
	}

	/* L3 with CDP */
	if (schemata->l3ca_num > 0 && schemata->l3ca[0].cdp) {
		fprintf(fd, "L3CODE:");
		for (i = 0; i < schemata->l3ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
				schemata->l3ca[i].u.s.code_mask);
		}
		fprintf(fd, "\nL3DATA:");
		for (i = 0; i < schemata->l3ca_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%llx", i, (unsigned long long)
				schemata->l3ca[i].u.s.data_mask);
		}
		fprintf(fd, "\n");
	}

	/* MBA */
	if (schemata->mba_num > 0) {
		fprintf(fd, "MB:");
		for (i = 0; i < schemata->mba_num; i++) {
			if (i > 0)
				fprintf(fd, ";");
			fprintf(fd, "%u=%u", i, schemata->mba[i].mb_max);
		}
		fprintf(fd, "\n");
	}


resctrl_alloc_schemata_write_exit:

	/* check if error occurred */
	if (ret == PQOS_RETVAL_OK)
		ret = resctrl_alloc_fclose(fd);
	else if (fd)
		resctrl_alloc_fclose(fd);

	/* setvbuf buffer should be freed after fclose */
	if (buf != NULL)
		free(buf);

	return ret;
}

/**
 * ---------------------------------------
 * Task utility functions
 * ---------------------------------------
 */

int
resctrl_alloc_task_validate(const pid_t task)
{
	if (kill(task, 0) == 0)
		return PQOS_RETVAL_OK;

	return PQOS_RETVAL_ERROR;
}

int
resctrl_alloc_task_write(const unsigned class_id, const pid_t task)
{
	FILE *fd;
	int ret;

	/* Check if task exists */
	ret = resctrl_alloc_task_validate(task);
	if (ret != PQOS_RETVAL_OK) {
		LOG_ERROR("Task %d does not exist!\n", (int)task);
		return PQOS_RETVAL_PARAM;
	}

	/* Open resctrl tasks file */
	fd = resctrl_alloc_fopen(class_id, rctl_tasks, "w");
	if (fd == NULL)
		return PQOS_RETVAL_ERROR;

	/* Write task ID to file */
	if (fprintf(fd, "%d\n", task) < 0) {
		LOG_ERROR("Failed to write to task %d to file!\n", (int) task);
		resctrl_alloc_fclose(fd);
		return PQOS_RETVAL_ERROR;
	}

	errno = 0;
	ret = resctrl_alloc_fclose(fd);
	if (ret != PQOS_RETVAL_OK && errno == ESRCH) {
		LOG_ERROR("Task %d does not exist! fclose\n", (int)task);
		ret = PQOS_RETVAL_PARAM;
	}

	return ret;
}

unsigned *
resctrl_alloc_task_read(unsigned class_id, unsigned *count)
{
	FILE *fd;
	unsigned *tasks = NULL, idx = 0;
	int ret;
	char buf[128];
	struct linked_list {
		uint64_t task_id;
		struct linked_list *next;
	} head, *current = NULL;

	/* Open resctrl tasks file */
	fd = resctrl_alloc_fopen(class_id, rctl_tasks, "r");
	if (fd == NULL)
		return NULL;

	head.next = NULL;
	current = &head;
	memset(buf, 0, sizeof(buf));
	while (fgets(buf, sizeof(buf), fd) != NULL) {
		uint64_t tmp;
		struct linked_list *p = NULL;

		ret = strtouint64(buf, 10, &tmp);
		if (ret != PQOS_RETVAL_OK)
			goto resctrl_alloc_task_read_exit_clean;
		p = malloc(sizeof(head));
		if (p == NULL)
			goto resctrl_alloc_task_read_exit_clean;
		p->task_id = tmp;
		p->next = NULL;
		current->next = p;
		current = p;
		idx++;
	}

	/* if no pids found then allocate empty buffer to be returned */
	if (idx == 0)
		tasks = (unsigned *) calloc(1, sizeof(tasks[0]));
	else
		tasks = (unsigned *) malloc(idx * sizeof(tasks[0]));
	if (tasks == NULL)
		goto resctrl_alloc_task_read_exit_clean;

	*count = idx;
	current = head.next;
	idx = 0;
	while (current != NULL) {
		tasks[idx++] = current->task_id;
		current = current->next;
	}

 resctrl_alloc_task_read_exit_clean:
	resctrl_alloc_fclose(fd);
	current = head.next;
	while (current != NULL) {
		struct linked_list *tmp = current->next;

		free(current);
		current = tmp;
	}
	return tasks;
}

int
resctrl_alloc_task_search(unsigned *class_id,
                          const struct pqos_cap *cap,
                          const pid_t task)
{
	FILE *fd;
	unsigned i, max_cos = 0;
	int ret;

	/* Check if task exists */
	ret = resctrl_alloc_task_validate(task);
	if (ret != PQOS_RETVAL_OK) {
		LOG_ERROR("Task %d does not exist!\n", (int)task);
		return PQOS_RETVAL_PARAM;
	}

	/* Get number of COS */
	ret = resctrl_alloc_get_grps_num(cap, &max_cos);
	if (ret != PQOS_RETVAL_OK)
		return ret;

	/**
	 * Starting at highest COS - search all COS tasks files for task ID
	 */
	for (i = (max_cos - 1); (int)i >= 0; i--) {
		uint64_t tid = 0;
		char buf[128];

		/* Open resctrl tasks file */
		fd = resctrl_alloc_fopen(i, rctl_tasks, "r");
		if (fd == NULL)
			return PQOS_RETVAL_ERROR;

		/* Search tasks file for specified task ID */
		memset(buf, 0, sizeof(buf));
		while (fgets(buf, sizeof(buf), fd) != NULL) {
			ret = strtouint64(buf, 10, &tid);
			if (ret != PQOS_RETVAL_OK)
				continue;

			if (task == (pid_t)tid) {
				*class_id = i;
				if (resctrl_alloc_fclose(fd) != PQOS_RETVAL_OK)
					return PQOS_RETVAL_ERROR;

				return PQOS_RETVAL_OK;
			}
		}
		if (resctrl_alloc_fclose(fd) != PQOS_RETVAL_OK)
			return PQOS_RETVAL_ERROR;
	}
	/* If not found in any COS group - return error */
	LOG_ERROR("Failed to get association for task %d!\n", (int)task);
	return PQOS_RETVAL_ERROR;
}

int
resctrl_alloc_task_file_check(const unsigned class_id, unsigned *found)
{
	FILE *fd;
	char buf[128];

	/* Open resctrl tasks file */
	fd = resctrl_alloc_fopen(class_id, rctl_tasks, "r");
	if (fd == NULL)
		return PQOS_RETVAL_ERROR;

	/* Search tasks file for any task ID */
	memset(buf, 0, sizeof(buf));
	if (fgets(buf, sizeof(buf), fd) != NULL)
		*found = 1;

	if (resctrl_alloc_fclose(fd) != PQOS_RETVAL_OK)
		return PQOS_RETVAL_ERROR;

	return PQOS_RETVAL_OK;
}

int
resctrl_alloc_assoc_set(const unsigned lcore, const unsigned class_id)
{
	int ret;
	struct resctrl_cpumask mask;

	ret = resctrl_alloc_cpumask_read(class_id, &mask);
	if (ret != PQOS_RETVAL_OK)
		return ret;

	resctrl_cpumask_set(lcore, &mask);

	ret = resctrl_alloc_cpumask_write(class_id, &mask);

	return ret;
}

int
resctrl_alloc_assoc_get(const unsigned lcore, unsigned *class_id)
{
	int ret;
	unsigned grps;
	unsigned i;
	struct resctrl_cpumask mask;
	const struct pqos_cap *cap;

	_pqos_cap_get(&cap, NULL);

	ret = resctrl_alloc_get_grps_num(cap, &grps);
	if (ret != PQOS_RETVAL_OK)
		return ret;

	for (i = 0; i < grps; i++) {
		ret = resctrl_alloc_cpumask_read(i, &mask);
		if (ret != PQOS_RETVAL_OK)
			return ret;

		if (resctrl_cpumask_get(lcore, &mask)) {
			*class_id = i;
                        return PQOS_RETVAL_OK;
		}
	}

	return ret;
}

int
resctrl_alloc_assoc_set_pid(const pid_t task, const unsigned class_id)
{
	/* Write to tasks file */
	return resctrl_alloc_task_write(class_id, task);
}

int
resctrl_alloc_assoc_get_pid(const pid_t task, unsigned *class_id)
{
	const struct pqos_cap *cap;

	_pqos_cap_get(&cap, NULL);

	/* Search tasks files */
	return resctrl_alloc_task_search(class_id, cap, task);
}

int
resctrl_alloc_get_unused_group(const unsigned grps_num, unsigned *group_id)
{
        unsigned used_groups[grps_num];
        unsigned i;
        int ret;

        if (group_id == NULL || grps_num == 0)
                return PQOS_RETVAL_PARAM;

        memset(used_groups, 0, sizeof(used_groups));

        for (i = grps_num - 1; i > 0; i--) {
                struct resctrl_cpumask mask;
                unsigned j;

                ret = resctrl_alloc_cpumask_read(i, &mask);
                if (ret != PQOS_RETVAL_OK)
                        return ret;

                for (j = 0; j < sizeof(mask.tab); j++)
                        if (mask.tab[j] > 0) {
                                used_groups[i] = 1;
                                break;
                        }

                if (used_groups[i] == 1)
                        continue;

                ret = resctrl_alloc_task_file_check(i, &used_groups[i]);
                if (ret != PQOS_RETVAL_OK)
                        return ret;
        }

        /* Find unused COS */
        for (i = grps_num - 1; i > 0; i--) {
                if (used_groups[i] == 0) {
                        *group_id = i;
                        return PQOS_RETVAL_OK;
                }
        }

        return PQOS_RETVAL_RESOURCE;
}
