/* vmlog - high-speed logging for free VMs                  */
/* Copyright (C) 2006 Edwin Steiner <edwin.steiner@gmx.net> */

/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "vmlog.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <jni.h>

/*** default macros **************************************************/

#ifndef VMLOG_LOCK
#define VMLOG_LOCK(vml)
#define VMLOG_UNLOCK(vml)
#endif

/* #define VMLOG_ENDIAN_CONVERT_WRITE */
/* #define VMLOG_HOST_LITTLE_ENDIAN */

#include "config.h"

#if defined(WORDS_BIGENDIAN)
#	define VMLOG_ENDIAN_CONVERT_WRITE
#	undef VMLOG_HOST_LITTLE_ENDIAN
#else
#	undef VMLOG_ENDIAN_CONVERT_WRITE
#	define VMLOG_HOST_LITTLE_ENDIAN
#endif


/*** constants *******************************************************/

/* currently vmlog does no rehashing, so these should be quite big */
#define VMLOG_INITIAL_STRING_HASH_SIZE       50000 /* XXX debug */
#define VMLOG_INITIAL_THREAD_HASH_SIZE       8 /* XXX debug */

/* initial size of the frame buffer - this is doubled each time    */
/* the frame buffer has to grow                                    */
#define VMLOG_INITIAL_FRAMES_CAPACITY        1 /* XXX debug */

/*** types ***********************************************************/

/* we declare this here because defining _LARGEFILE64_SOURCE works */
/* only if we are the first ones to include the headers, which may */
/* not be the case if vmlog.c is used as an include file.          */

#ifndef _LARGEFILE64_SOURCE
typedef long long off64_t;
off64_t lseek64(int fd, off64_t offset, int whence);
#endif

/*** tag definitions *************************************************/

/* CAUTION: these must are indexed by the VMLOG_TAG_... constants! */
vmlog_tag_definition vmlog_tag_definitions[] = {
	{ "enter", "enter", +1 },
	{ "leave", "leave", -1 },
	{ "throw", "throw",  0 },
	{ "catch", "catch",  0 },
	{ "unwnd", "unwnd", -1 },
	{ "signl", "signl",  0 },
	{ "unrol", "unrol", -1 },
	{ "rerol", "rerol", +1 },
	{ NULL   , NULL   ,  0 }
};

/*** global variables ************************************************/

static char *vmlog_progname = "vmlog";

/*** prototypes ******************************************************/

static void *vmlog_memdup(const void *m,int len);

/*** error reporting *************************************************/

void vmlog_set_progname(const char *progname)
{
	if (!progname) {
		progname = "vmlog (progname == NULL)";
	}

	vmlog_progname = vmlog_memdup(progname,strlen(progname)+1);
}

void vmlog_die(const char *fmt,...)
{
	va_list ap;

	fputs(vmlog_progname,stderr);
	fputs(": error: ",stderr);
	va_start(ap,fmt);
	vfprintf(stderr,fmt,ap);
	va_end(ap);
	fputc('\n',stderr);
	exit(1);
}

void vmlog_warn(const char *fmt,...)
{
	va_list ap;

	fputs(vmlog_progname,stderr);
	fputs(": warning: ",stderr);
	va_start(ap,fmt);
	vfprintf(stderr,fmt,ap);
	va_end(ap);
	fputc('\n',stderr);
}

void vmlog_die_usage(const char *usage,int error)
{
	assert(usage);
	
	fputs(usage,(error) ? stderr : stdout);
	exit((error) ? 1 : 0);
}

/*** utility functions ***********************************************/

static void *vmlog_memdup(const void *data,int len)
{
	char *p;

	p = VMLOG_NEW_ARRAY(char,len);
	assert(p);
	memcpy(p,data,len);

	return p;
}

static void *vmlog_strdup(const void *data,int len)
{
	char *p;

	p = VMLOG_NEW_ARRAY(char,len+1);
	assert(p);
	memcpy(p,data,len);
	p[len] = 0;

	return p;
}

char *vmlog_concat4len(const char *a,int alen,const char *b,int blen,
		       const char *c,int clen,const char *d,int dlen,
		       int *plen)
{
	int len;
	char *p;
	char *pp;

	assert(a);
	assert(b);
	assert(c);
	assert(d);

	len = alen + blen + clen + dlen;
	if (plen)
		*plen = len;

	p = VMLOG_NEW_ARRAY(char,len+1);
	pp = p;
	memcpy(pp,a,alen); pp += alen;
	memcpy(pp,b,blen); pp += blen;
	memcpy(pp,c,clen); pp += clen;
	memcpy(pp,d,dlen); pp += dlen;
	*pp = 0;

	return p;
}

char *vmlog_concat3(const char *a,const char *b,const char *c,int *plen)
{
	int len,lena,lenb,lenc;
	char *p;
	char *pp;

	assert(a);
	assert(b);
	assert(c);

	lena = strlen(a);
	lenb = strlen(b);
	lenc = strlen(c);

	len = lena + lenb + lenc;
	if (plen)
		*plen = len;

	p = VMLOG_NEW_ARRAY(char,len+1);
	pp = p;
	memcpy(pp,a,lena); pp += lena;
	memcpy(pp,b,lenb); pp += lenb;
	memcpy(pp,c,lenc); pp += lenc;
	*pp = 0;

	return p;
}

/*** file ops ********************************************************/

void vmlog_file_open(vmlog_file *file,const char *fname,vmlog_fmode fmode)
{
	int r;
	struct stat st;
	int flags = 0;
	
	assert(file);

	switch (fmode) {
		case vmlogRead:
			flags = O_RDONLY;
			break;

		case vmlogAppend:
			flags = O_WRONLY | O_APPEND | O_CREAT;
			break;

		case vmlogTruncateAppend:
			flags = O_WRONLY | O_APPEND | O_CREAT | O_TRUNC;
			break;

		default:
			vmlog_die("unknown fmode for opening file: %s: %d",
				fname,fmode);
	}

	r = open(fname,flags,0644);
	if (r == -1) {
		vmlog_die("could not open file: %s: %s",fname,strerror(errno));
	}
	file->fd = r;
	file->fnamelen = strlen(fname);
	file->fname = vmlog_memdup(fname,file->fnamelen+1);

	r = fstat(file->fd,&st);
	if (r == -1) {
		vmlog_die("could not stat file: %s: %s",fname,strerror(errno));
	}
	file->ofs = (fmode == vmlogRead) ? 0 : st.st_size;
}

void vmlog_file_close(vmlog_file *file)
{
	assert(file);

	if (file->fd == -1)
		return;

	close(file->fd);
	file->fd = -1;
	VMLOG_FREE_ARRAY(char,file->fnamelen+1,file->fname);
	file->fname = NULL;
	file->fnamelen = 0;
}

void vmlog_file_append(vmlog_file *file,const void *data,int len)
{
	int r;

	assert(len >= 0);
	if (!len)
		return;
	assert(data);
	
	do {
		r = write(file->fd,data,len);
	} while (r == -1 && errno == EINTR);

	if (r == -1) {
		vmlog_die("could not write to file: %s: %s",file->fname,strerror(errno));
	}

	if (r != len) {
		vmlog_die("could not write all data to file: %s",file->fname);
	}

	file->ofs += len;
}

void vmlog_file_stat(vmlog_file *file)
{
	int r;
	struct stat st;
	
	r = fstat(file->fd,&st);
	if (r == -1)
		vmlog_die("could not stat file: %s: %s",file->fname,strerror(errno));

	file->size = st.st_size;
}

void * vmlog_file_mmap(const char *fname,int *plen)
{
	int fd;
	int r;
	struct stat st;
	void *m;

	fd = open(fname,O_RDONLY);
	if (fd == -1)
		vmlog_die("could not open file: %s: %s",fname,strerror(errno));
	
	r = fstat(fd,&st);
	if (r == -1)
		vmlog_die("could not stat file: %s: %s",fname,strerror(errno));

	if (plen)
		*plen = st.st_size;
	
	if (st.st_size) {
		m = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,fd,0);
		if (m == MAP_FAILED) {
			vmlog_die("could not mmap file: %s: %s",fname,strerror(errno));
		}
	}
	else {
		/* fake a pointer */
		m = VMLOG_NEW(char);
	}

	close(fd);

	return m;
}

void vmlog_file_munmap(void *m,int len)
{
	int r;
	
	if (len) {
		r = munmap(m,len);
		if (r != 0)
			vmlog_warn("could not munmap file: %s",strerror(errno));
	}
	else {
		VMLOG_FREE(char,m);
	}
}

void vmlog_file_seek(vmlog_file *file,vmlog_fofs_t ofs)
{
	off64_t r;

	r = lseek64(file->fd,ofs,SEEK_SET);
	if (r == (off64_t)-1)
		vmlog_die("could not seek position in file: %s: %s",
			file->fname,strerror(errno));
	file->ofs = ofs;
}

/*** string storage **************************************************/

static void vmlog_add_string(vmlog_log *vml,const char *data,int len)
{
	vmlog_string_entry strent;
#if defined(VMLOG_ENDIAN_CONVERT_WRITE)
	vmlog_fofs_t tmp;
#endif
	
	assert(vml);

	if (vml->strfile.fd == -1)
		return;
	if (vml->idxfile.fd == -1)
		return;

	strent.ofs = vml->strfile.ofs;
	strent.len = len;

#if defined(VMLOG_ENDIAN_CONVERT_WRITE)
#if defined(VMLOG_HOST_LITTLE_ENDIAN)
	tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[0]) << 56)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[1]) << 48)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[2]) << 40)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[3]) << 32)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[4]) << 24)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[5]) << 16)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[6]) <<  8)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[7]) <<  0);
	strent.ofs = tmp;
	tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.len)[0]) << 24)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[1]) << 16)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[2]) <<  8)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[3]) <<  0);
	strent.len = tmp;
#else
	tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[7]) << 56)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[6]) << 48)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[5]) << 40)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[4]) << 32)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[3]) << 24)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[2]) << 16)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[1]) <<  8)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[0]) <<  0);
	strent.ofs = tmp;
	tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.len)[3]) << 24)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[2]) << 16)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[1]) <<  8)
	    | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[0]) <<  0);
	strent.len = tmp;
#endif
#endif /* defined(VMLOG_ENDIAN_CONVERT_WRITE) */
	
	vmlog_file_append(&(vml->strfile),data,len);
	vmlog_file_append(&(vml->idxfile),&strent,sizeof(vmlog_string_entry));
}

/*** index functions *************************************************/

static int vmlog_is_ignored(vmlog_log *vml,int index)
{
	return (index < vml->ignorelistlen);
}

/*** thread log functions ********************************************/

static void vmlog_thread_log_alloc_logbuf(vmlog_thread_log *tlog,int cap)
{
	assert(tlog);
	assert(cap >= 0);

	if (cap) {
		VMLOG_XZNEW_ARRAY(tlog->logbuf,vmlog_log_entry,cap);
	}
	else {
		tlog->logbuf = NULL;
	}
	tlog->logbufptr = tlog->logbuf;
	tlog->logbufend = tlog->logbuf + cap;
	tlog->logbufcap = cap;
}

static void vmlog_thread_log_flush(vmlog_thread_log *tlog)
{
	assert(tlog);
	assert(tlog->logbuf);

	vmlog_file_append(&(tlog->logfile),tlog->logbuf,
			(tlog->logbufptr - tlog->logbuf) * sizeof(vmlog_log_entry));

	tlog->logbufptr = tlog->logbuf;
}

static void vmlog_thread_log_realloc_frames(vmlog_thread_log *tlog,int cap)
{
	vmlog_frame *oldframes;

	assert(tlog);
	assert(cap >= tlog->depth);

	oldframes = tlog->frames;

	if (cap) {
		VMLOG_XZNEW_ARRAY(tlog->frames,vmlog_frame,cap);
	}
	else {
		tlog->frames = NULL;
	}
	
	if (oldframes) {
		if (tlog->frames) {
			memcpy(tlog->frames,oldframes,sizeof(vmlog_frame) * tlog->depth);
		}
		VMLOG_FREE_ARRAY(vmlog_frame,tlog->framescap,oldframes);
	}
	tlog->framescap = cap;
}

void vmlog_thread_log_append(vmlog_thread_log *tlog,vmlog_log_entry *logent)
{
#if defined(VMLOG_ENDIAN_CONVERT_WRITE)
	unsigned int tmp;

#if defined(VMLOG_HOST_LITTLE_ENDIAN)
	tmp = ((unsigned int)(((unsigned char*)logent)[3]) <<  0)
	    | ((unsigned int)(((unsigned char*)logent)[2]) <<  8)
	    | ((unsigned int)(((unsigned char*)logent)[1]) << 16);
#else
	tmp = ((unsigned int)(((unsigned char*)logent)[1]) <<  0)
	    | ((unsigned int)(((unsigned char*)logent)[2]) <<  8)
	    | ((unsigned int)(((unsigned char*)logent)[3]) << 16);
#endif
	logent->index = tmp;
#endif /* defined(VMLOG_ENDIAN_CONVERT_WRITE) */
	if (tlog->logbufptr) {
		if (tlog->logbufptr == tlog->logbufend) {
			vmlog_thread_log_flush(tlog);
		}
		*tlog->logbufptr++ = *logent;
	}
	else {
		vmlog_file_append(&(tlog->logfile),logent,sizeof(vmlog_log_entry));
	}
}

#define VMLOG_INT2STR_BUFFER 20

vmlog_thread_log *vmlog_thread_log_new(vmlog_log *vml,void *threadid,int index)
{
	vmlog_thread_log *tlog;
	char buf[VMLOG_INT2STR_BUFFER];
	int r;
	char *name;
	int namelen;

	VMLOG_XZNEW(tlog,vmlog_thread_log);

	tlog->threadid = threadid;
	tlog->threadidx = index;
	tlog->logfile.fd = -1;

	vmlog_thread_log_realloc_frames(tlog,VMLOG_INITIAL_FRAMES_CAPACITY);

	if (vml && vml->prefix) {
		r = snprintf(buf,VMLOG_INT2STR_BUFFER,"%d",index);
		assert(r < VMLOG_INT2STR_BUFFER);
		buf[VMLOG_INT2STR_BUFFER-1] = 0;
		name = vmlog_concat4len(vml->prefix,vml->prefixlen,
				        ".",1,
				        buf,strlen(buf),
				        ".log",4,
				        &namelen);
		vmlog_file_open(&(tlog->logfile),name,vmlogTruncateAppend);
		VMLOG_FREE_ARRAY(char,namelen+1,name);
	}

	return tlog;
}

void vmlog_thread_log_free(vmlog_thread_log *tlog)
{
	if (!tlog)
		return;

	if (tlog->logbuf)
		vmlog_thread_log_flush(tlog);
	
	vmlog_file_close(&(tlog->logfile));
	
	if (tlog->frames) {
		VMLOG_FREE_ARRAY(vmlog_frame,tlog->framescap,tlog->frames);
	}
	VMLOG_FREE(vmlog_thread_log,tlog);
}

vmlog_frame * vmlog_thread_log_enter(vmlog_thread_log *tlog,int index,vmlog_seq_t seq)
{
	vmlog_frame *frame;

	if (tlog->depth < 0) {
		vmlog_warn("negative call frame depth %d at seq " VMLOG_SEQ_FMT,
				tlog->depth,seq);
		return NULL;
	}
	
	if (tlog->depth >= tlog->framescap)
		vmlog_thread_log_realloc_frames(tlog,tlog->framescap * 2);

	frame = tlog->frames + tlog->depth;

	frame->index = index;
	frame->seq = seq;

	tlog->depth++;

	return frame;
}

vmlog_frame * vmlog_thread_log_leave(vmlog_thread_log *tlog,int index,vmlog_seq_t seq)
{
	vmlog_frame *frame;

	if (--tlog->depth < 0) {
		vmlog_warn("negative call frame depth %d at seq " VMLOG_SEQ_FMT,
				tlog->depth,seq);
		return NULL;
	}
	
	frame = tlog->frames + tlog->depth;

	if (index != frame->index)
		vmlog_warn("mismatched leave at seq " VMLOG_SEQ_FMT 
				": entered index %d, left index %d",
			seq,frame->index,index);

	return frame;
}

/*** tag definitions *************************************************/

/* RETURNS                                                           */
/*     the tag number, or -1 if the tag name is invalid              */

int vmlog_tag_from_name(const char *name,int namelen)
{
	vmlog_tag_definition *td;
	int i;
	
	if (!name || namelen < 1)
		return -1;

	td = vmlog_tag_definitions;
	i = 0;
	while (td->name) {
		if (namelen == strlen(td->name)
		    && strncmp(td->name,name,namelen) == 0) 
		{
			return i;
		}
		td++;
		i++;
	}

	return -1;
}

/*** hash functions **************************************************/

static unsigned int vmlog_thread_hash(void *threadid) 
{
	/* XXX use a better hash function? */
	return (unsigned int)(ptrint)threadid;
}

static unsigned int vmlog_string_hash(const char *data,int len) 
{
	register const unsigned char *p = (const unsigned char *) data;
	register unsigned int hash;
	register int i;

	/* The algorithm is the "One-at-a-time" algorithm as published    */
	/* by Bob Jenkins on http://burtleburtle.net/bob/hash/doobs.html. */

	hash = 0;
	for (i=len; i--;)
	{
	    hash += *p++;
	    hash += (hash << 10);
	    hash ^= (hash >> 6);
	}
	hash += (hash << 3);
	hash ^= (hash >> 11);
	hash += (hash << 15);

	return hash;
}

/*** hash tables *****************************************************/

static vmlog_thread_log *vmlog_get_thread_log(vmlog_log *vml,void *threadid)
{
	unsigned int h;
	vmlog_hash_entry *preventry = NULL;
	vmlog_hash_entry *entry;
	vmlog_thread_log *tlog;
	
	assert(vml);

	h = vmlog_thread_hash(threadid);
	entry = vml->threadhash.table + (h % vml->threadhash.size);
	do {
		tlog = (vmlog_thread_log *)entry->data;
		if (tlog && tlog->threadid == threadid)
			return tlog;
		preventry = entry;
		entry = entry->hashlink;
	} while (entry);

	/* this is a new threadid */
	tlog = vmlog_thread_log_new(vml,threadid,vml->threadhash.nentries++);
	
	assert(preventry);
	if (preventry->data) {
		VMLOG_XZNEW(entry,vmlog_hash_entry);

		preventry->hashlink = entry;
	}
	else {
		entry = preventry;
	}

	entry->data = tlog;

	/* XXX maybe rehash */

	return tlog;
}

int vmlog_get_string_index(vmlog_log *vml,const char *data,int len)
{
	unsigned int hash;
	vmlog_hash_entry *entry;
	vmlog_hash_entry *preventry = NULL;
	
	assert(vml);
	assert(data);
	assert(len >= 0);

	hash = vmlog_string_hash(data,len);
	entry = vml->stringhash.table + (hash % vml->stringhash.size);
	do {
		if (entry->len == len && entry->data && memcmp(data,entry->data,len) == 0)
			return entry->index;
		preventry = entry;
		entry = entry->hashlink;
	} while (entry);

	/* this is a new string */
	assert(preventry);
	if (preventry->data) {
		VMLOG_XZNEW(entry,vmlog_hash_entry);

		preventry->hashlink = entry;
	}
	else {
		entry = preventry;
	}

	entry->data = vmlog_memdup(data,len);
	entry->len = len;
	entry->index = vml->stringhash.nentries++;
	vmlog_add_string(vml,data,len);

	return entry->index;
}

static void vmlog_hashtable_init(vmlog_hash_table *ht,int size)
{
	assert(ht);
	assert(size > 0);
	
	ht->size = size;
	VMLOG_XZNEW_ARRAY(ht->table,vmlog_hash_entry,size);
	ht->nentries = 0;
}

static void vmlog_hashtable_free(vmlog_hash_table *ht,vmlog_hash_entry_destructor destr)
{
	int i;
	vmlog_hash_entry *entry,*next;
	
	assert(ht);

	for (i=0; i<ht->size; ++i) {
		entry = ht->table + i;
		if (destr)
			destr(entry);

		next = entry->hashlink;
		while (next) {
			entry = next;
			if (destr)
				destr(entry);
			next = entry->hashlink;
			VMLOG_FREE(vmlog_hash_entry,entry);
		}
	}

	VMLOG_FREE_ARRAY(vmlog_hash_entry,ht->size,ht->table);
	memset(ht,0,sizeof(vmlog_hash_table));
}

static void vmlog_thread_log_destructor(vmlog_hash_entry *entry)
{
	vmlog_thread_log *tlog;
	
	assert(entry);

	tlog = (vmlog_thread_log *)entry->data;
	vmlog_thread_log_free(tlog);
}

static void vmlog_string_destructor(vmlog_hash_entry *entry)
{
	char *str;

	assert(entry);

	str = (char *)entry->data;
	if (str) {
		VMLOG_FREE_ARRAY(char,entry->len,str);
	}
}

static void vmlog_open_string_files(vmlog_log *vml,int truncate)
{
	char *name;
	int namelen;
	int fmode;

	if (!vml->prefix)
		return;

	fmode = (truncate) ? vmlogTruncateAppend : vmlogAppend;

	name = vmlog_concat3(vml->prefix,"",".idx",&namelen);
	vmlog_file_open(&(vml->idxfile),name,fmode);
	VMLOG_FREE_ARRAY(char,namelen+1,name);

	name = vmlog_concat3(vml->prefix,"",".str",&namelen);
	vmlog_file_open(&(vml->strfile),name,fmode);
	VMLOG_FREE_ARRAY(char,namelen+1,name);
}

void vmlog_load_stringhash(vmlog_log *vml,const char *prefix)
{
	int n;
	vmlog_string_entry *idxmap;
	vmlog_string_entry *strent;
	char *strmap;
	int idxlen;
	int strlen;
	char *idxfname;
	char *strfname;
	int idxnamelen;
	int strnamelen;
	
	assert(vml);
	assert(prefix);

	idxfname = vmlog_concat3(prefix,".","idx",&idxnamelen);
	strfname = vmlog_concat3(prefix,".","str",&strnamelen);
	
	vmlog_hashtable_free(&(vml->stringhash),vmlog_string_destructor);
	vmlog_hashtable_init(&(vml->stringhash),VMLOG_INITIAL_STRING_HASH_SIZE);

	vmlog_file_close(&(vml->idxfile));
	vmlog_file_close(&(vml->strfile));
	vmlog_open_string_files(vml,1);

	idxmap = vmlog_file_mmap(idxfname,&idxlen);
	strmap = vmlog_file_mmap(strfname,&strlen);

	n = idxlen / sizeof(vmlog_string_entry);
	strent = idxmap;
	while (n--) {
		vmlog_get_string_index(vml,strmap + strent->ofs,strent->len);
		strent++;
	}

	vmlog_file_munmap(idxmap,idxlen);
	vmlog_file_munmap(strmap,strlen);
	
	VMLOG_FREE_ARRAY(char,idxnamelen+1,idxfname);
	VMLOG_FREE_ARRAY(char,strnamelen+1,strfname);
}

/*** public functions ************************************************/

vmlog_log * vmlog_log_new(const char *prefix,int truncate)
{
	vmlog_log *vml;

	VMLOG_XZNEW(vml,vmlog_log);

	vml->idxfile.fd = -1;
	vml->strfile.fd = -1;
	vmlog_hashtable_init(&(vml->stringhash),VMLOG_INITIAL_STRING_HASH_SIZE);
	vmlog_hashtable_init(&(vml->threadhash),VMLOG_INITIAL_THREAD_HASH_SIZE);
	
	if (prefix) {
		vml->prefixlen = strlen(prefix);
		vml->prefix = vmlog_memdup(prefix,vml->prefixlen+1);

		vmlog_open_string_files(vml,truncate);
	}

	return vml;
}

void vmlog_log_free(vmlog_log *vml)
{
	if (!vml)
		return;

	VMLOG_FREE_ARRAY(char,vml->prefixlen+1,vml->prefix);
	vml->prefix = NULL;
	vml->prefixlen = 0;	

	vmlog_hashtable_free(&(vml->threadhash),vmlog_thread_log_destructor);
	vmlog_hashtable_free(&(vml->stringhash),vmlog_string_destructor);

	vmlog_file_close(&(vml->idxfile));
	vmlog_file_close(&(vml->strfile));

	VMLOG_FREE(vmlog_log,vml);
}

static void vmlog_log_enter_tag(vmlog_log *vml,void *threadid,int tag,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);

	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	if (tlog->ignoredepth) {
		tlog->ignoredepth++;
		return;
	}

	if (vmlog_is_ignored(vml,index)) {
		tlog->ignoredepth++;
		return;
	}
	
	logent.tag = tag;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

static void vmlog_log_leave_tag(vmlog_log *vml,void *threadid,int tag,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);
	
	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	if (tlog->ignoredepth) {
		tlog->ignoredepth--;
		return;
	}
	
	logent.tag = tag;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

void vmlog_log_enter(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_log_enter_tag(vml,threadid,VMLOG_TAG_ENTER,name,namelen);
}

void vmlog_log_rerol(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_log_enter_tag(vml,threadid,VMLOG_TAG_REROL,name,namelen);
}

void vmlog_log_leave(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_log_leave_tag(vml,threadid,VMLOG_TAG_LEAVE,name,namelen);
}

void vmlog_log_unrol(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_log_leave_tag(vml,threadid,VMLOG_TAG_UNROL,name,namelen);
}

void vmlog_log_throw(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);
	
	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	if (tlog->ignoredepth)
		return;
	
	logent.tag = VMLOG_TAG_THROW;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

void vmlog_log_catch(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);
	
	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	if (tlog->ignoredepth)
		return;
	
	logent.tag = VMLOG_TAG_CATCH;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

void vmlog_log_unwnd(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);
	
	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	if (tlog->ignoredepth) {
		tlog->ignoredepth--;
		return;
	}
	
	logent.tag = VMLOG_TAG_UNWND;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

void vmlog_log_signl(vmlog_log *vml,void *threadid,const char *name,int namelen)
{
	vmlog_thread_log *tlog;
	int index;
	vmlog_log_entry logent;
	
	assert(vml);
	assert(name);
	assert(namelen >= 0);
	
	VMLOG_LOCK();
	tlog = vmlog_get_thread_log(vml,threadid);
	index = vmlog_get_string_index(vml,name,namelen);
	VMLOG_UNLOCK();

	logent.tag = VMLOG_TAG_SIGNL;
	logent.index = index;
	vmlog_thread_log_append(tlog,&logent);

	tlog->seq++;
}

void vmlog_log_load_ignorelist(vmlog_log *vml,const char *prefix)
{
	assert(vml);
	assert(prefix);

	vmlog_load_stringhash(vml,prefix);
	vml->ignorelistlen = vml->stringhash.nentries;
}

/*** ring buffer functions *******************************************/

static void vmlog_ringbuf_visualize(vmlog_ringbuf *ring)
{
	int i;

	fprintf(stdout,"vmlog_ringbuf %p: bufsize=%d availbefore=%d availafter=%d\n",
			(void*)ring,ring->bufsize,
			ring->debug_availbefore,ring->debug_availafter);

	for (i=0; i<=ring->bufsize; ++i) {
		if (i == ring->bufsize) {
			fprintf(stdout,"%3d: xxxxxxxxxxxxx",i);
		}
		else {
			fprintf(stdout,"%3d: %2d %10d",i,ring->buf[i].tag,ring->buf[i].index);
		}
		if (ring->start - ring->buf == i) fputs(" start",stdout);
		if (ring->cur   - ring->buf == i) fputs(" cur",stdout);
		if (ring->end   - ring->buf == i) fputs(" end",stdout);
		if (ring->cur   - ring->buf == i) fprintf(stdout," (" VMLOG_SEQ_FMT ")",ring->seq);
		fputc('\n',stdout);
	}	
}

static void vmlog_ringbuf_check_invariants(vmlog_ringbuf *ring)
{
	/* vmlog_ringbuf_visualize(ring); */
	
	assert(ring);

	assert(ring->bufsize > 0);
	assert(ring->bufend == ring->buf + ring->bufsize);

	assert(ring->start >= ring->buf && ring->start < ring->bufend);
	assert((ring->end > ring->buf && ring->end <= ring->bufend)
			||
	       (ring->end == ring->start));

	assert(ring->debug_availbefore >= 0);
	assert(ring->debug_availafter >= 0);
	assert(ring->debug_availbefore + ring->debug_availafter <= ring->bufsize);

	/* ring->cur can point to any present  */
	/* element (#) or be equal to ring->end*/

	if (ring->end >= ring->start) {
		/* case A: ring->end >= ring->start    */
		/*                                     */
		/* -------#############-----------     */
		/* ^      ^            ^          ^    */
		/* buf    start        end     bufend  */

		assert(ring->cur >= ring->start && ring->cur <= ring->end);

		assert(ring->cur - ring->start == ring->debug_availbefore);
		assert(ring->end - ring->cur   == ring->debug_availafter);
	}
	else {
		/* case B: ring->end < ring->start     */
		/*                                     */
		/* #######------------############     */
		/* ^      ^           ^           ^    */
		/* buf    end        start     bufend  */

		assert((ring->cur >= ring->start && ring->cur < ring->bufend)
				||
		       (ring->cur >= ring->buf && ring->cur <= ring->end));

		if (ring->cur >= ring->start) {
			assert(ring->cur - ring->start == ring->debug_availbefore);
			assert((ring->bufend - ring->cur) + (ring->end - ring->buf) 
					== ring->debug_availafter);
		}
		else {
			assert((ring->bufend - ring->start) + (ring->cur - ring->buf) 
					== ring->debug_availbefore);
			assert(ring->end - ring->cur == ring->debug_availafter);
		}
	}
}

vmlog_ringbuf * vmlog_ringbuf_new(const char *fname,int bufsize)
{
	vmlog_ringbuf *ring;

	assert(bufsize > 0);

	VMLOG_XZNEW(ring,vmlog_ringbuf);
	VMLOG_XZNEW_ARRAY(ring->buf,vmlog_log_entry,bufsize);

	ring->bufsize = bufsize;
	ring->bufend = ring->buf + bufsize;
	ring->start = ring->buf;
	ring->end = ring->buf;
	ring->cur = ring->buf;

	vmlog_file_open(&(ring->file),fname,vmlogRead);
	vmlog_file_stat(&(ring->file));

	vmlog_ringbuf_check_invariants(ring);

	return ring;
}

void vmlog_ringbuf_free(vmlog_ringbuf *ring)
{
	if (!ring)
		return;

	vmlog_ringbuf_check_invariants(ring);

	vmlog_file_close(&(ring->file));

	VMLOG_FREE_ARRAY(vmlog_log_entry,ring->bufsize,ring->buf);
	VMLOG_FREE(vmlog_ringbuf,ring);
}

static int vmlog_ringbuf_read(vmlog_ringbuf *ring,vmlog_log_entry *buf,
		vmlog_seq_t seq,int n)
{
	int r;
	vmlog_fofs_t ofs;

	ofs = seq * sizeof(vmlog_log_entry);
	if (ofs != ring->file.ofs)
		vmlog_file_seek(&(ring->file),ofs);

	do {
		/* fprintf(stdout,"vmlog_ringbuf_read(%p,%d,%d)\n",
				(void*)ring,buf-ring->buf,n); */
		
		r = read(ring->file.fd,buf,n * sizeof(vmlog_log_entry));
	} while (r == -1 && errno == EINTR);

	if (r == -1)
		vmlog_die("reading from file: %s: %s",ring->file.fname,strerror(errno));

	ring->file.ofs += r;

	if (r % sizeof(vmlog_log_entry) != 0) {
		/* XXX */
		vmlog_warn("partial log entry read from file: %s",ring->file.fname);
	}

	return r / sizeof(vmlog_log_entry);
}

static int vmlog_ringbuf_fill_forward(vmlog_ringbuf *ring,vmlog_log_entry *fillstart,
				      vmlog_log_entry *fillend,vmlog_seq_t seq,int len)
{
	int space;
	int n;
	int read;
	vmlog_log_entry *oldend;

#if 0
	fprintf(stdout,"vmlog_ringbuf_fill_forward(%p,%d,%d," VMLOG_SEQ_FMT ",%d)\n",
			(void*)ring,fillstart-ring->buf,fillend-ring->buf,seq,len);
#endif

	vmlog_ringbuf_check_invariants(ring);

	space = fillend - fillstart;
	n = (len <= space) ? len : space;

	if (n <= 0)
		return 0;

	read = vmlog_ringbuf_read(ring,fillstart,seq,n);
	if (!read)
		return 0;

	oldend = ring->end;
	ring->end = fillstart + read;
	ring->debug_availafter += read;

	if (ring->cur == ring->bufend)
		ring->cur = ring->buf;

	if (ring->start >= fillstart && ring->start != oldend) {
		/* check if old entries have been overwritten */
		if (ring->start <= ring->end) {
			ring->debug_availbefore -=
				ring->end - ring->start + 1;
			ring->start = ring->end + 1;
			if (ring->start >= ring->bufend) {
				ring->start = ring->buf;
				ring->debug_availbefore = ring->cur - ring->start;
			}
		}
	}

	vmlog_ringbuf_check_invariants(ring);

	return read;
}
	
static int vmlog_ringbuf_fill_backward(vmlog_ringbuf *ring,vmlog_log_entry *fillstart,
				       vmlog_log_entry *fillend,vmlog_seq_t seq,int len)
{
	int space;
	int n;
	int read;
	vmlog_log_entry *oldstart;
	vmlog_log_entry *oldend;

#if 0
	fprintf(stdout,"vmlog_ringbuf_fill_backward(%p,%d,%d," VMLOG_SEQ_FMT ",%d)\n",
			(void*)ring,fillstart-ring->buf,fillend-ring->buf,seq,len);
#endif

	vmlog_ringbuf_check_invariants(ring);

	space = fillend - fillstart;
	n = (len <= space) ? len : space;

	if (n <= 0)
		return 0;

	seq += space - n;
	fillstart += space - n;

	read = vmlog_ringbuf_read(ring,fillstart,seq,n);
	if (read != n)
		vmlog_die("could not read backward in file: %s: %s",
			ring->file.fname,strerror(errno));

	oldstart = ring->start;
	ring->start = fillstart;
	ring->debug_availbefore += read;

	oldend = ring->end;
	if (ring->end <= fillend && ring->end != oldstart) {
		/* check if old entries have been overwritten */
		if (ring->start <= ring->end) {
			ring->debug_availafter -=
				ring->end - ring->start + 1;
			ring->end = ring->start - 1;

			if (ring->end <= ring->buf) {
				ring->end = ring->bufend;
				if (ring->cur == ring->buf && ring->end == ring->bufend)
					ring->cur = ring->bufend;
				ring->debug_availafter = ring->end - ring->cur;
			}
		}
	}

	if (ring->end == ring->buf) {
		assert(oldstart == oldend);
		ring->end = ring->bufend;
	}

	if (ring->cur == ring->buf && ring->end == ring->bufend)
		ring->cur = ring->bufend;

	vmlog_ringbuf_check_invariants(ring);

	return read;
}
	
int vmlog_ringbuf_fill(vmlog_ringbuf *ring,int len)
{
	int count;
	int read;
	vmlog_log_entry *fillend;
	vmlog_seq_t seq;
	
	assert(ring);

	if (!len)
		return 0 /*XXX*/;

	count = 0;

	vmlog_ringbuf_check_invariants(ring);
	
	if (len > 0) {
		if (ring->end >= ring->cur) {
			/* case A'1: ring->end >= ring->start  */
			/*                                     */
			/*                     vvvvvvvvvvv     */
			/* ------OOOOO#########-----------     */
			/* ^     ^    ^        ^          ^    */
			/* buf  start cur      end     bufend  */

			/* case B'1: ring->end < ring->start   */
			/*                                     */
			/*                     vvvvvvvvvvv     */
			/* OOOOOOOOOOO#########----OOOOOOO     */
			/* ^          ^        ^   ^      ^    */
			/* buf        cur      end st. bufend  */

			/* fill space at end of buf */
			seq = ring->seq + (ring->end - ring->cur);
			read = vmlog_ringbuf_fill_forward(ring,ring->end,ring->bufend,
					seq,len);
			count += read;
			len -= read;

			if (ring->end != ring->bufend)
				goto no_more_entries;

			/* case A'1: ring->end >= ring->start  */
			/*                                     */
			/* vvvvvvvvvv                          */
			/* ------OOOOO####################     */
			/* ^     ^    ^                   ^    */
			/* buf  start cur         end==bufend  */

			/* case B'1: ring->end < ring->start   */
			/*                                     */
			/* vvvvvvvvvv                          */
			/* OOOOOOOOOOO####################     */
			/* ^          ^                   ^    */
			/* buf==start cur         end==bufend  */

			/* fill space at beg of buf */
			seq = ring->seq + (ring->end - ring->cur);
			fillend = (ring->cur == ring->bufend) ? ring->bufend : (ring->cur - 1);
			read = vmlog_ringbuf_fill_forward(ring,ring->buf,fillend,
					seq,len);
			count += read;
			len -= read;
		}
		else {
			/* ring->end < ring->cur */

			/* no case A'2 */
			assert(ring->end < ring->start);

			/* case B'2: ring->end < ring->start   */
			/*                                     */
			/*      vvvvvvvvvvv                    */
			/* #####------OOOOOO##############     */
			/* ^    ^     ^     ^             ^    */
			/* buf  end   start cur        bufend  */

			/* fill space in middle of buf */
			seq = ring->seq + (ring->bufend - ring->cur) + (ring->end - ring->buf);
			read = vmlog_ringbuf_fill_forward(ring,ring->end,ring->cur - 1,
					seq,len);
			count += read;
			len -= read;
		}
	}
	else {
		len = -len;

		if (len > ring->seq)
			len = ring->seq;

		if (ring->start <= ring->cur) {
			/* case A'1: ring->end >= ring->start  */
			/*                                     */
			/* vvvvvv                              */
			/* ------#####OOOOOOOOO-----------     */
			/* ^     ^    ^        ^          ^    */
			/* buf  start cur      end     bufend  */

			/* case B'2: ring->end < ring->start   */
			/*                                     */
			/* vvvvvvvvvvv                         */
			/* OOOOO------######OOOOOOOOOOOOOO     */
			/* ^    ^     ^     ^             ^    */
			/* buf  end   start cur        bufend  */

			/* fill space at beg of buf */
			seq = ring->seq - (ring->cur - ring->buf);
			read = vmlog_ringbuf_fill_backward(ring,ring->buf,ring->start,
					seq,len);
			count += read;
			len -= read;

			if (ring->start != ring->buf)
				goto no_more_entries;

			/* case A'1: ring->end >= ring->start  */
			/*                                     */
			/*             vvvvvvvvvvvvvvvvvvv     */
			/* ###########OOOOOOOOO-----------     */
			/* ^          ^        ^          ^    */
			/* buf=start  cur      end     bufend  */

			/* case B'2: ring->end < ring->start   */
			/*                                     */
			/*                   vvvvvvvvvvvvv     */
			/* #################OOOOOOOOOOOOOO     */
			/* ^                ^             ^    */
			/* buf=start        cur    end=bufend  */

			/* fill space at end of buf */
			seq -= (ring->bufend - ring->cur - 1);
			read = vmlog_ringbuf_fill_backward(ring,ring->cur+1,ring->bufend,
					seq,len);
			count += read;
			len -= read;
		}
		else {
			/* ring->start > ring->cur */

			/* case B'1: ring->end < ring->start   */
			/*                                     */
			/*             vvvvvvvvvvvv            */
			/* ###########OOOOOOOOO----#######     */
			/* ^          ^        ^   ^      ^    */
			/* buf        cur      end st. bufend  */

			/* no case A'2 */
			assert(ring->end < ring->start);

			/* fill space in middle of buf */
			seq = ring->seq - (ring->cur - ring->buf) - (ring->bufend - ring->cur - 1);
			read = vmlog_ringbuf_fill_backward(ring,ring->cur + 1,ring->start,
					seq,len);
			count += read;
			len -= read;
		}
	}

no_more_entries:
	vmlog_ringbuf_check_invariants(ring);
	
	return count;
}

static void vmlog_ringbuf_reset(vmlog_ringbuf *ring)
{
	ring->start = ring->buf;
	ring->cur = ring->buf;
	ring->end = ring->buf;

	ring->debug_availbefore = 0;
	ring->debug_availafter = 0;

	vmlog_ringbuf_check_invariants(ring);
}

static int vmlog_ringbuf_advance(vmlog_ringbuf *ring,int diff)
{
	int space;
	
	if (diff > 0) {
		if (ring->end >= ring->start) {
			/* case A */
advance_cur_to_end:
			space = ring->end - ring->cur;

			if (space <= 0)
				return 0;

			if (space < diff)
				diff = space;

simple_advance:
			ring->cur += diff;
			ring->seq += diff;
			ring->debug_availbefore += diff;
			ring->debug_availafter -= diff;
			return diff;
		}
		else {
			/* case B */
			if (ring->end >= ring->cur)
				goto advance_cur_to_end;

			space = ring->bufend - ring->cur;
			if (space > diff)
				goto simple_advance;

			ring->cur = ring->buf - space;
			goto advance_cur_to_end;
		}
	}
	else if (diff < 0) {
		if (ring->end >= ring->start) {
			/* case A */
advance_cur_to_start:
			space = ring->cur - ring->start;

			if (space <= 0)
				return 0;

			if (-space > diff)
				diff = -space;
			goto simple_advance;
		}
		else {
			/* case B */
			if (ring->cur >= ring->start)
				goto advance_cur_to_start;

			space = ring->cur - ring->buf;
			if (space >= -diff)
				goto simple_advance;

			ring->cur = ring->bufend + space;
			goto advance_cur_to_start;
		}
	}
	else {
		return 0;
	}
}

void vmlog_ringbuf_seek(vmlog_ringbuf *ring,vmlog_seq_t seq)
{
	vmlog_seq_t diff;
	
	vmlog_ringbuf_check_invariants(ring);

	diff = seq - ring->seq;
	if (abs(diff) < ring->bufsize)
		diff -= vmlog_ringbuf_advance(ring,(int)diff);

	if (!diff)
		return;

	vmlog_ringbuf_reset(ring);
	vmlog_file_seek(&(ring->file),seq * sizeof(vmlog_log_entry));
	ring->seq = seq;

	vmlog_ringbuf_check_invariants(ring);
}

vmlog_log_entry * vmlog_ringbuf_next(vmlog_ringbuf *ring,int prefetch)
{
	vmlog_ringbuf_check_invariants(ring);

	while (1) {
		if (ring->end >= ring->start) {
			/* case A */
			if (ring->cur < ring->end) {
				ring->debug_availafter--;
				ring->debug_availbefore++;
				ring->seq++;
				return ring->cur++;
			}
		}
		else {
			/* case B */
			if (ring->end >= ring->cur) {
				if (ring->cur < ring->end) {
					ring->debug_availafter--;
					ring->debug_availbefore++;
					ring->seq++;
					return ring->cur++;
				}
			}
			else {
				if (ring->cur < ring->bufend) {
					vmlog_log_entry *r;

					r = ring->cur;
					ring->seq++;
					if (++ring->cur == ring->bufend)
						ring->cur = ring->buf;
					ring->debug_availafter--;
					ring->debug_availbefore++;
					return r;
				}
			}
		}	

		if (!vmlog_ringbuf_fill(ring,prefetch))
			return NULL;
	}

	assert(0); /* NOT REACHED */
	return NULL;
}

vmlog_log_entry * vmlog_ringbuf_prev(vmlog_ringbuf *ring,int prefetch)
{
	vmlog_ringbuf_check_invariants(ring);

	while (1) {
		if (ring->end >= ring->start) {
			/* case A */
			if (ring->cur > ring->start) {
				ring->debug_availafter++;
				ring->debug_availbefore--;
				ring->seq--;
				return --ring->cur;
			}
		}
		else {
			/* case B */
			if (ring->cur >= ring->start) {
				if (ring->cur > ring->start) {
					ring->debug_availafter++;
					ring->debug_availbefore--;
					ring->seq--;
					return --ring->cur;
				}
			}
			else {
				if (--ring->cur < ring->buf)
					ring->cur = ring->bufend - 1;
				ring->seq--;
				ring->debug_availafter++;
				ring->debug_availbefore--;
				return ring->cur;
			}
		}	

		if (!vmlog_ringbuf_fill(ring,-prefetch))
			return NULL;
	}

	assert(0); /* NOT REACHED */
	return NULL;
}

/*** option parsing **************************************************/

vmlog_options *vmlog_opt_new(void)
{
	vmlog_options *opts;

	VMLOG_XZNEW(opts,vmlog_options);

	return opts;
}

int vmlog_opt_parse_seq(const char *arg,int len,vmlog_seq_t *seq)
{
	char *buf;
	char *endptr;
	int r;
	
	assert(arg);

	if (len < 1)
		return 0;

	buf = vmlog_strdup(arg,len);
	*seq = strtoll(buf,&endptr,10);

	r = (endptr[0] == 0);

	VMLOG_FREE_ARRAY(char,len+1,buf);
	return r;
}

int vmlog_opt_parse_range(const char *arg,vmlog_seq_t *start,vmlog_seq_t *end)
{
	const char *sep;
	int len;

	sep = strchr(arg,':');
	if (!sep) {
		len = strlen(arg);
		if (!vmlog_opt_parse_seq(arg,len,start))
			return 0;
		*end = *start;
		return 1;
	}

	len = sep - arg;
	if (!len) {
		*start = 0;
	}
	else {
		if (!vmlog_opt_parse_seq(arg,len,start))
			return 0;
	}

	len = strlen(arg) - len - 1;
	if (!len) {
		*end = VMLOG_SEQ_MAX;
	}
	else {
		if (!vmlog_opt_parse_seq(sep+1,len,end))
			return 0;
	}
	return 1;
}

void vmlog_opt_set_prefix(vmlog_options *opts, const char *arg)
{
	opts->prefix = vmlog_strdup(arg,strlen(arg));
}

void vmlog_opt_set_stringprefix(vmlog_options *opts, const char *arg)
{
	opts->stringprefix = vmlog_strdup(arg,strlen(arg));
}

void vmlog_opt_set_ignoreprefix(vmlog_options *opts, const char *arg)
{
	opts->ignoreprefix = vmlog_strdup(arg,strlen(arg));
}

static int vmlog_opt_parse_one_option(vmlog_options *opts, const char *arg, const char *nextarg)
{
	int eat;

	if (strncmp(arg,"-vmlog:",7) != 0) {
		return 0;
	}

	/* a vmlog option */

	eat = 1;
	if (strcmp(arg,"-vmlog:prefix") == 0) {
		if (!nextarg)
			vmlog_die("expected a prefix after -vmlog:prefix");
		vmlog_opt_set_prefix(opts,nextarg);
		eat++;
	}
	else if (strcmp(arg,"-vmlog:strings") == 0) {
		if (!nextarg)
			vmlog_die("expected a prefix after -vmlog:strings");
		vmlog_opt_set_stringprefix(opts,nextarg);
		eat++;
	}
	else if (strcmp(arg,"-vmlog:ignore") == 0) {
		if (!nextarg)
			vmlog_die("expected a prefix after -vmlog:ignore");
		vmlog_opt_set_ignoreprefix(opts,nextarg);
		eat++;
	}
	else {
		vmlog_die("unknown -vmlog:... option: %s",arg);
	}

	return eat;
}

vmlog_options *vmlog_opt_parse_cmd_line(int *pargc,char **argv)
{
	int i;
	const char *arg;
	vmlog_options *opts;
	int eat;
	int left;

	assert(pargc);

	opts = vmlog_opt_new();

	if (*pargc && argv[0])
		opts->progname = vmlog_strdup(argv[0],strlen(argv[0]));

	i = 1;
	while (i < *pargc) {
		arg = argv[i];
		
		left = *pargc - i - 1;

		eat = vmlog_opt_parse_one_option(opts,arg,
				(left) ? argv[i+1] : NULL);

		if (eat == 0) {
			i++;
			continue;
		}

		/* remove the option from the command line */
		
		memmove(argv + i,argv + i + eat,sizeof(char*) * (*pargc - (i+eat)));
		*pargc -= eat;
	}

	return opts;
}

vmlog_options *vmlog_opt_parse_vmargs(JavaVMInitArgs *vmargs)
{
	int i;
	const char *arg;
	vmlog_options *opts;
	int eat;

	assert(vmargs);

	opts = vmlog_opt_new();

	i = 0;
	while (i < vmargs->nOptions) {
		arg = vmargs->options[i].optionString;

		eat = vmlog_opt_parse_one_option(opts,arg,
				(i+1 < vmargs->nOptions) ? vmargs->options[i+1].optionString : NULL);

		if (eat == 0) {
			i++;
			continue;
		}
		
		/* remove the option from the command line */
		
		memmove(vmargs->options + i,vmargs->options + i + eat,
				sizeof(JavaVMOption) * (vmargs->nOptions - (i+eat)));
		vmargs->nOptions -= eat;
	}

	return opts;
}

void vmlog_opt_free(vmlog_options *opts)
{
	if (!opts)
		return;

	if (opts->prefix)
		VMLOG_FREE_ARRAY(char,strlen(opts->prefix)+1,opts->prefix);
	if (opts->stringprefix)
		VMLOG_FREE_ARRAY(char,strlen(opts->stringprefix)+1,opts->stringprefix);
	if (opts->ignoreprefix)
		VMLOG_FREE_ARRAY(char,strlen(opts->ignoreprefix)+1,opts->ignoreprefix);

	VMLOG_FREE(vmlog_options,opts);
}

/* vim: noet ts=8 sw=8
 */
