/* Author : Stephen Smalley, <sds@epoch.ncsc.mil> */

/* FLASK */

/*
 * Implementation of the SID table type.
 */

#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>

#include <sepol/policydb/sidtab.h>

#include <sepol/policydb/flask.h>

#define SIDTAB_HASH(sid) \
(sid & SIDTAB_HASH_MASK)

#define INIT_SIDTAB_LOCK(s)
#define SIDTAB_LOCK(s)
#define SIDTAB_UNLOCK(s)

int sepol_sidtab_init(sidtab_t * s)
{
	int i;

	s->htable = malloc(sizeof(sidtab_ptr_t) * SIDTAB_SIZE);
	if (!s->htable)
		return -ENOMEM;
	for (i = 0; i < SIDTAB_SIZE; i++)
		s->htable[i] = (sidtab_ptr_t) NULL;
	s->nel = 0;
	s->next_sid = 1;
	s->shutdown = 0;
	INIT_SIDTAB_LOCK(s);
	return 0;
}

int sepol_sidtab_insert(sidtab_t * s, sepol_security_id_t sid,
			context_struct_t * context)
{
	int hvalue;
	sidtab_node_t *prev, *cur, *newnode;

	if (!s || !s->htable)
		return -ENOMEM;

	hvalue = SIDTAB_HASH(sid);
	prev = NULL;
	cur = s->htable[hvalue];
	while (cur != NULL && sid > cur->sid) {
		prev = cur;
		cur = cur->next;
	}

	if (cur && sid == cur->sid) {
		errno = EEXIST;
		return -EEXIST;
	}

	newnode = (sidtab_node_t *) malloc(sizeof(sidtab_node_t));
	if (newnode == NULL)
		return -ENOMEM;
	newnode->sid = sid;
	if (context_cpy(&newnode->context, context)) {
		free(newnode);
		return -ENOMEM;
	}

	if (prev) {
		newnode->next = prev->next;
		prev->next = newnode;
	} else {
		newnode->next = s->htable[hvalue];
		s->htable[hvalue] = newnode;
	}

	s->nel++;
	if (sid >= s->next_sid)
		s->next_sid = sid + 1;
	return 0;
}

int sepol_sidtab_remove(sidtab_t * s, sepol_security_id_t sid)
{
	int hvalue;
	sidtab_node_t *cur, *last;

	if (!s || !s->htable)
		return -ENOENT;

	hvalue = SIDTAB_HASH(sid);
	last = NULL;
	cur = s->htable[hvalue];
	while (cur != NULL && sid > cur->sid) {
		last = cur;
		cur = cur->next;
	}

	if (cur == NULL || sid != cur->sid)
		return -ENOENT;

	if (last == NULL)
		s->htable[hvalue] = cur->next;
	else
		last->next = cur->next;

	context_destroy(&cur->context);

	free(cur);
	s->nel--;
	return 0;
}

context_struct_t *sepol_sidtab_search(sidtab_t * s, sepol_security_id_t sid)
{
	int hvalue;
	sidtab_node_t *cur;

	if (!s || !s->htable)
		return NULL;

	hvalue = SIDTAB_HASH(sid);
	cur = s->htable[hvalue];
	while (cur != NULL && sid > cur->sid)
		cur = cur->next;

	if (cur == NULL || sid != cur->sid) {
		/* Remap invalid SIDs to the unlabeled SID. */
		sid = SECINITSID_UNLABELED;
		hvalue = SIDTAB_HASH(sid);
		cur = s->htable[hvalue];
		while (cur != NULL && sid > cur->sid)
			cur = cur->next;
		if (!cur || sid != cur->sid)
			return NULL;
	}

	return &cur->context;
}

int sepol_sidtab_map(sidtab_t * s,
		     int (*apply) (sepol_security_id_t sid,
				   context_struct_t * context,
				   void *args), void *args)
{
	int i, ret;
	sidtab_node_t *cur;

	if (!s || !s->htable)
		return 0;

	for (i = 0; i < SIDTAB_SIZE; i++) {
		cur = s->htable[i];
		while (cur != NULL) {
			ret = apply(cur->sid, &cur->context, args);
			if (ret)
				return ret;
			cur = cur->next;
		}
	}
	return 0;
}

void sepol_sidtab_map_remove_on_error(sidtab_t * s,
				      int (*apply) (sepol_security_id_t sid,
						    context_struct_t * context,
						    void *args), void *args)
{
	int i, ret;
	sidtab_node_t *last, *cur, *temp;

	if (!s || !s->htable)
		return;

	for (i = 0; i < SIDTAB_SIZE; i++) {
		last = NULL;
		cur = s->htable[i];
		while (cur != NULL) {
			ret = apply(cur->sid, &cur->context, args);
			if (ret) {
				if (last) {
					last->next = cur->next;
				} else {
					s->htable[i] = cur->next;
				}

				temp = cur;
				cur = cur->next;
				context_destroy(&temp->context);
				free(temp);
				s->nel--;
			} else {
				last = cur;
				cur = cur->next;
			}
		}
	}

	return;
}

static inline sepol_security_id_t sepol_sidtab_search_context(sidtab_t * s,
							      context_struct_t *
							      context)
{
	int i;
	sidtab_node_t *cur;

	for (i = 0; i < SIDTAB_SIZE; i++) {
		cur = s->htable[i];
		while (cur != NULL) {
			if (context_cmp(&cur->context, context))
				return cur->sid;
			cur = cur->next;
		}
	}
	return 0;
}

int sepol_sidtab_context_to_sid(sidtab_t * s,
				context_struct_t * context,
				sepol_security_id_t * out_sid)
{
	sepol_security_id_t sid;
	int ret = 0;

	*out_sid = SEPOL_SECSID_NULL;

	sid = sepol_sidtab_search_context(s, context);
	if (!sid) {
		SIDTAB_LOCK(s);
		/* Rescan now that we hold the lock. */
		sid = sepol_sidtab_search_context(s, context);
		if (sid)
			goto unlock_out;
		/* No SID exists for the context.  Allocate a new one. */
		if (s->next_sid == UINT_MAX || s->shutdown) {
			ret = -ENOMEM;
			goto unlock_out;
		}
		sid = s->next_sid++;
		ret = sepol_sidtab_insert(s, sid, context);
		if (ret)
			s->next_sid--;
	      unlock_out:
		SIDTAB_UNLOCK(s);
	}

	if (ret)
		return ret;

	*out_sid = sid;
	return 0;
}

void sepol_sidtab_hash_eval(sidtab_t * h, char *tag)
{
	int i, chain_len, slots_used, max_chain_len;
	sidtab_node_t *cur;

	slots_used = 0;
	max_chain_len = 0;
	for (i = 0; i < SIDTAB_SIZE; i++) {
		cur = h->htable[i];
		if (cur) {
			slots_used++;
			chain_len = 0;
			while (cur) {
				chain_len++;
				cur = cur->next;
			}

			if (chain_len > max_chain_len)
				max_chain_len = chain_len;
		}
	}

	printf
	    ("%s:  %d entries and %d/%d buckets used, longest chain length %d\n",
	     tag, h->nel, slots_used, SIDTAB_SIZE, max_chain_len);
}

void sepol_sidtab_destroy(sidtab_t * s)
{
	int i;
	sidtab_ptr_t cur, temp;

	if (!s || !s->htable)
		return;

	for (i = 0; i < SIDTAB_SIZE; i++) {
		cur = s->htable[i];
		while (cur != NULL) {
			temp = cur;
			cur = cur->next;
			context_destroy(&temp->context);
			free(temp);
		}
		s->htable[i] = NULL;
	}
	free(s->htable);
	s->htable = NULL;
	s->nel = 0;
	s->next_sid = 1;
}

void sepol_sidtab_set(sidtab_t * dst, sidtab_t * src)
{
	SIDTAB_LOCK(src);
	dst->htable = src->htable;
	dst->nel = src->nel;
	dst->next_sid = src->next_sid;
	dst->shutdown = 0;
	SIDTAB_UNLOCK(src);
}

void sepol_sidtab_shutdown(sidtab_t * s)
{
	SIDTAB_LOCK(s);
	s->shutdown = 1;
	SIDTAB_UNLOCK(s);
}

/* FLASK */