// SPDX-License-Identifier: LGPL-2.1-only
/**
 * Copyright IBM Corporation. 2008
 *
 * Author:	Dhaval Giani <dhaval@linux.vnet.ibm.com>
 *
 * Code initiated and designed by Dhaval Giani. All faults are most likely
 * his mistake.
 */

#define _GNU_SOURCE

#include <libcgroup.h>
#include <libcgroup-internal.h>

#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

static void init_cgroup(struct cgroup *cgroup)
{
	cgroup->task_fperm = NO_PERMS;
	cgroup->control_fperm = NO_PERMS;
	cgroup->control_dperm = NO_PERMS;

	cgroup->control_gid = NO_UID_GID;
	cgroup->control_uid = NO_UID_GID;
	cgroup->tasks_gid = NO_UID_GID;
	cgroup->tasks_uid = NO_UID_GID;
}

void init_cgroup_table(struct cgroup *cgroups, size_t count)
{
	size_t i;

	for (i = 0; i < count; ++i)
		init_cgroup(&cgroups[i]);
}

struct cgroup *cgroup_new_cgroup(const char *name)
{
	struct cgroup *cgroup = calloc(1, sizeof(struct cgroup));

	if (!cgroup)
		return NULL;

	init_cgroup(cgroup);
	strncpy(cgroup->name, name, FILENAME_MAX - 1);
	cgroup->name[FILENAME_MAX - 1] = '\0';

	return cgroup;
}

struct cgroup_controller *cgroup_add_controller(struct cgroup *cgroup, const char *name)
{
	struct cgroup_controller *controller;
	int i, ret;

	if (!cgroup)
		return NULL;

	/* Still not sure how to handle the failure here. */
	if (cgroup->index >= CG_CONTROLLER_MAX)
		return NULL;

	/* Still not sure how to handle the failure here. */
	for (i = 0; i < cgroup->index; i++) {
		if (strncmp(name, cgroup->controller[i]->name, CONTROL_NAMELEN_MAX) == 0)
			return NULL;
	}

	controller = calloc(1, sizeof(struct cgroup_controller));
	if (!controller)
		return NULL;

	strncpy(controller->name, name, CONTROL_NAMELEN_MAX - 1);
	controller->name[CONTROL_NAMELEN_MAX - 1] = '\0';

	controller->cgroup = cgroup;
	controller->index = 0;

	if (strcmp(controller->name, CGROUP_FILE_PREFIX) == 0) {
		/*
		 * Operating on the "cgroup" controller is only allowed
		 * on cgroup v2 systems
		 */
		controller->version = CGROUP_V2;
	} else {
		ret = cgroup_get_controller_version(controller->name, &controller->version);
		if (ret) {
			cgroup_dbg("failed to get cgroup version for controller %s\n",
				   controller->name);
			free(controller);
			return NULL;
		}
	}

	cgroup->controller[cgroup->index] = controller;
	cgroup->index++;

	return controller;
}

int cgroup_add_all_controllers(struct cgroup *cgroup)
{
	struct cgroup_controller *cgc;
	struct controller_data info;
	void *handle;
	int ret;

	/* go through the controller list */
	ret = cgroup_get_all_controller_begin(&handle, &info);
	if ((ret != 0) && (ret != ECGEOF)) {
		fprintf(stderr, "cannot read controller data: %s\n", cgroup_strerror(ret));
		return ret;
	}

	while (ret == 0) {
		if (info.hierarchy == 0) {
			/*
			 * the controller is not attached to any
			 * hierarchy skip it.
			 */
			goto next;
		}

		/* add mounted controller to cgroup structure */
		cgc = cgroup_add_controller(cgroup, info.name);
		if (!cgc) {
			ret = ECGINVAL;
			fprintf(stderr, "controller %s can't be added\n", info.name);
			goto end;
		}

next:
		ret = cgroup_get_all_controller_next(&handle, &info);
		if (ret && ret != ECGEOF)
			goto end;
	}

end:
	cgroup_get_all_controller_end(&handle);
	if (ret == ECGEOF)
		ret = 0;
	if (ret)
		fprintf(stderr,	"cgroup_get_controller_begin/next failed (%s)\n",
			cgroup_strerror(ret));

	return ret;
}

static void cgroup_free_value(struct control_value *value)
{
	if (value->multiline_value)
		free(value->multiline_value);
	if (value->prev_name)
		free(value->prev_name);

	free(value);
}

void cgroup_free_controller(struct cgroup_controller *ctrl)
{
	int i;

	for (i = 0; i < ctrl->index; i++)
		cgroup_free_value(ctrl->values[i]);
	ctrl->index = 0;

	free(ctrl);
}

void cgroup_free_controllers(struct cgroup *cgroup)
{
	int i;

	if (!cgroup)
		return;

	for (i = 0; i < cgroup->index; i++)
		cgroup_free_controller(cgroup->controller[i]);

	cgroup->index = 0;
}

void cgroup_free(struct cgroup **cgroup)
{
	struct cgroup *cg = *cgroup;

	/* Passing NULL pointers is OK. We just return. */
	if (!cg)
		return;

	cgroup_free_controllers(cg);
	free(cg);
	*cgroup = NULL;
}

int cgroup_add_value_string(struct cgroup_controller *controller, const char *name,
			    const char *value)
{
	int i;
	struct control_value *cntl_value;

	if (!controller)
		return ECGINVAL;

	if (controller->index >= CG_NV_MAX)
		return ECGMAXVALUESEXCEEDED;

	for (i = 0; i < controller->index && i < CG_NV_MAX; i++) {
		if (!strcmp(controller->values[i]->name, name))
			return ECGVALUEEXISTS;
	}

	cntl_value = calloc(1, sizeof(struct control_value));
	if (!cntl_value)
		return ECGCONTROLLERCREATEFAILED;

	strncpy(cntl_value->name, name, sizeof(cntl_value->name));
	cntl_value->name[sizeof(cntl_value->name)-1] = '\0';

	if (value) {
		if (strlen(value) >= sizeof(cntl_value->value)) {
			fprintf(stderr, "value exceeds the maximum of %ld characters\n",
				sizeof(cntl_value->value) - 1);
			free(cntl_value);
			return ECGCONFIGPARSEFAIL;
		}

		strncpy(cntl_value->value, value, sizeof(cntl_value->value));
		cntl_value->value[sizeof(cntl_value->value)-1] = '\0';
		cntl_value->dirty = true;
	}

	controller->values[controller->index] = cntl_value;
	controller->index++;

	return 0;
}

int cgroup_add_value_int64(struct cgroup_controller *controller, const char *name, int64_t value)
{
	char *val;
	int ret;

	ret = asprintf(&val, "%"PRId64, value);
	if (ret < 0) {
		last_errno = errno;
		return ECGOTHER;
	}

	ret = cgroup_add_value_string(controller, name, val);
	free(val);

	return ret;
}

int cgroup_add_value_uint64(struct cgroup_controller *controller, const char *name,
			    u_int64_t value)
{
	char *val;
	int ret;

	ret = asprintf(&val, "%" PRIu64, value);
	if (ret < 0) {
		last_errno = errno;
		return ECGOTHER;
	}

	ret = cgroup_add_value_string(controller, name, val);
	free(val);

	return ret;
}

int cgroup_add_value_bool(struct cgroup_controller *controller, const char *name, bool value)
{
	char *val;
	int ret;

	if (value)
		val = strdup("1");
	else
		val = strdup("0");
	if (!val) {
		last_errno = errno;
		return ECGOTHER;
	}

	ret = cgroup_add_value_string(controller, name, val);
	free(val);

	return ret;
}

int cgroup_remove_value(struct cgroup_controller * const controller, const char * const name)
{
	int i;

	for (i = 0; i < controller->index; i++) {
		if (strcmp(controller->values[i]->name, name) == 0) {
			cgroup_free_value(controller->values[i]);

			if (i == (controller->index - 1)) {
				/* This is the last entry in the table. There's nothing to move */
				controller->index--;
			} else {
				memmove(&controller->values[i],	&controller->values[i + 1],
				sizeof(struct control_value *) * (controller->index - i - 1));
				controller->index--;
			}
			return 0;
		}
	}

	return ECGROUPNOTEXIST;
}

int cgroup_compare_controllers(struct cgroup_controller *cgca, struct cgroup_controller *cgcb)
{
	int i;

	if (!cgca || !cgcb)
		return ECGINVAL;

	if (strcmp(cgca->name, cgcb->name))
		return ECGCONTROLLERNOTEQUAL;

	if (cgca->index != cgcb->index)
		return ECGCONTROLLERNOTEQUAL;

	for (i = 0; i < cgca->index; i++) {
		struct control_value *cva = cgca->values[i];
		struct control_value *cvb = cgcb->values[i];

		if (strcmp(cva->name, cvb->name))
			return ECGCONTROLLERNOTEQUAL;

		if (strcmp(cva->value, cvb->value))
			return ECGCONTROLLERNOTEQUAL;
	}

	return 0;
}

int cgroup_compare_cgroup(struct cgroup *cgroup_a, struct cgroup *cgroup_b)
{
	int i;

	if (!cgroup_a || !cgroup_b)
		return ECGINVAL;

	if (strcmp(cgroup_a->name, cgroup_b->name))
		return ECGROUPNOTEQUAL;

	if (cgroup_a->tasks_uid != cgroup_b->tasks_uid)
		return ECGROUPNOTEQUAL;

	if (cgroup_a->tasks_gid != cgroup_b->tasks_gid)
		return ECGROUPNOTEQUAL;

	if (cgroup_a->control_uid != cgroup_b->control_uid)
		return ECGROUPNOTEQUAL;

	if (cgroup_a->control_gid != cgroup_b->control_gid)
		return ECGROUPNOTEQUAL;

	if (cgroup_a->index != cgroup_b->index)
		return ECGROUPNOTEQUAL;

	for (i = 0; i < cgroup_a->index; i++) {
		struct cgroup_controller *cgca = cgroup_a->controller[i];
		struct cgroup_controller *cgcb = cgroup_b->controller[i];

		if (cgroup_compare_controllers(cgca, cgcb))
			return ECGCONTROLLERNOTEQUAL;
	}

	return 0;
}

int cgroup_set_uid_gid(struct cgroup *cgroup, uid_t tasks_uid, gid_t tasks_gid, uid_t control_uid,
		       gid_t control_gid)
{
	if (!cgroup)
		return ECGINVAL;

	cgroup->tasks_uid = tasks_uid;
	cgroup->tasks_gid = tasks_gid;
	cgroup->control_uid = control_uid;
	cgroup->control_gid = control_gid;

	return 0;
}

int cgroup_get_uid_gid(struct cgroup *cgroup, uid_t *tasks_uid, gid_t *tasks_gid,
		       uid_t *control_uid, gid_t *control_gid)
{
	if (!cgroup)
		return ECGINVAL;

	*tasks_uid = cgroup->tasks_uid;
	*tasks_gid = cgroup->tasks_gid;
	*control_uid = cgroup->control_uid;
	*control_gid = cgroup->control_gid;

	return 0;
}

struct cgroup_controller *cgroup_get_controller(struct cgroup *cgroup, const char *name)
{
	int i;
	struct cgroup_controller *cgc;

	if (!cgroup)
		return NULL;

	for (i = 0; i < cgroup->index; i++) {
		cgc = cgroup->controller[i];

		if (!strcmp(cgc->name, name))
			return cgc;
	}

	return NULL;
}

int cgroup_get_value_string(struct cgroup_controller *controller, const char *name, char **value)
{
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			*value = strdup(val->value);

			if (!*value)
				return ECGOTHER;

			return 0;
		}
	}

	return ECGROUPVALUENOTEXIST;

}

int cgroup_set_value_string(struct cgroup_controller *controller, const char *name,
			    const char *value)
{
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			strncpy(val->value, value, CG_VALUE_MAX);
			val->value[sizeof(val->value)-1] = '\0';
			val->dirty = true;
			return 0;
		}
	}

	return cgroup_add_value_string(controller, name, value);
}

int cgroup_get_value_int64(struct cgroup_controller *controller, const char *name, int64_t *value)
{
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			if (sscanf(val->value, "%" SCNd64, value) != 1)
				return ECGINVAL;

			return 0;
		}
	}

	return ECGROUPVALUENOTEXIST;
}

int cgroup_set_value_int64(struct cgroup_controller *controller, const char *name, int64_t value)
{
	int ret;
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			ret = snprintf(val->value, sizeof(val->value), "%" PRId64, value);
			if (ret >= sizeof(val->value))
				return ECGINVAL;

			val->dirty = true;
			return 0;
		}
	}

	return cgroup_add_value_int64(controller, name, value);
}

int cgroup_get_value_uint64(struct cgroup_controller *controller, const char *name,
			    u_int64_t *value)
{
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			if (sscanf(val->value, "%" SCNu64, value) != 1)
				return ECGINVAL;

			return 0;
		}
	}

	return ECGROUPVALUENOTEXIST;
}

int cgroup_set_value_uint64(struct cgroup_controller *controller, const char *name,
			    u_int64_t value)
{
	int ret;
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			ret = snprintf(val->value, sizeof(val->value), "%" PRIu64, value);
			if (ret >= sizeof(val->value))
				return ECGINVAL;

			val->dirty = true;
			return 0;
		}
	}

	return cgroup_add_value_uint64(controller, name, value);
}

int cgroup_get_value_bool(struct cgroup_controller *controller, const char *name, bool *value)
{
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			int cgc_val;

			if (sscanf(val->value, "%d", &cgc_val) != 1)
				return ECGINVAL;

			if (cgc_val)
				*value = true;
			else
				*value = false;

			return 0;
		}
	}

	return ECGROUPVALUENOTEXIST;
}

int cgroup_set_value_bool(struct cgroup_controller *controller, const char *name, bool value)
{
	int ret;
	int i;

	if (!controller)
		return ECGINVAL;

	for (i = 0; i < controller->index; i++) {
		struct control_value *val = controller->values[i];

		if (!strcmp(val->name, name)) {
			if (value)
				ret = snprintf(val->value, sizeof(val->value), "1");
			else
				ret = snprintf(val->value, sizeof(val->value), "0");

			if (ret >= sizeof(val->value))
				return ECGINVAL;

			val->dirty = true;
			return 0;

		}
	}

	return cgroup_add_value_bool(controller, name, value);
}

struct cgroup *create_cgroup_from_name_value_pairs(const char *name,
						   struct control_value *name_value, int nv_number)
{
	struct cgroup_controller *cgc;
	struct cgroup *src_cgroup;
	char con[FILENAME_MAX];

	int ret;
	int i;

	/* create source cgroup */
	src_cgroup = cgroup_new_cgroup(name);
	if (!src_cgroup) {
		fprintf(stderr, "can't create cgroup: %s\n", cgroup_strerror(ECGFAIL));
		goto scgroup_err;
	}

	/* Add pairs name-value to relevant controllers of this cgroup. */
	for (i = 0; i < nv_number; i++) {

		if ((strchr(name_value[i].name, '.')) == NULL) {
			fprintf(stderr, "wrong -r  parameter (%s=%s)\n", name_value[i].name,
				name_value[i].value);
			goto scgroup_err;
		}

		strncpy(con, name_value[i].name, FILENAME_MAX - 1);
		con[FILENAME_MAX - 1] = '\0';

		strtok(con, ".");

		/*
		 * find out whether we have to add the controller or
		 * cgroup already contains it.
		 */
		cgc = cgroup_get_controller(src_cgroup, con);
		if (!cgc) {
			/* add relevant controller */
			cgc = cgroup_add_controller(src_cgroup, con);
			if (!cgc) {
				fprintf(stderr, "controller %s can't be add\n",	con);
				goto scgroup_err;
			}
		}

		/* add name-value pair to this controller */
		ret = cgroup_add_value_string(cgc, name_value[i].name, name_value[i].value);
		if (ret) {
			fprintf(stderr, "name-value pair %s=%s can't be set\n",	name_value[i].name,
				name_value[i].value);
			goto scgroup_err;
		}
	}

	return src_cgroup;

scgroup_err:
	cgroup_free(&src_cgroup);

	return NULL;
}

int cgroup_get_value_name_count(struct cgroup_controller *controller)
{
	if (!controller)
		return -1;

	return controller->index;
}


char *cgroup_get_value_name(struct cgroup_controller *controller, int index)
{

	if (!controller)
		return NULL;

	if (index < controller->index)
		return (controller->values[index])->name;
	else
		return NULL;
}

char *cgroup_get_cgroup_name(struct cgroup *cgroup)
{
	if (!cgroup)
		return NULL;

	return cgroup->name;
}
