/* $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->loop) {
		ev_stat_stop(cdbp->loop, &cdbp->stat_ev);
	}
}

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(EV_P_ ev_stat *w, int revents)
{
	struct cdb *cdbp = w->data;
	int nfd;

	/* Check cdb file for modifications */
	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);
	}
}

void cdb_add_timer(struct cdb *cdbp, EV_P_ ev_tstamp seconds)
{
	cdbp->loop = loop;
	ev_stat_init(&cdbp->stat_ev, cdb_timer_callback, cdbp->filename, seconds);
	cdbp->stat_ev.data = cdbp;
	ev_stat_start(EV_A_ & cdbp->stat_ev);
}