/common/stringring.c
/* Blerg is (C) 2011 The Dominion of Awesome, and is distributed under a
 * BSD-style license.  Please see the COPYING file for details.
 */

#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stringring.h>

#define STRINGRING_DATA_CHECK() \
	if (sr == NULL)         \
		return 0;       \
	if (data == NULL)       \
		return 0;       \
	if (data[0] == 0)       \
		return 0;


struct stringring * stringring_open(const char *filename) {
	struct stringring *sr;
	struct stat st;
	int initialize = 0;

	sr = malloc(sizeof(struct stringring));
	if (sr == NULL) {
		perror("stringring_handle malloc");
		return NULL;
	}

	sr->fd = open(filename, O_RDWR | O_CREAT, 0600);
	flock(sr->fd, LOCK_EX);
	if (sr->fd == -1) {
		perror("stringring open");
		goto stringring_open__open_failed;
	}

	fstat(sr->fd, &st);
	if (st.st_size < sizeof(struct stringring_block)) {
		ftruncate(sr->fd, sizeof(struct stringring_block));
		initialize = 1;
	}
	sr->sb = mmap(NULL, sizeof(struct stringring_block), PROT_READ | PROT_WRITE, MAP_SHARED, sr->fd, 0);
	if (sr->sb == MAP_FAILED) {
		perror("stringring mmap");
		goto stringring_open__map_failed;
	}

	if (initialize) {
		stringring_clear(sr);
	}
	flock(sr->fd, LOCK_UN);

	return sr;

stringring_open__map_failed:
	flock(sr->fd, LOCK_UN);
	close(sr->fd);
stringring_open__open_failed:
	free(sr);
	return NULL;
}

void stringring_close(struct stringring *sr) {
	if (sr == NULL)
		return;

	if (sr->sb != NULL)
		munmap(sr->sb, sizeof(struct stringring_block));

	if (sr->fd > -1)
		close(sr->fd);

	free(sr);
}

int stringring_add(struct stringring *sr, const char *data) {
	STRINGRING_DATA_CHECK();

	flock(sr->fd, LOCK_EX);
	if (sr->sb->counter == 0) {
		sr->sb->counter = STRINGRING_N_ENTRIES - 1;
	} else {
		sr->sb->counter--;
	}

	int data_len = strlen(data);
	sr->sb->entries[sr->sb->counter].timestamp = time(NULL);
	strncpy((char *)sr->sb->entries[sr->sb->counter].data, data, STRINGRING_DATA_SIZE);
	if (data_len < STRINGRING_DATA_SIZE) {
		/* zero out the rest of the data */
		memset(sr->sb->entries[sr->sb->counter].data + data_len, 0, STRINGRING_DATA_SIZE - data_len);
	}
	flock(sr->fd, LOCK_UN);

	return 1;
}

int stringring_find_unlocked(struct stringring *sr, const char *data, uint64_t cutoff) {
	int n, i;

	i = sr->sb->counter;
	for (n = 0; n < STRINGRING_N_ENTRIES; n++) {
		if (sr->sb->entries[i].timestamp > 0 &&
		    sr->sb->entries[i].timestamp > cutoff &&
		    strncmp((char *)sr->sb->entries[i].data, data, STRINGRING_DATA_SIZE) == 0) {
			return i;
		}
		i = (i + 1) % STRINGRING_N_ENTRIES;
	}

	return -1;
}

int stringring_find(struct stringring *sr, const char *data, unsigned int max_age) {
	int ret;
	uint64_t cutoff = (max_age > 0 ? time(NULL) - max_age : 0);

	flock(sr->fd, LOCK_SH);
	ret = stringring_find_unlocked(sr, data, cutoff);
	flock(sr->fd, LOCK_UN);

	return ret;
}

int stringring_remove_index_unlocked(struct stringring *sr, int idx) {
	if (sr == NULL)
		return 0;
	if (idx < 0 || idx >= STRINGRING_N_ENTRIES)
		return 0;

	sr->sb->entries[idx].timestamp = 0;
	memset(sr->sb->entries[idx].data, 0, STRINGRING_DATA_SIZE);

	return 1;
}

int stringring_remove_index(struct stringring *sr, int idx) {
	int ret;

	flock(sr->fd, LOCK_EX);
	ret = stringring_remove_index_unlocked(sr, idx);
	flock(sr->fd, LOCK_UN);

	return ret;
}

int stringring_touch(struct stringring *sr, const char *data) {
	STRINGRING_DATA_CHECK();
	int ret, idx;

	flock(sr->fd, LOCK_EX);
	idx = stringring_find_unlocked(sr, data, 0);
	ret = idx >= 0;
	if (ret) {
		sr->sb->entries[idx].timestamp = time(NULL);
	}
	flock(sr->fd, LOCK_UN);

	return ret;
}

int stringring_remove(struct stringring *sr, const char *data) {
	STRINGRING_DATA_CHECK();
	int ret;

	flock(sr->fd, LOCK_EX);
	ret = stringring_remove_index_unlocked(sr,
			stringring_find_unlocked(sr, data, 0));
	flock(sr->fd, LOCK_UN);

	return ret;
}

int stringring_clear(struct stringring *sr) {
	if (sr == NULL)
		return 0;

	flock(sr->fd, LOCK_EX);
	memset(sr->sb, 0, sizeof(struct stringring_block));
	flock(sr->fd, LOCK_UN);

	return 1;
}