summaryrefslogblamecommitdiffstats
path: root/uidmap.c
blob: 8315be299d21e2fcad8ca27a673fc19ba387369f (plain) (tree)





































































































































































































































































                                                                                                                             
#include "hashmap.h"
#include "lstring.h"
#include "types.h"
#include "helper.h"
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>

static struct hashmap _numToName, _nameToNum;
static BOOL _initDone = FALSE;
static const char *_saveFileName = NULL;

static const uint16_t MAGIC = 1234;
static const int64_t MAX_AGE = 86400 * 2; // 2 days
#define MAX_NAME_LEN 1024

HASHMAP_FUNCS_CREATE(name, struct string, struct user);
HASHMAP_FUNCS_CREATE(number, uint32_t, struct user);

struct user
{
	uint32_t number;
	const struct string *name;
	int64_t lastUse;
};

static void insertTime(const struct string *name, const uint32_t number, const int64_t lastUse)
{
	uint8_t *buffer = malloc(sizeof(struct user) + sizeof(struct string) + name->l);
	struct user *u = (struct user*)buffer;
	struct string *s = (struct string*)(buffer + sizeof(struct user));
	char *str = (char*)(buffer + sizeof(struct user) + sizeof(struct string));
	memcpy(str, name->s, name->l);
	s->s = str;
	s->l = name->l;
	u->name = s;
	u->number = number;
	u->lastUse = lastUse;
	if (name_hashmap_put(&_nameToNum, u->name, u) != u) {
		plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by name (%"PRIu32")", (int)s->l, s->s, number);
	}
	if (number_hashmap_put(&_numToName, &u->number, u) != u) {
		plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by number (%"PRIu32")", (int)s->l, s->s, number);
	}
	// TODO: Leak
}

static void insert(const struct string *name, const uint32_t number)
{
	insertTime(name, number, time(NULL));
}

static int uint32_compare(const void *a, const void *b)
{
	return (*(const uint32_t*)a) != (*(const uint32_t*)b);
}

static int string_compare(const void *a, const void *b)
{
	return !equals((const struct string*)a, (const struct string*)b);
}

static size_t uint32_hash(const void *key)
{
	return *(const uint32_t*)key;
}

static size_t string_hash(const void *key)
{
	const struct string *key_str = (const struct string*)key;
	size_t hash = 0;

	for (size_t pos = 0; pos < key_str->l; ++pos) {
		hash += key_str->s[pos];
		hash += (hash << 10);
		hash ^= (hash >> 6);
	}
	hash += (hash << 3);
	hash ^= (hash >> 11);
	hash += (hash << 15);
	return hash;
}

uint32_t uidmap_getNumberForName(const struct string *name)
{
	// Try string matching
	char buf[name->l];
	struct string lower = { .s = buf, .l = name->l };
	for (size_t i = 0; i < name->l; ++i) {
		buf[i] = tolower(name->s[i]);
	}
	// Get
	struct user *u = name_hashmap_get(&_nameToNum, &lower);
	if (u != NULL) {
		u->lastUse = time(NULL);
		return u->number;
	}
	// Not known yet - try numeric
	uint32_t asNumber = parseUInt32(name);
	if (asNumber >= 2000) {
		u = number_hashmap_get(&_numToName, &asNumber);
		if (u == NULL) {
			// No collision, use name as number
			insert(&lower, asNumber);
			return asNumber;
		}
		plog(DEBUG_WARNING, "%.*s is numeric (%u) but collides", (int)name->l, name->s, asNumber);
	}
	// Default case - generate number
	asNumber = (uint32_t)string_hash(&lower);
	while (asNumber < 2000) {
		asNumber = rand() + 2000;
	}
	for (;;) {
		//plog(DEBUG_WARNING, "Using number %u for %.*s", asNumber, (int)name->l, name->s);
		u = number_hashmap_get(&_numToName, &asNumber);
		if (u == NULL)
			break;
		do {
			asNumber = rand() + 2000;
		} while (asNumber < 2000);
	}
	insert(&lower, asNumber);
	return asNumber;
}

const struct string *uidmap_getNameForNumber(const uint32_t number)
{
	struct user *u = number_hashmap_get(&_numToName, &number);
	if (u == NULL)
		return NULL;
	u->lastUse = time(NULL);
	return u->name;
}

void uidmap_init(const char *fileName)
{
	if (_initDone)
		return;
	_initDone = TRUE;
	hashmap_init(&_numToName, uint32_hash, uint32_compare, 0);
	hashmap_init(&_nameToNum, string_hash, string_compare, 0);
	if (fileName == NULL)
		return;
	// We have a filename, remember it and try to load DB
	_saveFileName = strdup(fileName);
	FILE *fh = fopen(_saveFileName, "rb");
	if (fh == NULL)
		return;
	fseek(fh, 0, SEEK_END);
	const int fsize = ftell(fh);
	fseek(fh, 0, SEEK_SET);
	if (fsize > 0) {
		const size_t entries = (size_t)fsize / 25;
		hashmap_destroy(&_numToName);
		hashmap_destroy(&_nameToNum);
		hashmap_init(&_numToName, uint32_hash, uint32_compare, entries);
		hashmap_init(&_nameToNum, string_hash, string_compare, entries);
	}
	// Not endian safe, do not copy file around...
	uint16_t len = 0;
	int64_t lastUse;
	uint32_t uidNumber;
	char nbuffer[MAX_NAME_LEN];
	struct string name;
	name.s = nbuffer;
	if (fread(&len, sizeof(len), 1, fh) != 1 || len != MAGIC) {
		plog(DEBUG_WARNING, "uid map file missing header magic (%"PRIu16")", len);
		fclose(fh);
		return;
	}
	int loaded = 0;
	const int64_t now = time(NULL);
	for (;;) {
		if (fread(&len, sizeof(len), 1, fh) != 1)
			break;
		if (len > MAX_NAME_LEN) {
			plog(DEBUG_WARNING, "DB has entry of size %"PRIu16", aborting import");
			break;
		}
		if (fread(&lastUse, sizeof(lastUse), 1, fh) != 1 || fread(&uidNumber, sizeof(uidNumber), 1, fh) != 1) {
			plog(DEBUG_WARNING, "DB has truncated entry (no lastUse/uidNumber)");
			break;
		}
		if (fread(nbuffer, len, 1, fh) != 1) {
			plog(DEBUG_WARNING, "DB has truncated entry (no name)");
			break;
		}
		// Check age
		if (now - lastUse >  MAX_AGE)
			continue;
		if (lastUse > now)
			lastUse = now;
		// Add
		name.l = len;
		insertTime(&name, uidNumber, lastUse);
		loaded++;
	}
	fclose(fh);
	plog(DEBUG_INFO, "Loaded %d entries from %s", loaded, _saveFileName);
}

void uidmap_cleanupSave()
{
	if (!_initDone)
		return;
	FILE *fh = NULL;
	if (_saveFileName != NULL) {
		fh = fopen(_saveFileName, "wb");
		if (fh != NULL) {
			if (fwrite(&MAGIC, sizeof(MAGIC), 1, fh) != 1) {
				plog(DEBUG_WARNING, "Cannot write magic to %s", _saveFileName);
				fclose(fh);
				fh = NULL;
			}
		}
	}
	const int64_t deadline = time(NULL) - MAX_AGE;
	struct hashmap_iter *it = hashmap_iter(&_nameToNum);
	struct user *u = NULL;
	int saved = 0, removed = 0;
	uint16_t len;
	while (it != NULL) {
		u = name_hashmap_iter_get_data(it);
		if (u->lastUse < deadline) {
			it = hashmap_iter_remove(&_nameToNum, it);
			if (number_hashmap_remove(&_numToName, &u->number) != u) {
				plog(DEBUG_WARNING, "Could not remove user from numToName that was in nameToNum");
			}
			free(u);
			removed++;
			continue;
		}
		// Save if file
		if (fh != NULL && u->name->l < MAX_NAME_LEN) {
			len = (uint16_t)u->name->l;
			if (fwrite(&len, sizeof(len), 1, fh) != 1
					|| fwrite(&u->lastUse, sizeof(u->lastUse), 1, fh) != 1
					|| fwrite(&u->number, sizeof(u->number), 1, fh) != 1
					|| fwrite(u->name->s, len, 1, fh) != 1
				) {
				plog(DEBUG_WARNING, "Could not write record to %s", _saveFileName);
				fclose(fh);
				fh = NULL;
			} else {
				saved++;
			}
		}
		it = hashmap_iter_next(&_nameToNum, it);
	}
	if (fh != NULL) {
		fsync(fileno(fh));
		fclose(fh);
	}
	if (saved > 0 || removed > 0) {
		plog(DEBUG_VERBOSE, "Purged %d old entries, saved %d entries", removed, saved);
	}
}