/common/stringbucket.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/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/file.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stringbucket.h>

#define STRINGBUCKET_STRINGSIZE 64

const char * strnchr(const char *s, int c, int n) {
	const char *ptr = s;
	while (n > 0) {
		if (*ptr == c)
			return ptr;
		ptr++;
		n--;
	}
	return NULL;
}

int stringbucket_remap(struct stringbucket *sb) {
	struct stat st;

	if (sb->list != NULL) {
		munmap(sb->list, sb->size);
	}

	flock(sb->fd, LOCK_SH);
	fstat(sb->fd, &st);
	flock(sb->fd, LOCK_UN);
	sb->size = st.st_size;
	if (sb->size > 0) {
		sb->list = mmap(NULL, sb->size, PROT_READ | PROT_WRITE, MAP_SHARED, sb->fd, 0);
		if (sb->list == MAP_FAILED) {
			perror("stringbucket mmap");
			return 0;
		}
	} else {
		/* Don't map anything for now. */
		sb->list = NULL;
	}

	return 1;
}

struct stringbucket * stringbucket_open(const char *filename) {
	struct stringbucket *obj = malloc(sizeof(struct stringbucket));
	obj->list = NULL;
	obj->size = 0;

	if (obj == NULL) {
		perror("stringbucket allocate");
		goto stringbucket_open__malloc_fail;
	}

	obj->fd = open(filename, O_RDWR | O_APPEND | O_CREAT, 0600);
	if (obj->fd == -1) {
		perror("stringbucket open");
		goto stringbucket_open__open_fail;
	}

	if (stringbucket_remap(obj) == 0) {
		goto stringbucket_open__mmap_fail;
	}

	return obj;

stringbucket_open__mmap_fail:
	close(obj->fd);
stringbucket_open__open_fail:
	free(obj);
stringbucket_open__malloc_fail:
	return NULL;
}

void stringbucket_close(struct stringbucket *sb) {
	if (sb == NULL)
		return;

	if (sb->list != NULL)
		munmap(sb->list, sb->size);

	close(sb->fd);
	free(sb);
}

int stringbucket_find(struct stringbucket *sb, const char *string) {
	if (sb->list == NULL)
		return  -1;

	char * end = sb->list + sb->size;
	int string_len = strlen(string);

	char * ptr = sb->list;
	while (ptr < end) {
		char * next = (char *) strnchr(ptr, '\n', end - ptr);
		if (next == NULL)
			next = end;
		int len = next - ptr;
		if (len > STRINGBUCKET_STRINGSIZE)
			len = STRINGBUCKET_STRINGSIZE;
		if (memcmp(ptr, string, (len < string_len ? string_len : len)) == 0) {
			return (ptr - sb->list);
		}
		ptr = next + 1;
	}

	return -1;
}

int stringbucket_add(struct stringbucket *sb, const char *string) {
	if (stringbucket_find(sb, string) != -1) return 0;
	int str_len = strlen(string);
	int len;

	flock(sb->fd, LOCK_EX);
	len = write(sb->fd, string, str_len);
	if (len < 0)
		perror("stringbucket add");
	if (len < str_len)
		goto stringbucket_add__fail;
	len = write(sb->fd, "\n", 1);
	if (len < 0)
		perror("stringbucket add");
	if (len == 0)
		goto stringbucket_add__fail;
	flock(sb->fd, LOCK_UN);

	/* remap the data to include added content */
	if (stringbucket_remap(sb) == 0)
		return 0;

	return 1;

stringbucket_add__fail:
	ftruncate(sb->fd, sb->size);
	flock(sb->fd, LOCK_UN);
	return 0;
}

int stringbucket_delete(struct stringbucket *sb, const char *string) {
	int pos = stringbucket_find(sb, string);
	if (pos == -1) return 0;

	/* We doin' it DOS style! */
	sb->list[pos] = 0;
	return 1;
}

void stringbucket_iterate(struct stringbucket *sb, void (*iter)(char *, void *), void *stuff) {
	if (sb->list == NULL)
		return;

	char string[STRINGBUCKET_STRINGSIZE + 1];
	char * ptr = sb->list;
	char * end = sb->list + sb->size;

	while (ptr < end) {
		char * next = (char *) strnchr(ptr, '\n', end - ptr);
		if (next == NULL)
			next = end;
		if (ptr[0] == 0) {
			ptr = next + 1;
			continue;
		}
		int len = next - ptr;
		memcpy(string, ptr, len);
		string[len] = 0;
		iter(string, stuff);
		ptr = next + 1;
	}
}