/*
 * Implementation of the userspace SID hashtable.
 *
 * Author : Eamon Walsh, <ewalsh@epoch.ncsc.mil>
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "selinux_internal.h"
#include <selinux/avc.h>
#include "avc_sidtab.h"
#include "avc_internal.h"

static inline unsigned sidtab_hash(const char * key)
{
	char *p, *keyp;
	unsigned int size;
	unsigned int val;

	val = 0;
	keyp = (char *)key;
	size = strlen(keyp);
	for (p = keyp; (unsigned int)(p - keyp) < size; p++)
		val =
		    (val << 4 | (val >> (8 * sizeof(unsigned int) - 4))) ^ (*p);
	return val & (SIDTAB_SIZE - 1);
}

int sidtab_init(struct sidtab *s)
{
	int i, rc = 0;

	s->htable = (struct sidtab_node **)avc_malloc
	    (sizeof(struct sidtab_node *) * SIDTAB_SIZE);

	if (!s->htable) {
		rc = -1;
		goto out;
	}
	for (i = 0; i < SIDTAB_SIZE; i++)
		s->htable[i] = NULL;
	s->nel = 0;
      out:
	return rc;
}

int sidtab_insert(struct sidtab *s, const char * ctx)
{
	int hvalue, rc = 0;
	struct sidtab_node *newnode;
	char * newctx;

	newnode = (struct sidtab_node *)avc_malloc(sizeof(*newnode));
	if (!newnode) {
		rc = -1;
		goto out;
	}
	newctx = (char *) strdup(ctx);
	if (!newctx) {
		rc = -1;
		avc_free(newnode);
		goto out;
	}

	hvalue = sidtab_hash(newctx);
	newnode->next = s->htable[hvalue];
	newnode->sid_s.ctx = newctx;
	newnode->sid_s.refcnt = 1;	/* unused */
	s->htable[hvalue] = newnode;
	s->nel++;
      out:
	return rc;
}

int
sidtab_context_to_sid(struct sidtab *s,
		      const char * ctx, security_id_t * sid)
{
	int hvalue, rc = 0;
	struct sidtab_node *cur;

	*sid = NULL;
	hvalue = sidtab_hash(ctx);

      loop:
	cur = s->htable[hvalue];
	while (cur != NULL && strcmp(cur->sid_s.ctx, ctx))
		cur = cur->next;

	if (cur == NULL) {	/* need to make a new entry */
		rc = sidtab_insert(s, ctx);
		if (rc)
			goto out;
		goto loop;	/* find the newly inserted node */
	}

	*sid = &cur->sid_s;
      out:
	return rc;
}

void sidtab_sid_stats(struct sidtab *h, char *buf, int buflen)
{
	int i, chain_len, slots_used, max_chain_len;
	struct sidtab_node *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;
		}
	}

	snprintf(buf, buflen,
		 "%s:  %d SID entries and %d/%d buckets used, longest "
		 "chain length %d\n", avc_prefix, h->nel, slots_used,
		 SIDTAB_SIZE, max_chain_len);
}

void sidtab_destroy(struct sidtab *s)
{
	int i;
	struct sidtab_node *cur, *temp;

	if (!s)
		return;

	for (i = 0; i < SIDTAB_SIZE; i++) {
		cur = s->htable[i];
		while (cur != NULL) {
			temp = cur;
			cur = cur->next;
			freecon(temp->sid_s.ctx);
			avc_free(temp);
		}
		s->htable[i] = NULL;
	}
	avc_free(s->htable);
	s->htable = NULL;
}