/* $Id: cdb_init.c,v 1.12 2008-11-06 18:07:04 mjt Exp $
 * cdb_init, cdb_free and cdb_read routines
 *
 * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
 * Public domain.
 */

#include "cdb.h"

unsigned
cdb_hash(const void *buf, unsigned len)
{
	register const unsigned char *p = (const unsigned char *) buf;
	register const unsigned char *end = p + len;
	register unsigned hash = 5381; /* start value */
	while (p < end)
		hash = (hash + (hash << 5)) ^ *p++;
	return hash;
}

int
cdb_init(struct cdb *cdbp, int fd)
{
	struct stat st;
	unsigned char *mem;
	unsigned fsize, dend;
#ifdef _WIN32
	HANDLE hFile, hMapping;
#endif

	/* get file size */
	if (fstat (fd, &st) < 0)
		return -1;
	/* trivial sanity check: at least toc should be here */
	if (st.st_size < 2048)
		return errno = EPROTO, -1;
	fsize = (unsigned) (st.st_size & 0xffffffffu);
	/* memory-map file */
#ifdef _WIN32
	hFile = (HANDLE) _get_osfhandle(fd);
	if (hFile == (HANDLE) -1)
	return -1;
	hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if (!hMapping)
	return -1;
	mem = (unsigned char *)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
	CloseHandle(hMapping);
	if (!mem)
	return -1;
#else
	mem = (unsigned char*) mmap (NULL, fsize, PROT_READ, MAP_SHARED, fd, 0);
	if (mem == MAP_FAILED)
		return -1;
#endif /* _WIN32 */

	cdbp->cdb_fd = fd;
	cdbp->cdb_fsize = fsize;
	cdbp->cdb_mem = mem;
	cdbp->mtime = st.st_mtime;

	cdbp->cdb_vpos = cdbp->cdb_vlen = 0;
	cdbp->cdb_kpos = cdbp->cdb_klen = 0;
	dend = cdb_unpack (mem);
	if (dend < 2048)
		dend = 2048;
	else if (dend >= fsize)
		dend = fsize;
	cdbp->cdb_dend = dend;

	return 0;
}

void
cdb_free(struct cdb *cdbp)
{
	if (cdbp->cdb_mem) {
#ifdef _WIN32
		UnmapViewOfFile((void*) cdbp->cdb_mem);
#else
		munmap ((void*) cdbp->cdb_mem, cdbp->cdb_fsize);
#endif /* _WIN32 */
		cdbp->cdb_mem = NULL;
	}
	cdbp->cdb_fsize = 0;

	if (cdbp->check_timer_ev) {
		evtimer_del (cdbp->check_timer_ev);
		g_free (cdbp->check_timer_ev);
		g_free (cdbp->check_timer_tv);
	}
}

const void *
cdb_get(const struct cdb *cdbp, unsigned len, unsigned pos)
{
	if (pos > cdbp->cdb_fsize || cdbp->cdb_fsize - pos < len) {
		errno = EPROTO;
		return NULL;
	}
	return cdbp->cdb_mem + pos;
}

int
cdb_read(const struct cdb *cdbp, void *buf, unsigned len, unsigned pos)
{
	const void *data = cdb_get (cdbp, len, pos);
	if (!data)
		return -1;
	memcpy (buf, data, len);
	return 0;
}

static void
cdb_timer_callback (int fd, short what, gpointer ud)
{
	struct cdb *cdbp = ud;
	gint nfd;
	struct stat st;

	/* Check cdb file for modifications */
	if (stat (cdbp->filename, &st) != -1) {
		if (st.st_mtime > cdbp->mtime) {
			if ((nfd = open (cdbp->filename, O_RDONLY)) != -1) {
				if (cdbp->cdb_mem) {
#ifdef _WIN32
					UnmapViewOfFile((void*) cdbp->cdb_mem);
#else
					munmap ((void*) cdbp->cdb_mem, cdbp->cdb_fsize);
#endif /* _WIN32 */
					cdbp->cdb_mem = NULL;
				}
				(void)close (cdbp->cdb_fd);
				cdbp->cdb_fsize = 0;
				(void)cdb_init (cdbp, nfd);
			}
		}
	}

	evtimer_add (cdbp->check_timer_ev, cdbp->check_timer_tv);
}

void
cdb_add_timer(struct cdb *cdbp, unsigned seconds)
{
	cdbp->check_timer_ev = g_malloc (sizeof (struct event));
	cdbp->check_timer_tv = g_malloc (sizeof (struct timeval));

	cdbp->check_timer_tv->tv_sec = seconds;
	cdbp->check_timer_tv->tv_usec = 0;

	evtimer_set (cdbp->check_timer_ev, cdb_timer_callback, cdbp);
	evtimer_add (cdbp->check_timer_ev, cdbp->check_timer_tv);
}