
/*
updated on new site
Todo:
	Keep a table for each virtual domain/class pair
	When we hit 'vdomain'
		pop domain table (if alread in vdomain)
		save domain table
		Duplicate defaults table
	When we hit 'class'
		pop class table (if in class)
		push class table defaults
		duplicate defaults table into new 'class' table
	Add ref counts, so tables can be freed when associated threads exit

	When user logs in:
		Search vdomains to find one with matching address
		Search that domains list of classes, to find last matching one.
		Set ini table handle to point to that table, (add ref count)

  Reference counting:
	Function to 'get' a table reference, use mutex's and add to ref
	Function to 'free' a table reference, use mutexes, and free if it's dangling
	Function to add all 'used' tables to 'dangling list' (with mutexes) and make safe coppies
		and lock them.
	Function to release lock on safe ones.
	Function to clean dangling list.


*/


/* Can we write generic ini file ? */
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#define DSTR_LIB 1
#include "thread.h"
#define VINI_MAIN 1
#include "vini.h"
#include "lib.h"
// Chrisp experiment, if we remove this from 'dmalloc' will it all still work.
#define DMID 9
#include "debug.h"



#include "lprintf.h"
#include "rstring.h"

// Lets use 'simple' ones instead to detect any obvious faults.

/*

  this is removed with new dmalloc code. 

#undef malloc
#undef free
#undef realloc
#undef strdup
#undef calloc
#define malloc simple_malloc
#define free(x) simple_free(x,__FILE__,__LINE__)
#define realloc simple_realloc
#define strdup simple_strdup
#define calloc simple_calloc

*/


#define vini_startstop log_startstop
void /*@printflike@*/ vini_startstop(const char * arg_list, ...);	// Log a to start/stop.log

#ifndef FALSE
#define FALSE  0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

// Wont allow undef as is different symbol?? (osx xcode build)
#define trace sytem_trace
#define vline system_vline

static int g_gateway,g_mirror_secret;
static char *(*do_encrypt)(char *in, char *out);
static int vline;
static char vline_bf[BFSZ];

// Chrisp added to get timings on vini mutexes
#define mutex_wait mutex_wait_debug
#define mutex_release mutex_release_debug




int vini_set_encrypt(int gate, int mirror,char *(*xxx)(char *in, char *out) )
{
	do_encrypt = xxx;
	g_gateway = gate;
	g_mirror_secret = mirror;
	return TRUE;
}

static int (*emsg)(const char *s,...);
static int (*kmsg)(const char *s,...);

/*
Ini settings-
	spool /var/disk1 20mb
	spool /var/disk2 20mb
	vdomain 1.2.3.4 netwin.*
		user_file /usr/xxx/users.dat
	end vdoman

	Required
	Multiple values allowed?
		spool, diskspace,
	Parameter types? (string, int, mb,gig)

	spool path=/var/spool/disk1 space=2mb
	spool /var/spool/disk2 3gig
*/
int  imsg(const char * arg_list, ...);

enum {P_STRING,P_INT,P_MB,P_PATH};
#define MAXTABLE 1800
static Initype itypes[MAXTABLE];
static int ntable=0;
static Initable *vdomains;
static int vdomains_count;
static Mutex *imutex = NULL;

#ifndef MUTEX_TIME
#define MUTEX_TIME 80000
#ifdef MARIJN_DEBUG
// Make sure it does not timeout with mutex crashes when running in the debugger
#undef MUTEX_TIME
#define MUTEX_TIME 8000000
#endif
#endif

static char *lock_fname;
static int lock_line;
int  vini_mutex_wait(char *fname, int line);
void vini_mutex_release(char *fname, int line);
void vini_lock_all_x_real(Initable **start, char *fname, int line);
void vini_free_slow(Initable *start);
void vini_abort(char *reason);

char *vini_get_p_val(Inival *v, int x, char *param);

typedef struct TINFO {
	void *table;
	char *file;
	int line;
	int nref;
	char *type;
} Tinfo;
#define MAXTRACE 5000

static Tinfo trace[MAXTRACE];
static int tracei;

void vini_trace_add(char *type, void *table, int nref, char *file, int line)
{
	Tinfo *t;
	t = &trace[tracei];
	
	t->file = file; t->line = line;
	t->type = type;
	t->table = table;
	t->nref = nref;
	tracei++;
	if (tracei>=MAXTRACE) tracei = 0;
}
int vini_symlink(char *fname, char *symlink);
void vini_trace_show(void)
{
	Tinfo *t;
	int i;
	for (i=tracei;i<MAXTRACE;i++) {
		t = &trace[i];
		if (t->file!=NULL) vini_startstop("vini_trace: [%d] %s %s:%d nref=%d %p\n",i,t->type,t->file,t->line,t->nref,t->table);
	}
	for (i=0;i<tracei;i++) {
		t = &trace[i];
		if (t->file!=NULL) vini_startstop("vini_trace: [%d] %s %s:%d nref=%d %p\n",i,t->type,t->file,t->line,t->nref,t->table);
	}
}


Inival *vini_which(Inihandle *h, int x, Initype **tt);

Initable *vini_new_class(Initable *dom);
int vini_free_inival(Inival *table[])
{
	int i,j;
	Inival *v,*vnext;
	for (i=0; i<ntable; i++) {
		v = table[i];
		for (;v!=NULL; v=vnext) {
			vnext = v->next;
			for (j=0; j<MAXP; j++) {
				if (v->val[j]!=NULL) free(v->val[j]);
			}
			free(v);
		}
	}
	memset(table,0,sizeof(Inival *)*ntable);
	return TRUE;
}
int vini_exit(void)
{
	/* Free all the vdomain and class tables */
	vini_free_idle(TRUE);
	vini_free_current();
	vini_free_settings();
	ntable = 0;
	if (imutex != NULL) {th_mutex_free(imutex); imutex = NULL;}
	return TRUE;
}

int vini_add_domain(Initable *t)
{
	Initable *r;
	Initable **last;
	int i;
	last = &vdomains;
	for (i=0,r=vdomains; r!=NULL; r=r->next,i++) {
		last = &(r->next);
	}
	*last = t;
	vdomains_count++;
//	printf("vini_add_domain(%p)\n",t);
//	emsg("vini_add_domain(%p)\n",t);
	return TRUE;
}
Initable *vini_new_domain(void)
{
	Initable *v;
	v = calloc(1,sizeof(Initable));
	v->table = calloc(ntable,sizeof(Inival *));
	v->nref = 0;
	v->id = 1234;
//	printf("vini new domain table %p\n",v->table);
//	emsg("vini new domain table %p\n",v->table);
	vini_add_domain(v);
	return v;
}
int vini_clear_domain(Initable *curdom)
{
	vini_free_inival(curdom->table);
	return TRUE;
}
int vini_blank_domain(void)
{
	Initable *nn;
	Initable *t;
	int n=0;

	vini_mutex_wait(__FILE__,__LINE__);
	nn = vini_new_domain();
//	printf("Blank domain %p\n",nn);
	for (n=0,t=vdomains;t!=NULL;t=t->next,n++) {
		if (t==nn) {
//			imsg("vini_blank_domain %d\n",n);
			vini_mutex_release(__FILE__,__LINE__);
			return n;
		}
	}
	vini_mutex_release(__FILE__,__LINE__);
	//imsg("vini_blank_domain, not found\n");
	return -1;
}
int vini_blank_class(int domainid)
{
	Initable *t;
	int n=0;

	for (n=0,t=vdomains;t!=NULL;t=t->next,n++) {
		if (n==domainid) break;
	}
	if (t==NULL) {
		emsg("blank_class failed\n");
		return -1;
	}
	if (vini_new_class(t)==NULL) return -1;
	for (n=0,t=t->classes; t!=NULL; t=t->next,n++) ;
	return n-1;
}
Initable *vini_new_class(Initable *dom)
{
	Initable *v,**last,*c;
	v = calloc(1,sizeof(Initable));
	v->id = 1234;
	v->table = calloc(ntable,sizeof(Inival *));
	last = &dom->classes;
	for (c = dom->classes; c!=NULL; c=c->next) {
		last = &c->next;
	}
	*last = v;
	v->next = NULL;
	return v;
}
int vini_showclass(FILE *f,Initable *b);
int vini_max(void)
{
	return ntable;
}

int vini_showsetting(int (xprint)(void *, char *, ...), void *data, int i, Inival *r, char *xinfo)
{
	int j;
	int nlines=0;
	strcpy(xinfo,"ok");
	if (xprint==NULL) return FALSE;
	strcpy(xinfo,"data");
	if (!data) return FALSE;
	strcpy(xinfo,"rnull");
	if (!r) return FALSE;


	for(; r; r = r->next) {
		nlines++;
		if (r->no_edit) {
			strcpy(xinfo,"noedit");
			continue;
		}

		if (r->val[0] && itypes[i].np == 1 && !r->val[0][0] && !itypes[i].dflt[0]) {
			//!(itypes[i].type&VINI_FIELDS)
			strcpy(xinfo,"mystery");
			continue;
		}
		if (itypes[i].type & VINI_VDOMAIN) xprint(data,"%.900s ",itypes[i].name);
		else xprint(data,"\t%.900s ",itypes[i].name);

		for (j = 0; j < itypes[i].np; j++) {
			char tmp5[BFSZ];
			char *v0;
			if (!r->val[j]) continue;
			v0 = r->val[j];
			if (i==g_mirror_secret) {
				if (j==0) v0 = do_encrypt(v0,tmp5);
			}
			if (i==g_gateway) {
				if (strcmpnc(itypes[i].pname[j],"pass")==0) v0 = do_encrypt(v0,tmp5);
			}

			if (itypes[i].np == 1 && !(itypes[i].type&VINI_FIELDS)) xprint(data,"\"%.900s\" ",v0);
			else xprint(data,"%.100s=\"%.900s\" ",itypes[i].pname[j],v0);
		}
		xprint(data,"\n");
	}
	if (nlines>0) lprintf(xinfo,BFSZ-1,"Lines %d",nlines);

	return TRUE;
}


int vini_showsetting_name(Inihandle *h, int (xprint)(void *, char *, ...), void *data, char *setting)
{
	Initype *t;
	Inival *v;
	int i;
	char xinfo[BFSZ];
	for (i = 0; i < ntable; i++) {
		if (strcmpnc(itypes[i].name,setting)==0)  // chrisp just changed this to strcmpnc() so it will find it if the setting case is wrong...
			break;
	}
	if (i == ntable) return FALSE;

	v = vini_which(h,i,&t);
	if (!v) return FALSE;

	return vini_showsetting(xprint,data,i,v,xinfo);
}

int vini_setting_string(Inihandle *h, char *setting, char *bf, int bfsz)
{
	Initype *t;
	Inival *v;
	RString *s;
	int i;
	int r;
	char xinfo[BFSZ];

	*bf = 0;
	for (i = 0; i < ntable; i++) {
		if (strcmp(itypes[i].name,setting)==0)
			break;
	}
	if (i == ntable) return FALSE;

	v = vini_which(h,i,&t);
	if (!v) return FALSE;

	s = rstring_new(BFSZ);
	r = vini_showsetting((int (*)(void*,char*,...))rstring_printf,(void*)s,i,v,xinfo);
	rstring_to_char(s,bf,bfsz);
	rstring_free(&s);

	return r;
}

int vini_showconfig(FILE *f,Initable *b)
{
	int i;
	Initable *x;
	char xinfo[BFSZ];
	int doglobal = (b==vini_domains());

//sprintf(out,"Initable %p\n",b); outlen = lib_strlen(out);

	// First send out the global settings
	if (doglobal) {
		fprintf(f,"global_settings\n");
		for (i=0; i<ntable; i++) {
			if (!(itypes[i].type & VINI_GLOBAL)) continue;
			vini_showsetting((int (*)(void *,char*,...))fprintf,(void*)f,i,b->table[i],xinfo);
		}
	}
	// Then all the domains
	for (i=0; i<ntable; i++) {
		if (itypes[i].type & VINI_GLOBAL) continue;
		vini_showsetting((int (*)(void *,char*,...))fprintf,(void*)f,i,b->table[i],xinfo);
	}
	for (x=b->classes; x!=NULL; x=x->next) {
		vini_showclass(f,x);
	}
	return TRUE;
}

int vini_showclass(FILE *f,Initable *b)
{
	Inival *r;
	int i;
	int j;

/* sprintf(out,"Class Initable %p\n",b); outlen = lib_strlen(out); */
	for (i=0; i<ntable; i++) {
		r = b->table[i];
		for (;r!=NULL; r=r->next) {
			if (itypes[i].type & VINI_CLASS)
				fprintf(f,"\t%.900s ",itypes[i].name);
			else
				fprintf(f,"\t\t%.900s ",itypes[i].name);
			for (j=0; j<MAXP; j++) {
				if (r->val[j]!=NULL) {
					if (itypes[i].np==1 && !(itypes[i].type&VINI_FIELDS)) fprintf(f,"\"%.900s\" ",r->val[j]);
					else fprintf(f,"%.100s=\"%.900s\" ",itypes[i].pname[j],r->val[j]);
				}
			}
			fprintf(f,"\n");
		}
	}
	return TRUE;
}

int vini_copy_table(Initable *a, Initable *b)
{
	Inival *v,*r,**vnext;
	int i;
	int j;
	a->no_edit = b->no_edit;
//	emsg("Copy table %p <-- %p   tables %p <-- %p\n",a,b,a->table,b->table);
	for (i=0; i<ntable; i++) {
		r = b->table[i];
		vnext = NULL;
		for (;r!=NULL; r=r->next) {
//			emsg("Non null entry %d %.500s\n",i,itypes[i].name);
			if (vnext==NULL) {
				vnext = &a->table[i];
				for (v=a->table[i];v!=NULL;v=v->next) vnext = &v->next;
			}
			vini_newval(vnext); /* Create new value entry for this ini setting */
			v = *vnext;
			if (v == NULL) lib_abort();
			v->no_edit = r->no_edit;
			// And point vnext at the next pointer.
			vnext = &(v->next);
			for (j=0; j<MAXP; j++) {
				if (r->val[j]!=NULL) {
					v->val[j] = strdup(r->val[j]);
					//emsg("Coppied [%d] %.500s\n",j,r->val[j]);
				}
				v->vali[j] = r->vali[j];
			}
		}
	}
	//a->classes = b->classes;
	//b->classes = NULL;
	return TRUE;
}

#include "defines.h"
static char inifile[BFSZ];
static char sinifile[BFSZ]; //symbolic one
/*	vini_init() initialises the vini routines to use ini filename 'fname', and
 *	log error messages to 'xemsg'.
 *
 *	vini_init() returns TRUE if 'fname' exists, and FALSE otherwise.
 */
int vini_kmsg(int (*xkmsg)(const char *s,...))
{
	kmsg = xkmsg;
	return 0;
}
int vini_init(char *fname, int (*xemsg)(const char *s,...))
{
	int				result;			/* result of stat() */
	struct stat		garb_buffer;	/* buffer to hold stat() data */

	result = TRUE;
	emsg = xemsg;

	lprintf(inifile,BFSZ-10, "%s", fname);
	vini_symlink(inifile,sinifile);
	if ( stat(sinifile, &garb_buffer) == -1 ) result = FALSE;

	if (imutex==NULL) imutex = th_mutex_new("vini_mutex");

	return result;
}

/* ptypes, string, path, mb, bool, restofline */
enum {T_STRING,T_PATH,T_MB,T_BOOL,T_INT};
void vini_free_settings(void)
{
	Initype *t;
	int i;
	int j;

	for (i=0; i<MAXTABLE; i++) {
		t = &itypes[i];
		if (t->name!=NULL) free(t->name);
		if (t->dflt!=NULL) free(t->dflt);
		if (t->desc!=NULL) free(t->desc);
		for (j=0; j<MAXP; j++) {
			if (t->pname[j]!=NULL) free(t->pname[j]);
		}
		t->np = 0;
	}
}
int vini_add(int code, char *xcmd, char *dflt, int type, char *desc)
{
	int np=0;
	char cmd[BFSZ];
	Initype *t;
	char *s,*s2;
	char *upto;

	if (code!=ntable) {
		printf("FAULT vini add (%d/%d) (%s) (%s) \n",code,ntable,xcmd,desc);
		vini_abort("code != table in vini_add");
	}
	if (ntable>=MAXTABLE) {
		printf("Ini add, exceeded maxtable size\n");
		vini_abort("Exceeded ini table size");
	}
//emsg("vini_add %d %.500s\n",code,xcmd);
	strcpy(cmd,xcmd);
	s = mystrtok(cmd," ",&upto);
	if (s==NULL) return FALSE;
	t = &itypes[code];
	if (t->name!=NULL) {
		printf("t->name not null (%.500s)\n",t->name);
		vini_abort("name is null in vini add");
	}
	if (t->np!=0) {
		printf("t->np not zero\n");
		vini_abort("np is not zero in vini add");
	}
	t->name = strdup(s); mystrlwr(t->name);
	t->ismulti = FALSE;

	if ((type & VINI_MULTI)>0) t->ismulti = TRUE;

	s = mystrtok(NULL," ",&upto);
	for (;s!=NULL;) {
		if (t->np>=MAXP) {
			printf("Too many parameters for a single ini setting %d %d\n",t->np,MAXP);
			emsg("Too many parameters for a single ini setting %d %d\n",t->np,MAXP);
			vini_abort("Too many parameters for a single ini setting");
		}
		t->isop[t->np] = FALSE;
		if (*s=='[') {
			s++;
			s2 = strchr(s,']'); if (s2!=NULL) *s2 = 0;
			t->isop[t->np] = TRUE;
		}
		t->pname[t->np] = NULL;
		s2 = strchr(s,'=');
		if (s2!=NULL) {
			*s2 = 0;
			t->pname[t->np] = strdup(s);
			/*printf("%s Parameter [%d](%.500s)\n",t->name,t->np,s);*/

			type |= VINI_FIELDS;
			s = s2+1;
		}
		//emsg("Type (%.500s)\n",s);
		if (strcmp(s,"string")==0) t->ptype[t->np] = T_STRING;
		else if (strcmp(s,"mb")==0) t->ptype[t->np] = T_MB;
		else if (strcmp(s,"path")==0) t->ptype[t->np] = T_PATH;
		else if (strcmp(s,"bool")==0) t->ptype[t->np] = T_BOOL;
		else if (strcmp(s,"int")==0) t->ptype[t->np] = T_INT;
		else {
			printf("Invalid ini parameter (%.500s) %s\n",s,xcmd);
			emsg("Invalid ini parameter (%.500s) %s\n",s,xcmd);
			vini_abort("Invalid ini paramter");
		}
		t->np++;
		s = mystrtok(NULL," ",&upto);
	}
	t->dflt = strdup(dflt);
	t->desc = strdup(desc);
	t->type = type;

	if (code<=ntable) ntable = code+1;

	return TRUE;
}
int vini_free_current_this(Initable *xthis);
int vini_reload(void)
{
	Initable *vhanging;
	/* Other threads must be locked down before this is called */
	vini_mutex_wait(__FILE__,__LINE__);
	vini_mutex_state("doing_lock_all");
	vini_lock_all();
	vini_mutex_state("lock_all_done");
	vhanging = vdomains;
	vdomains = NULL;
	vini_load();
	vini_mutex_state("load done");
	vini_unlock_all();
	vini_mutex_state("unlock all done");
//	lib_msleep(3000);
	vini_free_slow(vhanging);
	vini_mutex_release(__FILE__,__LINE__);
	return TRUE;
}
int vini_free_current(void)
{
	Initable *t,*tnext;
	//imsg("vini free current\n");
	for (t=vdomains;t!=NULL;t=tnext) {
		//imsg("vini freeing %p\n",t);
		tnext = t->next;
		// chrisp commented out as it shows on linux startup...
//		if (t->nref>0) emsg("Could not free ini table dangling, nref=%d %p\n",t->nref,t);

		vini_free_dangling(t,"current",0);
	}

	vdomains = NULL;
	return TRUE;
}
int vini_newval(Inival **x)
{
	if (*x!=NULL) {
		printf("vini_newval, not null\n");
		vini_abort("vini_newval not already null as it should be");
	}
	*x = calloc(sizeof(Inival),1);
	return TRUE;
}
//#define BIGBF 32000
#define BIGBF BFSZ

// NOTE, CHRISP SET TO BFSZ RATHER THAN 32K BECAUSE i'M PARANOID.
// If we increase this limit, we need to increase other limits elsewhere and 
// that strkes me  as a bad idea as settings shouldn't be this long normally. 
static int include_used;
// This allows one to place #symlink:c:\surgemail\surgemail.ini in the ini file.... to move it
// to a sensible place...
int vini_symlink(char *fname, char *symlink)
{
	return lib_symlink(fname,symlink);
/*	FILE *f;
	char *s;
	char bf[BFSZ];
	ncpy(symlink,fname,BFSZ-1);
	f = fopen(symlink,"r");
	if (f==NULL) return FALSE;
	if (fgets(bf,BFSZ-1,f)==NULL) {
		fclose(f);
		return FALSE;
	}
	fclose(f);
	if (strncmpnc(bf,"#symlink:",9)==0) {
		ncpy(symlink,bf+9,BFSZ-1);
	} else return FALSE; // no symlink...
	s = strchr(symlink,'\n'); if (s!=NULL) *s = 0;
	s = strchr(symlink,'\r'); if (s!=NULL) *s = 0;
	return TRUE;
	*/
}
void vini_load(void)
{
	Initype *t;
	Inival *tval;
	Inival *lasttval=NULL;
	Inival **vnext;
	Inival **lastvnext=NULL;
	Inival *v;
	int g_include = FALSE;
	Initable *cur=NULL;
	Initable *lastcur=NULL;
	int lastcode=-1;
	Inival *lastv=NULL;
	int nlines=0;
	char state[BFSZ];
	Wordlist *gfiles=NULL;
	Wordlist *gfiles_upto=NULL;
	time_t start = time(NULL);
	Initable *curdom=NULL;
	Initable *curcls=NULL;
	FILE *f;
	char *p[190];
	char bf[BIGBF];
	char *s;
	int i;
	int fstack=0;
	FILE *infiles[20];
	int np;
	int code;
	int gotone = FALSE;
	int inclass = FALSE;
	int inglobal = FALSE;
	char cur_line[BFSZ];
	char saved_line[BFSZ];
	strcpy(saved_line,"");

	/* duplicate defaults table*/
	vini_symlink(inifile,sinifile);
	f = fopen(sinifile,"r");
	if (f==NULL) {
		printf("Could not open (%.500s) %.500s\n",sinifile,strerror(errno));
		emsg("Could not open (%.500s) %.500s\n",sinifile,strerror(errno));
		exit(99);
		return;
	}
//	printf("vini_load(%s)\n",sinifile);
	vdomains_count = 0;
again:
	for (;!feof(f);) {
		if (fgets(bf,BIGBF-1,f)==NULL) break;

again2:
		ncpy(cur_line,bf,BFSZ-1);

		lprintf(state,BFSZ-1,"Read line %d %d sec",nlines,(int) (time(NULL)-start));
		vini_mutex_state(state);
		nlines++;
		
		s = strchr(bf,'\n'); if (s!=NULL) *s = 0;
		s = strchr(bf,'\r'); if (s!=NULL) *s = 0;
		/* Tokenize the string */
		np = vini_token_quote(bf,p,80);

		if (np<1) continue;
		mystrlwr(p[0]);

		if (strcmp(p[0],"g_include")==0 && np==2 && fstack<5) {
			word_add(&gfiles,p[1]); gfiles_upto = gfiles;
		}
		if (strcmp(p[0],"include")==0 && np==2 && fstack<5) {
			FILE *fnew;
			fnew = fopen(p[1],"r");
			if (fnew!=NULL) {
				infiles[fstack++] = f;
				f = fnew;
				include_used = TRUE;
			} else printf("include failed (%s) (%s)\n",p[1],strerror(errno));
			continue;
		}
		if (strncmp(p[0],"#",1)==0) continue; /* A comment */
		if (strcmp(p[0],"global_settings")==0)  {
			cur = curdom = vini_new_domain();
			inclass = FALSE;
			inglobal = TRUE;
			continue;
		}
		if (np<2) continue;
		for (i=0; i<ntable; i++) {
			if (strcmp(itypes[i].name,p[0])==0) break;
		}

		if (i>=ntable) {			
			printf("load: Invalid ini setting (%.500s)\n",p[0]);
			emsg("load: Invalid ini setting (%.500s)\n",p[0]);
			continue;
		}
		if (itypes[i].type & VINI_VDOMAIN) {
			if (inglobal) {
				FILE *fnew;
				if (gfiles_upto!=NULL) {
					g_include = TRUE;
					ncpy(saved_line,cur_line,BFSZ-1);
					fnew = fopen(gfiles_upto->word,"r");
					if (fnew!=NULL) {
						infiles[fstack++] = f;
						f = fnew;
					} else printf("include failed (%s) (%s)\n",p[1],strerror(errno));
					gfiles_upto = gfiles_upto->next;
					continue;
				}
			}
			if (!inglobal) {
				cur = curdom = vini_new_domain();
				if (g_include) cur->no_edit = TRUE;
				//printf("New domain %p\n",cur);
				inclass = FALSE;
			}
			inglobal = FALSE;
		}
		if (itypes[i].type & VINI_CLASS) {
			cur = curcls = vini_new_class(curdom);
			inclass = TRUE;
			inglobal = FALSE;
		}
		if (cur==NULL) {
			printf("Invalid setting (%.500s) before vdomain line\n",p[0]);
			emsg("Invalid setting (%.500s) before \"global_settings\" or \"vdomain\" line\n",p[0]);
			continue;
		}
		if (inclass) {
			if ((itypes[i].type & VINI_NOCLASS)!=0) {
				printf("Not a class setting (%.500s)\n",p[0]);
				emsg("Not a class setting (%.500s)\n",p[0]);
				continue;
			}
		} else {
			if ((itypes[i].type & VINI_NOCLASS)==0) {
				printf("Is a class setting but we are not in a class section (%.500s)\n",p[0]);
				emsg("Is a class setting but we are not in a class section (%.500s)\n",p[0]);
				continue;
			}
		}

		t = &itypes[i];
		tval = cur->table[i];
		if (tval!=NULL) if (!t->ismulti) {
			//emsg("Duplicate setting ignored %d (%.500s)\n",nlines,cur_line);
		}
		code = i;
		vnext = &cur->table[i];

		if ((lastcode==code) && (lastcur==cur) && (lastvnext==vnext)&& (lasttval==tval)) {
			v = lastv;
		} else {
			v = tval;
		}
		for (;v!=NULL;v=v->next) {
			lastv = v;
			vnext = &v->next;
		}

		lastcode = code;
		lastcur = cur;
		lastvnext = &cur->table[i];
		lasttval = tval;

		vini_newval(vnext); /* Create new value entry for this ini setting */
		v = *vnext;
		if (g_include) v->no_edit = TRUE;
		if (!vini_set_val(t,v,p,np)) {
			emsg("Problem line: %d %.500s\n",nlines,cur_line);
		}
	}
	fclose(f);
	f = NULL;
	if (fstack>0) {
		f = infiles[--fstack];
		if (lib_strlen(saved_line)>0) {
			ncpy(bf,saved_line,BFSZ-1);
			g_include = FALSE;
			goto again2;
		}
		goto again;
	}
	/*
	{
		Initable *r;
		for (i=0,r=vdomains; r!=NULL; r=r->next,i++) {
			//imsg("vini_loaded[%d]=%p\n",i,r);
			//printf("Domains vini_loaded[%d]=%p\n",i,r);
		}
	}
	*/
	word_free(&gfiles);
}
#define tr vline = __LINE__
int vini_set_oneof(Initable *tble, int mid, char *line);

int vini_set(int domainid,int classid, int mid,char *line)
{
	/* Find the write table, and set the variable */
	Initable *t;
	int n=0;


	vini_mutex_wait(__FILE__,__LINE__); // lockup in here somehow...
tr;
	/* Find 'n' domain */
	for (t=vini_domains();t!=NULL;t=t->next,n++) if (n==domainid) break;
tr;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		printf("vini_set domainid %d doesn't exist\n",domainid);
		emsg("vini_set domainid %d doesn't exist\n",domainid);
		return FALSE;
	}
tr;

	/* Find 'n'th class of that domain */
	if (classid>=0) {
		n = 0;
		for (t=t->classes;t!=NULL;t=t->next,n++) if (n==classid) break;
tr;
		if (t==NULL) {
			vini_mutex_release(__FILE__,__LINE__);
			// emsg("vini_set classid %d:%d doesn't exist\n",domainid,classid);
			// cannot use emsg while ini locked...
			return FALSE;
		}
	}

	/* Now set the setting for the mid'th setting of it */
tr;
	vini_set_oneof(t,mid,line);
tr;
	vini_mutex_release(__FILE__,__LINE__);
	return TRUE;
}
int vini_global_set(char *line)
{
	return vini_set(0,-1,-1,line);
}

int vini_clear(int domainid,int classid, int x)
{
	/* Find the write table, and set the variable */
	Initable *t;
	int n=0;
	vini_mutex_wait(__FILE__,__LINE__);
	for (t=vini_domains();t!=NULL;t=t->next,n++) if (n==domainid) break;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		printf("vini_set domainid %d doesn't exist\n",domainid);
		emsg("vini_set domainid %d doesn't exist\n",domainid);
		return FALSE;
	}
	n = 0;
	for (t=t->classes;t!=NULL;t=t->next,n++) if (n==classid) break;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		emsg("vini_set classid %d:%d doesn't exist\n",domainid,classid);
		return FALSE;
	}

	/* Now set the setting for the mid'th setting of it */
	vini_clear_one(t,x);
	vini_mutex_release(__FILE__,__LINE__);
	return TRUE;
}
int vini_dom_clear(int domainid, int x)
{
	/* Find the write table, and set the variable */
	Initable *t;
	int n=0;
	vini_mutex_wait(__FILE__,__LINE__);
	for (t=vini_domains();t!=NULL;t=t->next,n++) if (n==domainid) break;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		emsg("vini_set domainid %d doesn't exist\n",domainid);
		return FALSE;
	}
	/* Now set the setting for the mid'th setting of it */
	vini_clear_one(t,x);
	vini_mutex_release(__FILE__,__LINE__);
	return TRUE;
}

int vini_clear_one_with_value(Initable *tble, int x, char *field, char *val);

int vini_dom_clear_with_value(int domainid, int x, char *field, char *val)
{
	/* Find the write table, and set the variable */
	Initable *t;
	int n=0;
	vini_mutex_wait(__FILE__,__LINE__);
	for (t=vini_domains();t!=NULL;t=t->next,n++) if (n==domainid) break;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		emsg("vini_set domainid %d doesn't exist\n",domainid);
		return FALSE;
	}
	/* Now set the setting for the mid'th setting of it */
	vini_clear_one_with_value(t,x,field,val);
	vini_mutex_release(__FILE__,__LINE__);
	return TRUE;
}

int vini_class_delete(int domainid,int classid)
{
	/* Find the write table, and set the variable */
	Initable *t,**tlast;
	int n=0;
	vini_mutex_wait(__FILE__,__LINE__);
	for (t=vini_domains();t!=NULL;t=t->next,n++) if (n==domainid) break;
	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		emsg("vini_set domainid %d doesn't exist\n",domainid);
		return FALSE;
	}
	n = 0;
	tlast = &(t->classes);
	for (t=t->classes;t!=NULL;t=t->next,n++) {
		if (n==classid) {
			*tlast = t->next;
			//vini_free_table(t);
			t->todel = TRUE;
			vini_mutex_release(__FILE__,__LINE__);
			return TRUE;
		}
		tlast = &(t->next);
	}
	vini_mutex_release(__FILE__,__LINE__);
	return FALSE;
}
int vini_domain_delete(int domainid, char *domname)
{
	/* Find the write table, and set the variable */
	Initable *t,**tlast;
	int n=0;

	vini_mutex_wait(__FILE__,__LINE__);
	tlast = &vdomains;
	for (t=vini_domains();t!=NULL;t=t->next,n++) {
		if (n==domainid) {
			*tlast = t->next;
			vini_free_domain(t);
			vini_mutex_release(__FILE__,__LINE__);
			return TRUE;
		}
		tlast = &(t->next);
	}
	vini_mutex_release(__FILE__,__LINE__);
	return FALSE;
}
int vini_free_domain(Initable *tt)
{
	Initable *t,*tnext;
	for (t=tt->classes;t!=NULL;t=tnext) {
		tnext = t->next;
		//vini_free_table(t);
		t->todel = TRUE;
	}
	//vini_free_table(tt);
	tt->todel = TRUE;
	return TRUE;
}
int vini_free_table_broken_use_dangling(Initable *t)
{
//imsg("vini_free_table %p\n",t);
	vini_free_inival(t->table);
	free(t->table);
	t->table = NULL;
	free(t);
	return TRUE;
}
int vini_clear_one(Initable *tble, int x)
{
	Inival *tval,*vnext;
	tval = tble->table[x];
	for (;tval!=NULL;tval=vnext) {
		vnext = tval->next;
		vini_free_val(tval);
		free(tval);
	}
	tble->table[x] = NULL;
	return TRUE;
}
int vini_clear_one_with_value(Initable *tble, int x, char *field, char *val)
{
	int field_index=-1;
	int i;
	Inival *tval;
	Inival **tvalp;
	Initype it=itypes[x];
	tval = tble->table[x];
	for(i=0;it.pname[i];i++) {
		if (stricmp(it.pname[i],field)==0) {
			field_index=i;
			break;
		}
	}


	if (field_index<0) return FALSE;

	tvalp=&(tble->table[x]);
	while (*tvalp!=NULL) {
		tval=*tvalp;
		if (tval->val[field_index] && stricmp(tval->val[field_index],val)==0) {
			*tvalp=tval->next;
			vini_free_val(tval);
			free(tval);
		}
		else {
			tvalp=&(tval->next);
		}
	}
	return TRUE;
}
char *vini_get_crash(int *line)
{
	*line = vline;
	return vline_bf;
}
int vini_set_oneof(Initable *tble, int mid, char *xline)
{
	int np;
	char *p[100];
	int i;
	int n=0;
	Initype *typ;
	Inival **last,*tval,**vnext,*v;
	char line[BFSZ];
	line[0]='\0';
	lprintf(line,BFSZ-10,"%s",xline);
tr;
	ncpy(vline_bf,xline,BFSZ-1);
	np = vini_token_quote(line,p,80);
	if (np<2) return FALSE;
	mystrlwr(p[0]);
tr;
	for (i=0; i<ntable; i++) if (strcmp(itypes[i].name,p[0])==0) break;
	if (i>=ntable) {
		char *s=NULL;
		tr;
		emsg("oneof: Invalid ini setting (%.500s)\n",p[0]);
//		*s = 0;
		return FALSE;
	}
tr;
	typ = &itypes[i];
	last = &tble->table[i];
	tval = tble->table[i];

	if (typ->ismulti) {
		/* Find the 'n'th setting of this multi value setting */
		tr;
		for (;tval!=NULL;tval=tval->next,n++) {
			if (n==mid) break;
			last = &tval->next;
		}
	}
	tr;
	if (mid>=0) if (tval==NULL) {
		tr;
		printf("Setting not defined (%.500s)\n",p[0]);
		emsg("Setting not defined (%.500s)\n",p[0]);
		vini_abort("Setting not defined");
	}
	tr;
//lib_msleep(100000000);
	if (tval==NULL) {
		tr;
		vnext = &tble->table[i];
		for (v=tble->table[i];v!=NULL;v=v->next) vnext = &v->next;
		tr;
		vini_newval(vnext); /* Create new value entry for this ini setting */
		tr;
		tval = *vnext;
	}
	tr;
	vini_free_val(tval);
	if (!vini_set_val(typ,tval,p,np)) {
		tr;
			emsg("Problem line: %.500s\n",xline);
	}
	tr;
	return TRUE;
}
int vini_set_val(Initype *t, Inival *v, char *p[], int np)
{
	int i;
	int ret = TRUE;
	char *p1=p[1];
	if (t->np==1 && !(t->type&VINI_FIELDS)) { /* Single parameter */
		if (v->val[0]!=NULL) {
			//emsg("Duplicate setting ignored (%.500s)\n",p[0]);
		}
		if (t->ptype[0]==T_BOOL) {
			if (strcmpnc(p1,"on")==0) p1 = "TRUE";
		}
		v->val[0] = strdup(p1);
		v->vali[0] = atoi(p1);
		if (t->ptype[0]==T_BOOL) {
			v->vali[0] = FALSE;
			if (strcmpnc(v->val[0],"true")==0) v->vali[0] = TRUE;
			if (strcmpnc(v->val[0],"\"true\"")==0) v->vali[0] = TRUE;
		}
	} else { /* Parameters aaa=bbb*/
		// emsg("Add multi parameter setting %d\n",np);
		for (i=1; i<np; i+=3) {
			if (i+3>np) break;
			if (!vini_readone(t,v,p[i],p[i+1],p[i+2])) {
				ret = FALSE;
				break;
			}
		}
	}
	return ret;
}
char *vini_file(void)
{
//	vini_symlink(inifile,sinifile);
	return sinifile;
}
//	vini_set_encrypt(I_G_GATEWAY,"pass",I_G_MIRROR_SECRET,auto_encrypt);
int vini_write_log(char *src, int line)
{
	return vini_write_real(src,line,NULL);
}
int vini_write_real(char *src, int line, char *altfile)
{
	/* Write out a new dftp.ini file */
	Initable *t;
	FILE *f;
	char bfile[BFSZ];
	char tmp[BFSZ];
	int failed=FALSE;

	if (include_used) {
		emsg("INCLUDE_USED in surgemail.ini SO VINI_SAVE IS FAILING\n");
		return FALSE;
	}

	if (altfile) {
		lprintf(bfile,BFSZ,"%s.tmp",altfile);
		f = fopen(bfile,"w");
	}
	else {
		lprintf(bfile,BFSZ,"%s_old",sinifile);
		lprintf(tmp,BFSZ,"%s_old2",sinifile);
		if (remove(tmp)!=0) ;//printf("Remove (%s) failed %s\n",bfile,strerror(errno));
		if (rename(bfile,tmp)!=0) ;//printf("Rename (%s) failed %s\n",bfile,strerror(errno));
		if (rename(sinifile,bfile)!=0) ;//printf("Rename (%s) failed %s\n",bfile,strerror(errno));
		f = fopen(sinifile,"w");
	}
	if (!f) {
		emsg("Could not write ini file: %s",strerror(errno));
//		printf("Could not write ini file: %s\n",strerror(errno));
		return FALSE;
	}
	fprintf(f,"# WARNING - so you think you can modify this file by hand?  Not so fast!!!\n");
	fprintf(f,"# Ok, you can IF you stop the server process first OR do a RELOAD after making your\n");
	fprintf(f,"# changes OR don't modify anything with the web admin tool before the next restart\n");
	fprintf(f,"# otherwise your changes will be lost.\n");
	fprintf(f,"# --------------------------------------------------------------------------------------------------\n");
	for (t=vini_domains();t!=NULL;t=t->next) {
		vini_showconfig(f,t);
	}
	if (fclose(f)!=0) failed = TRUE;

	if (!altfile) if (failed) { // Restore the old file if the write failed. 
		remove(sinifile);
		rename(bfile,sinifile);
	}
	if (altfile) {
		if (!remove_rename(altfile,bfile,tmp,BFSZ)) {
			emsg("Could not remove/rename (%s) to (%s): %s",altfile,bfile,tmp);
			return FALSE;
		}
	} else {
		if (kmsg) {
			kmsg("vini_write %s:%d, length %d\n",src,line,lib_file_size(sinifile));
		}
	}
	
	return TRUE;
}





int vini_readone(Initype *t, Inival *v, char *cmd, char *eq, char *val)
{
	int i;
	if (strcmp(eq,"=")!=0) {
		emsg("Equals sign missing after (%.500s)\n",cmd);
		return FALSE;
	}
	for (i=0; i<MAXP; i++) {
		if (t->pname[i]!=NULL) if (strcmp(t->pname[i],cmd)==0) break;
	}
	if (i>=MAXP) {
		emsg("Invalid ini file parameter (%.500s)\n",cmd);
		printf("Invalid ini file parameter (%.500s)\n",cmd);
		for (i=0; i<MAXP; i++) {
			if (t->pname[i]!=NULL) emsg("Valid parameters for %.500s (%.500s)\n",t->name,t->pname[i]);
			if (t->pname[i]!=NULL) printf("Valid parameters for %.500s (%.500s)\n",t->name,t->pname[i]);
		}
		return FALSE;
	}
	if (v->val[i]!=NULL) {
		emsg("Duplicate paramter ignored (%.500s) was set to (%.500s)\n",cmd,v->val[i]);
		return FALSE;
	}
/*emsg("Added parameter %p, [%d] (%.500s) = (%.500s) v=%p\n",v,i,t->pname[i],val,v);*/
	if (t->ptype[i]==T_BOOL) {
		if (strcmpnc(val,"on")==0) val = "TRUE";
	}
	v->val[i] = strdup(val);
	v->vali[i] = atoi(val);
	if (t->ptype[i]==T_BOOL) {
		v->vali[i] = FALSE;
		if (strcmpnc(v->val[i],"true")==0) v->vali[i] = TRUE;
	}
	return TRUE;
}
int vini_free_val(Inival *v)
{
	int i;
	for (i=0; i<MAXP; i++) {
		if (v->val[i]!=NULL) free(v->val[i]);
		v->val[i] = NULL;
		v->vali[i] = 0;
	}
	return TRUE;
}
/*
typedef struct tracking_vini_handles{
	Initable *tbl;
	Inihandle **handleptr;
	Inihandle *handle;
	char *action;
	char *file;
	int line;
	int table_n;
}tracking_vini_handles;

static tracking_vini_handles handles[1000];
static int numberhandles = 0;

void vini_handle_add_do(Inihandle **h, char*infile, int atline){
	handles[numberhandles].handleptr = h;
	handles[numberhandles].handle = *h;
	handles[numberhandles].action = "add";
	handles[numberhandles].line = atline;
	handles[numberhandles].file = infile;
	if ((*h)->tbl != NULL) {
		handles[numberhandles].tbl = (*h)->tbl;
		handles[numberhandles].table_n = (*h)->tbl->nref;
	}else {
		handles[numberhandles].tbl = NULL;
		handles[numberhandles].table_n = 0;
	}
	numberhandles++;
}

void vini_handle_remove_do(Inihandle **h, char*infile, int atline){
	if (*h == NULL) return;
	handles[numberhandles].handleptr = h;
	handles[numberhandles].handle = *h;
	handles[numberhandles].action = "del";
	handles[numberhandles].line = atline;
	handles[numberhandles].file = infile;
	if ((*h)->tbl != NULL) {
		handles[numberhandles].tbl = (*h)->tbl;
		handles[numberhandles].table_n = (*h)->tbl->nref;
	}else {
		handles[numberhandles].tbl = NULL;
		handles[numberhandles].table_n = 0;
	}
	numberhandles++;
}
void vini_handle_imsg_remaining(void){
	int i;
	imsg("starting leaked handle pointers.");
	for (i=0;i<numberhandles;i++){
		imsg(" %s handle %p %p table(%p)nref=(%d) %s % 6d %s",
			handles[i].action,
			handles[i].handleptr,
			handles[i].handle,
			handles[i].tbl,
			handles[i].table_n,
			handles[i].action,
			handles[i].line,
			handles[i].file);
	}
	imsg("%d leaked handle pointers.", numberhandles);
}*/


void vini_gethandle_real(Inihandle **h,char *file,int line)
{
	*h = d_callocid(3,sizeof(Inihandle),1,file,line);
	if (*h==NULL) {
		printf("calloc returned null\n");
		emsg("calloc returned null\n");
		lib_abort();
	}
	vini_mutex_wait(file,line);
	(*h)->tbl = vdomains; /* Default primary table */
	vini_get_ref_real((*h)->tbl,&(*h)->n,*h,file,line);
	vini_mutex_release(file,line);
}
void vini_gethandle_stack(Inihandle *h)
{
	vini_gethandle_stack_line(h,__FILE__,__LINE__);
}
void vini_gethandle_stack_line(Inihandle *h, char *src, int line)
{
	memset(h,0,sizeof(Inihandle));
	vini_mutex_wait(src,line);
	h->tbl = vdomains; /* Default primary table */
	vini_get_ref(h->tbl,&(h->n),h);
	vini_mutex_release(src,line);
}
int vini_domains_count(void)
{
	return vdomains_count;
}
Initable *vini_domains(void)
{
	return vdomains;
}
void vini_freehandle_real(Inihandle **h, char *file, int line)
{
	if (*h==NULL) return;
//imsg("vini_free_handle %p\n",*h);
	vini_free_ref_real((*h)->tbl,(*h)->n,*h,file,line);
	(*h)->v = NULL;
	free(*h);
	*h = NULL;
}
void vini_freehandle_stack(Inihandle *h, char *file, int line)
{
	vini_free_ref_real(h->tbl,h->n,h,file,line);
	h->v = NULL;
}
int vini_dump_info(Inihandle *h)
{
	static int busy;
	if (!busy) {
		busy = TRUE;
		vini_startstop("vini_which, h->tbl->table is null -- Trying to dump some clues\n");	
		vini_startstop("Handle pointer is %p\n",h);	
		vini_startstop("Handle pointer ->tbl ==  %p\n",h->tbl);	
		vini_trace_show();
		vini_startstop("vini_which, h->tbl->table is null -- clues above...\n");	
		vini_startstop("Handle pointer is %p\n",h);	
		busy = FALSE;
	} 
	return TRUE;
}
// {int domain; int n; Inival *v; Initable *tbl; Initable *cls; } Inihandle;
/*
typedef struct INITABLE {
	int id; 
	Inival **table; 
	int nref; 
	void *ref[100];
	char *ref_file[100];
	int ref_line[100];
	int todel; 
	int no_edit;
	struct INITABLE *classes; 
	struct INITABLE *next;
} Initable;

*/
Inival *vini_which_with_mutex(Inihandle *h, int x, Initype **tt)
{
	static int vx;
	int crash_test=FALSE;
	Initype *t;
	Inival *v;
	int i=0;
	Initable *xvdomains;

	vini_mutex_wait(__FILE__,__LINE__);
	xvdomains = vdomains;
	for (i=0;xvdomains==NULL && i<1000;i++) {
		vini_mutex_release(__FILE__,__LINE__);
		lib_msleep(20);
		vini_mutex_wait(__FILE__,__LINE__);
		xvdomains = vdomains;
	}

	if (h==NULL) {
		if (xvdomains==NULL) {
			char *s=NULL;
			printf("No domains defined, server cannot run without any domains\n");
			*s = 0;
			vini_mutex_release(__FILE__,__LINE__);
			vini_abort("No domains defined - cannot continue");
		}
		v = xvdomains->table[x];
	} else if ((itypes[x].type & VINI_GLOBAL)!=0) {
		/* CHRISP CHANGE TO MAKE GLOBALS WORK RIGHT */
		v = xvdomains->table[x];
	} else {
		if (h->tbl==NULL) {
			vini_mutex_release(__FILE__,__LINE__);
			vini_abort("vini_which, h->tbl is null");
		}
		if (h->tbl == NULL) lib_abort();
		if (h->tbl->table==NULL) { // and this is sometimes true, which should be impossible.
			// maybe it happens during a crash...
			vini_dump_info(h);
			vini_mutex_release(__FILE__,__LINE__);
			vini_abort("vini_which, h->tbl->table is null");
		}
		if (h->tbl == NULL) lib_abort();
		v = h->tbl->table[x];	// Crashes here somteimes.
	}
	t = &itypes[x];
	if (!(t->type & VINI_NOCLASS)) {
		if (h!=NULL) if (h->cls!=NULL) {
			v = h->cls->table[x];
		}
	}

	if (t==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		vini_abort("vini_which, t is null");
	}
	if (t->name==NULL) {
		vini_mutex_release(__FILE__,__LINE__);
		vini_abort("vini_which: name is null");
	}
	if (x<0) {
		vini_mutex_release(__FILE__,__LINE__);
		vini_abort("vini_which: x is less than zero");
	}

	*tt = t;
	vini_mutex_release(__FILE__,__LINE__);
	return v;

}
Inival *vini_which(Inihandle *h, int x, Initype **tt)
{
	static int vx;
	int crash_test=FALSE;
	Initype *t;
	Inival *v;
	int i=0;
	Initable *xvdomains;

	xvdomains = vdomains;
	for (i=0;xvdomains==NULL && i<1000;i++) {
		lib_msleep(20);
		xvdomains = vdomains;
	}

	if (h==NULL) {
		if (xvdomains==NULL) {
			char *s=NULL;
			printf("No domains defined, server cannot run without any domains\n");
			*s = 0;
			vini_mutex_release(__FILE__,__LINE__);
			vini_abort("No domains defined - cannot continue");
		}
		v = xvdomains->table[x];
	} else if ((itypes[x].type & VINI_GLOBAL)!=0) {
		/* CHRISP CHANGE TO MAKE GLOBALS WORK RIGHT */
		v = xvdomains->table[x];
	} else {
		if (h->tbl==NULL) {
			vini_abort("vini_which, h->tbl is null");
		}
		if (h->tbl == NULL) lib_abort();
		if (h->tbl->table==NULL) { // and this is sometimes true, which should be impossible.
			// maybe it happens during a crash...
			vini_dump_info(h);
			vini_abort("vini_which, h->tbl->table is null");
		}
		if (h->tbl == NULL) lib_abort();
		v = h->tbl->table[x];	// Crashes here somteimes.
	}
	t = &itypes[x];
	if (!(t->type & VINI_NOCLASS)) {
		if (h!=NULL) if (h->cls!=NULL) {
			v = h->cls->table[x];
		}
	}

	if (t==NULL) {
		vini_abort("vini_which, t is null");
	}
	if (t->name==NULL) {
		vini_abort("vini_which: name is null");
	}
	if (x<0) {
		vini_abort("vini_which: x is less than zero");
	}

	*tt = t;
	return v;

}
char *vini_get_name_domain(Inihandle *ini, char *name)
{
	Initype *t;
	Inival *v;
	int i;

	for (i = 0; i < ntable; i++) {
		if (strcmpnc(name,itypes[i].name)==0) {
			v = vini_which(ini, i, &t);
			if (v==NULL) return t->dflt;
			if (v->val[0]==NULL) return t->dflt;
			return v->val[0];
		}
	}
	return "";
}

char *vini_get_namex(char *name, char *file, int line)
{
	/* Slow but works for multiple ini.h files at same time, only for globals */
	Inival *r;
	Initype *t;
	int i;
	Inival **b;
	vini_mutex_wait(file,line);
	b = vdomains->table;
	for (i=0; i<ntable; i++) {
		r = b[i];
		if (strcmpnc(name,itypes[i].name)==0) {
			vini_which(NULL, i, &t);
			vini_mutex_release(file,line);
			if (r==NULL) return t->dflt;
			if (r->val[0]==NULL) return t->dflt;
			return r->val[0];
		}
	}
	vini_mutex_release(file,line);
	return "";
}
char *vini_show_all(void)
{
	/* Slow but works for multiple ini.h files at same time, only for globals */
	Inival *r;
	int i;
	Inival **b;
	vini_mutex_wait(__FILE__,__LINE__);
	b = vdomains->table;
	for (i=0; i<ntable; i++) {
		r = b[i];
		printf("\t\tnames.append(\"%s\")\n",itypes[i].name);
	}
	vini_mutex_release(__FILE__,__LINE__);
	return "";
}

char *vini_get(Inihandle *h, int x)
{
	Initype *t;
	Inival *v;

	v = vini_which(h,x,&t);
	if (v==NULL) return t->dflt;
	if (v->val[0]==NULL) return t->dflt;
	return v->val[0];
}
int is_str_true(char *str){
	if (str == NULL) return FALSE;
	if (*(str+0) != 'T' && *(str+0) != 't') return FALSE;
	if (*(str+1) != 'R' && *(str+1) != 'r') return FALSE;
	if (*(str+2) != 'U' && *(str+2) != 'u') return FALSE;
	if (*(str+3) != 'E' && *(str+3) != 'e') return FALSE;
	return TRUE;
}
int vini_global_istrue(int x)
{
	Initype *t;
	Inival *v;
	v = vini_which(NULL,x,&t);
	if (v==NULL) return is_str_true(t->dflt);
	if (v->val[0]==NULL) return is_str_true(t->dflt);
	return is_str_true(v->val[0]);
}
char *vini_setting_name(int i) {
	if (i >= ntable) return NULL;
	return itypes[i].name;
}
int vini_find_name(char *s)
{
	int i;
	for (i=0; i<ntable; i++) {
		if (strcmp(itypes[i].name,s)==0) return i;
	}
	return -1;
}
char *vini_get_varname( int x)
{
	return itypes[x].name;
}
int vini_get_vartype(int x)
{
	return itypes[x].type;
}
int vini_type_domain(int x)
{
	if ((itypes[x].type & VINI_CLASS)!=0) return FALSE;
	if ((itypes[x].type & VINI_VDOMAIN)!=0) return FALSE;
	if ((itypes[x].type & VINI_GLOBAL)==0) return TRUE;
//		if ((itypes[x].type & VINI_MULTI)==0) return TRUE;
	return FALSE;
}
int vini_type_fields(int x)
{
	return (itypes[x].type & VINI_FIELDS);
}
int vini_type_global(int x)
{
	if ((itypes[x].type & VINI_GLOBAL)!=0)
		if ((itypes[x].type & VINI_MULTI)==0) return TRUE;
	return FALSE;
}
int vini_no_edit(int x)
{
	Initype *t;
	Inival *v;

	if (x<0) return FALSE;
	v = vini_which(NULL,x,&t);
	if (v==NULL) return FALSE;
	for (;v!=NULL;v=v->next) {
		if (v->no_edit) return TRUE;
	}

	return FALSE;
}
int vini_type_multi(int x)
{
	if ((itypes[x].type & VINI_MULTI)!=0) return TRUE;
	return FALSE;
}
int vini_type_gmulti(int x)
{
	if ((itypes[x].type & VINI_GLOBAL)!=0)
		if ((itypes[x].type & VINI_MULTI)!=0) return TRUE;
	return FALSE;
}
char *vini_get_vardesc(int x)
{
	return itypes[x].desc;
}
int vini_isbool(int x,int i)
{
	return (itypes[x].ptype[i] == T_BOOL);
}
int vini_get_np(int x)
{
	return itypes[x].np;
}
char *vini_get_pname(int x,int i)
{
	if (itypes[x].pname[i]==NULL) return "parameter";
	return itypes[x].pname[i];
}
int vini_true(Inihandle *h, int x)
{
	Initype *t;
	Inival *v;
	v = vini_which(h,x,&t);
	if (v==NULL) return FALSE;
	return v->vali[0];
}
int vini_geti(Inihandle *h, int x)
{
	Initype *t;
	Inival *v;
	v = vini_which(h,x,&t);
	if (v==NULL) return FALSE;
	return v->vali[0];
}
char *vini_get_p(Inihandle *h, int x, char *param)
{
	Initype *t;
	Inival *v;
	int i;

	v = vini_which(h,x,&t);

	for (i=0; i<t->np; i++) {
		if (strcmp(t->pname[i],param)==0) {
			if (v==NULL) return "";
			if (v->val[i]==NULL) return "";
			return v->val[i];
		}
	}
//	printf("invalid parameter %d (%.500s)\n",x,param);
	printf("invalid parameter %d (%.500s)\n",x,param);
	emsg("invalid parameter %d (%.500s)\n",x,param);
	vini_abort("Invalid paramter");
	return NULL;
}
char *vini_get_p_val(Inival *v, int x, char *param)
{
	int i;
	Initype *t;
	t = &itypes[x];
	for (i=0; i<t->np; i++) {
		if (strcmp(t->pname[i],param)==0) {
			if (v==NULL) return "";
			if (v->val[i]==NULL) return "";
			return v->val[i];
		}
	}
	return NULL;
}
int vini_set_p(Inihandle *h, int x, char *param, char *val)
{
	Initype *t;
	Inival *v;
	int i;
	char *old;

	v = vini_which(h,x,&t);

	for (i=0; i<t->np; i++) {
		if (strcmp(t->pname[i],param)==0) {
			if (v!=NULL) {
				if (v->val[i]!=NULL) {
					old = v->val[i];
					v->val[i] = NULL;
					free(old);
				}
				v->val[i] = strdup(val);
			}
			return TRUE;
		}
	}
//	printf("invalid parameter %d (%.500s)\n",x,param);
	emsg("invalid parameter %d (%.500s)\n",x,param);
	return FALSE;
}
char *vini_get_p_raw(Inival *v, int x, char *param)
{
	Initype *t;
	int i;

	t = &itypes[x];
	if (t==NULL) {
//		printf("vini_get_p_raw \n");
		vini_abort("vini_get_p_raw: t is null");
	}

	if (t->name==NULL) {
//		printf("vini_get_p_raw2 \n");
		vini_abort("vini_get_p_raw: name is null");
	}
	if (x<0) {
//		printf("vini_get_p_raw3 \n");
		vini_abort("vini_get_p_raw: x is sub zero");
	}
	for (i=0; i<t->np; i++) {
		if (strcmp(t->pname[i],param)==0) {
			if (v==NULL) return "";
			if (v->val[i]==NULL) return "";
			return v->val[i];
		}
	}
	printf("invalid parameter %d (%.500s)\n",x,param);
	emsg("invalid parameter %d (%.500s)\n",x,param);
	vini_abort("vini_get_p_raw: no such parameter");
	return NULL;
}
int vini_geti_p(Inihandle *h, int x, char *param)
{
	return TRUE;
}
char *vini_cur_p(Inihandle *h, int x, char *param)
{
	Initype *t;
	int i;
	Inival *v;

	v = vini_which(h,x,&t);
	if (t->name==NULL) vini_abort("vini_cur_p: name is null");
	if (x<0) vini_abort("vini_cur_p: x sub");
	for (i=0; i<t->np; i++) {
		if (t->pname[i]!=NULL) if (strcmp(t->pname[i],param)==0) {
			if (h->v->val[i]==NULL) return "";
			return h->v->val[i];
		}
	}
	printf("invalid parameter %d (%.500s)\n",x,param);
	emsg("invalid parameter %d (%.500s)\n",x,param);
	vini_abort("vini_cur_p: no param");
	return NULL;
}
char *vini_cur(Inihandle *h, int x)
{
	Initype *t;
	Inival *v;

	v = vini_which(h,x,&t);
	if (t->name==NULL) {
//		printf("abortvini_cur\n");
		vini_abort("vini_cur: name is null");
	}
	if (x<0) vini_abort("vini_cur: x is sub");

	return h->v->val[0];
}
int vini_cur_p_true(Inihandle *h, int x, char *param)
{
	Initype *t;
	int i;
	char bf[BFSZ];
	Inival *v;

	v = vini_which(h,x,&t);
	if (t->name==NULL) vini_abort("vini_cur_p_True: name is null");
	if (x<0) vini_abort("x");
	for (i=0; i<t->np; i++) {
		if (t->pname[i]!=NULL) if (strcmp(t->pname[i],param)==0) {
			if ((h->v->val[i])==NULL) return FALSE;
			lprintf(bf,BFSZ-10,"%s",h->v->val[i]);
			mystrlwr(bf);
			if (strcmp(bf,"true")==0) return TRUE;
			return FALSE;
		}
	}
	printf("invalid parameter %d (%.500s)\n",x,param);
	emsg("invalid parameter %d (%.500s)\n",x,param);
	vini_abort("no parmater");
	return FALSE;
}
int vini_curi_p(Inihandle *h, int x, char *param)
{
	return TRUE;
}

int vini_cur_ptr_true(Iniparam *inip, char *param)
{
	Initype *t;
	int i;
	char bf[BFSZ];
	Inival *v;

	v = vini_which(inip->h,inip->x,&t);
	if (t->name==NULL) vini_abort("vini_cur_ptr_True: name is null");
	if (inip->x<0) vini_abort("vini_cur_ptr_true: x");
	for (i=0; i<t->np; i++) {
		if (t->pname[i]!=NULL) if (strcmp(t->pname[i],param)==0) {
			if ((inip->v->val[i])==NULL) return FALSE;
			lprintf(bf,BFSZ-10,"%s",inip->v->val[i]);
			mystrlwr(bf);
			if (strcmp(bf,"true")==0) return TRUE;
			return FALSE;
		}
	}
	printf("invalid parameter %d (%.500s)\n",inip->x,param);
	emsg("invalid parameter %d (%.500s)\n",inip->x,param);
	vini_abort("vini_cur_ptr: invalid param");
	return FALSE;
}


char *vini_cur_ptr(Iniparam *inip, char *param)
{
	Initype *t;
	int i;
	Inival *v;

	v = vini_which(inip->h,inip->x,&t);
	if (t->name==NULL) vini_abort("vini_cur_ptr: name is null");
	if (inip->x<0) vini_abort("vini_cur_ptr: x");
	for (i=0; i<t->np; i++) {
		if (t->pname[i]!=NULL) if (strcmp(t->pname[i],param)==0) {
			if (inip->v->val[i]==NULL) return "";
			return inip->v->val[i];
		}
	}
	printf("invalid parameter %d (%.500s)\n",inip->x,param);
	emsg("invalid parameter %d (%.500s)\n",inip->x,param);
	vini_abort("vini_cur_ptr: param");
	return NULL;
}
Iniparam *vini_next_ptr(Iniparam *inip)
{
	if (inip->v==NULL) return NULL;
	if (inip->v->next==NULL) return NULL;
	inip->v = inip->v->next;
	return inip;
}
Iniparam *vini_find_ptr(Inihandle *h, int x, Iniparam **output)
{
	Initype *t;
	(*output)->h = h;
	(*output)->v = vini_which(h,x,&t);;
	(*output)->t = t;
	(*output)->x = x;
	if (t->name==NULL) vini_abort("vini_find_ptr: null");
	if (x<0) vini_abort("vini_find_ptr: x");
	if ((*output)->v == NULL) return NULL;
	return *output;
}
int vini_next(Inihandle *h, int x)
{
	if (h->v==NULL) return FALSE;
	if (h->v->next==NULL) return FALSE;
	h->v = h->v->next;
	return TRUE;
}
int vini_find(Inihandle *h, int x)
{
	Initype *t;
	Inival *v;


	v = vini_which(h,x,&t);

	if (t->name==NULL) vini_abort("vini_find: name");
	if (x<0) vini_abort("find: x");
	h->v = v;
	if (v==NULL) return FALSE;
	return TRUE;
}
int vini_count(Inihandle *h, int x)
{
	int r,count = 0;
	for(r = vini_find(h,x); r; r = vini_next(h,x)) count++;
	return count;
}

int vini_token_quote(char *s, char *p[], int tmax)
{
        int n=0;
	char *sep=" \t";
	char *sep2="= \t";

        for (;*s!=0;) {
                s += strspn(s,sep);  /* Skip spaces */
                if (*s==0) break;
		if (*s=='"') {
			s++;
	                p[n++] = s;
			for (;*s!=0;s++) {
				if (*s=='"') break;
//				if (*s=='\\') if (s[1]!=0) s++;
			}
			goto dorest;
		}
		if (*s=='=') {
	                p[n++] = "=";
			goto dorest;
		}
                p[n++] = s;
                s += strcspn(s,sep2);  /* Find end of word */
		if (*s=='=') {
			p[n++] = "=";
		}
dorest:
                if (*s==0) break;
                *s = 0; s++;
                if ((n+5)>=tmax) {
                        emsg("Too many tokens in this header line\n");
                        break;
                }
        }
        p[n] = NULL;
        return n;
}
static int stats_ref;
int vini_get_stats_ref(void)
{
	return stats_ref;
}
//int  kmsg(const char * arg_list, ...);

/* Stuff to make this thread safe */
// Lets count vini references by file/line...
#define MAXSTATS 10000
static char *stats_file[MAXSTATS];
static int stats_line[MAXSTATS];
static int stats_count[MAXSTATS];

Initable *vini_get_ref_real(Initable *t, int *pn, void *x, char *file, int line)
{
	int n;
	int h = line % MAXSTATS;
	vini_mutex_wait(file,line);
	stats_ref++;
	stats_file[h] = file;
	stats_line[h] = line;
	stats_count[h]++;
	vini_trace_add("get ",t,t->nref,file,line);
//	printf("vini_get_ref %p %d %s:%d\n",t,t->nref,file,line);
	if (t==NULL) {
//		printf("vini_get_ref_real failed as initable was null - surgemail.ini corrupt or doesn't exist - examine it carefully\n");
		vini_abort("get_ref: t=null");
	}
//	t->nref;

	for (n=0; n<100; n++) { /* Find a free entry in our reference table */
		if (t == NULL) lib_abort();
		if (t->ref[n]==NULL) break;
	}
	if (n>=100) n = -1;
	*pn = n;
	if (n>=0 && n<100) {
		t->ref[n] = x;
		t->ref_file[n] = file;
		t->ref_line[n] = line;
	}
//kmsg("vini_get_ref %p %d %s:%d %p\n",t,t->nref,file,line,x);
	t->nref++ ;
	vini_mutex_release(file,line); //__FILE__,__LINE__);
	return t;
}
void vini_stats_show(FILE *f)
{
	int i;
	for (i=0; i<MAXSTATS; i++) {
		if (stats_file[i]!=NULL) fprintf(f,"%s:%d %d\n",stats_file[i],stats_line[i],stats_count[i]);
	}
}
void vini_free_ref_real(Initable *t, int n, void *x, char *file, int line)
{
	int h = line % MAXSTATS;
	vini_mutex_wait(file,line);

	stats_file[h] = file;
	stats_line[h] = line;
	stats_count[h]--;

	stats_ref--;
	vini_trace_add("free",t,t->nref,file,line);
//kmsg("vini_free_ref %p %d %s:%d n=%d %p\n",t,t->nref,file,line,n,x);
//	printf("vini_free_ref %p %d %s:%d\n",t,t->nref,file,line);

	if (t->id!=1234) {
		vini_startstop("vini_free_ref_real failing %s:%d %d %d\n",file,line,t->id,t->nref);
		lib_abort();
	}
	if (n>=0 && n<100) {
		if (t->ref[n]!=x) {
			vini_startstop("vini_free_ref_real bad pointer %d %p, %s:%d %d %d, was %s:%d %p\n",n,x,file,line,t->id,t->nref,
				t->ref_file[n],t->ref_line[n],t->ref[n]);
			lib_abort();
		}
		t->ref[n] = NULL;
	}
	t->nref--;
	if (t->nref<=0) {
		if (t->nref<0) {
			vini_startstop("nref is less than zero %d %s:%d\n",t->nref,file,line);
			lib_abort();
		}
		if (t->todel) {
			//imsg("Freeing dangling ini handle %p\n",t);
			vini_free_dangling(t,file,line);
		} else {
			//imsg("Freeing dangling but not set to del %p\n",t);
		}
	} else {
		//imsg("Freeing dangling, ref count is %d %p\n",t->nref,t);
	}
	vini_mutex_release(file,line); //__FILE__,__LINE__);
}
typedef struct FREELIST {Initable *t; time_t when; struct FREELIST *next;} Freelist;
static Freelist *freelist;
void vini_free_later(Initable *t)
{
	Freelist *f;
//	vini_trace_add("free_later",t,t->nref,"free_later",0);
	f = calloc(1,sizeof(Freelist));
	f->when = time(NULL);
	f->t = t;
	f->next = freelist;
	freelist = f;
}
void vini_free_idle(int nowait)
{
	Freelist *f,**last,*next;
	vini_mutex_wait(__FILE__,__LINE__);
	vini_trace_add("free_idle",NULL,0,"free_idle",0);
	last = &freelist;
	for (f=freelist;f!=NULL;f=next) {
		next=f->next;
		if (  (nowait || (time(NULL) - f->when > 120)) && (f->t->nref==1) ) {
//		if (  (nowait || (time(NULL) - f->when > 1)) && (f->t->nref==1) ) {
			*last = f->next;
			f->t->nref--;
			vini_free_dangling(f->t,"idle",0);
			free(f);
		} else {
			last = &f->next;
		}
	}
	vini_mutex_release(__FILE__,__LINE__);
}
void vini_lock_all_x(char *fname, int line)
{
	int n=0;
	Initable *t,**last,*next;
	vini_mutex_wait(fname,line);
	vini_trace_add("lock_all ",NULL,0,fname,line);
	lock_fname = fname;
	lock_line = line;
	/* Go thru list of tables, any that have nref>0 duplicate and leave dangling */

	vini_mutex_state("lock_all_1");
	last = &(vdomains);
	for (t=vini_domains();t!=NULL;t=next) {
		n++;
		vini_mutex_state("lock_all_2");
		next = t->next;
//		if (t->nref>0 || TRUE) {
		if (t->nref>0 || n==1) {
			t->nref++;
			vini_mutex_state("lock_all_3");
			vini_trace_add("freelater",t,t->nref,fname,line);
			vini_free_later(t);
			//imsg("Creating dangling ini handle %p\n",t);
			*last = t  = vini_dangle(t); /* In turn it dangles any class tables */
		} else {
			*last = t;
		}
		t->next = next;
		last = &(t->next);
		vini_mutex_state("lock_all_4");
	}
	vini_mutex_state("lock_all_5");
	*last  = NULL;
}
static char *held_file;
static int held_at,held_was;
void vini_free_slow(Initable *start)
{
	Initable *t,*next;
	for (t=start;t!=NULL;t=next) {
		next = t->next;
		t->nref++;
		vini_trace_add("freeslow",t,t->nref,__FILE__,__LINE__);
		vini_free_later(t);
	}
}
static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds, char *file, int line);
int vini_set_mutex(int (*cb)(int vm, int isget, int seconds , char *file, int line), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}

void vini_unlock_all(void)
{
	char *was_fname = lock_fname;
	int was_line = lock_line;
	vini_trace_add("unlock_all ",NULL,0,"none",0);
	lock_fname = NULL;
	lock_line = 0;

// Experiement
	vini_free_idle(FALSE);

	vini_mutex_release(was_fname,was_line); //__FILE__,__LINE__);

}
void vini_unlock_real(char *file, int line)
{
	lock_fname = NULL;
	lock_line = 0;
	vini_mutex_release(file,line);
}
static int held_count;
static char mstate[BFSZ];
static time_t mstate_time;
void vini_mutex_state(char *s)
{
	ncpy(mstate,s,BFSZ-1);
	mstate_time = time(NULL);
}
static int mutex_dead;
static int mutex_id;



int vini_mutex_wait(char *fname, int line)
{
	static int done;
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600,fname,line);
	if (mutex_dead) {
		if (mutex_id == th_id()) return TRUE;
		lib_msleep(7000);
		return FALSE;
	}
	if (!th_mutex_wait(imutex,MUTEX_TIME)) {
		if (!done) {
			done = TRUE;
			mutex_id = th_id();
			if (held_file==NULL) held_file = "None";
			vini_abort("mutex");
		}
		mutex_dead = TRUE;
		lib_msleep(7000); // Wait 7 seconds, then continue to allow things to unlock.
		return TRUE;
	}
	held_count++;
	if (held_count==1) {
		held_at = line;
		held_file = fname;
	}
	return TRUE;
}
void vini_mutex_release(char *fname, int line)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600,fname,line); return; }
	if (held_count==1) {
		held_was = held_at;
		held_at = 0;
	}
	held_count--;
//	if (mutex_dead) if (held_count<0) return;
	th_mutex_release(imutex);
}

void vini_lock_x(char *fname, int line)
{
	vini_mutex_wait(fname,line);
	lock_fname = fname;
	lock_line = line;

}
Initable *vini_dangle(Initable *t)
{
	Initable *xnew;
	xnew = vini_duplicate(t);
	xnew->next = t->next;
	xnew->nref = 0;
	xnew->classes = vini_dangle_classes(t->classes);
	t->todel = TRUE;
	t->next = NULL;
	return xnew;
}
Initable *vini_dangle_classes(Initable *tlist)
{
	Initable *t,*nt,**last;
	Initable *newlist=NULL;

	last = &newlist;
	for (t=tlist;t!=NULL;t=t->next) {
//		imsg("Creating dangling ini handle class %p\n",t);
		nt = vini_duplicate(t);
		*last = nt;
		last = &(nt->next);
	}
	return newlist;
}
Initable *vini_duplicate(Initable *t)
{
	Initable *xnew;
	xnew = calloc(1,sizeof(Initable));
	xnew->id = 1234;
	xnew->table = malloc(sizeof(Inival *)*ntable);
//	emsg("vini_duplicated table %p\n",xnew->table);
	memset(xnew->table,0,sizeof(Inival *)*ntable);
	vini_copy_table(xnew,t); // some evidence of memory leak...
	xnew->nref = 0;
	xnew->todel = FALSE;
	xnew->next = NULL;
	xnew->classes = NULL;
	return xnew;
}
void vini_free_dangling(Initable *t, char *file, int line)
{
	Initable *d;
	Initable *next;

	vini_trace_add("freedangle",t,t->nref,file,line);
	if (t->nref>0) return;
	if (t->id!=1234) {
		emsg("vini_free_dangling failing %d %d\n",t->id,t->nref);
		lib_abort();
	}
	if (t->nref<0) {
		emsg("vini_free_dangling failed nref=%d\n",t->nref);
		lib_abort();
	}
	//imsg("vini_free_dangling %p nref %d %s:%d\n",t,t->nref,file,line);
	for (d=t->classes;d!=NULL;d=next) {
		next = d->next;
		vini_free_inival(d->table);
		free(d->table);
		free(d);
	}

	vini_free_inival(t->table);
	free(t->table);
	t->id=2222;
	free(t);
}
/*#define INITESTING true*/

#ifdef INITESTING

enum {I_SPOOL,I_DEBUG,I_MAX_THREADS};
struct ISYNTAX {int code; char *cmd; char *dflt; int type; char *description;} Isyntax;


/* Valid types,  string,mb,path,bool,int */
static struct ISYNTAX isyntax[]={
	I_SPOOL,"spool path=path space=mb","",VINI_MULTI | VINI_REQ,"Path to news storage area(s)"
	,I_DEBUG,"debug bool","",VINI_NORM,"Enable debugging"
	,I_MAX_THREADS,"max_threads int","",VINI_NORM,"Max threads we can use"
	,0,NULL,NULL,0,NULL
};
int main()
{
	int i;
	int r;
	Inihandle *h=NULL;
	vini_init("vnews.ini",printf);
	for (i=0; isyntax[i].cmd!=NULL;i++) {
		vini_add(isyntax[i].code,isyntax[i].cmd,isyntax[i].dflt,isyntax[i].type,isyntax[i].description);
	}
	vini_reload();

	vini_gethandle(&h);
	for (r = vini_find(h,I_SPOOL);r;r=vini_next(h,I_SPOOL) ) {
		printf("vini_cur(I_SPOOL,0) = (%.500s)\n",vini_cur_p(h,I_SPOOL,"path"));
		printf("vini_cur(I_SPOOL,1) = (%.500s)\n",vini_cur_p(h,I_SPOOL,"space"));
	}
	printf("vini_true I_DEBUG %d\n",vini_true(h,I_DEBUG));
	vini_freehandle(&h);
	return 0;
}
#endif


int vini_testdomains(char *s)
{
/*	int i;
	Initable *r;
	for (i=0,r=vdomains; r!=NULL; r=r->next,i++) {
		imsg("vini_loaded[%d]=%p %s\n",i,r,s);
		printf("vini_loaded[%d]=%p %s\n",i,r,s);
	}
	*/
	return 0;
}
void vini_switch_table(Inihandle *ini, Initable *t)
{
	if (ini->tbl == t) return ;
	vini_free_ref(ini->tbl,ini->n,ini);
	ini->tbl = t;
	vini_get_ref(ini->tbl,&ini->n,ini);
}
int vini_switch_table_n(Inihandle *ini, int domainid)
{
	Initable *t;
	int n;

	for (n=0,t=vdomains;t!=NULL;t=t->next,n++) {
		if (n == domainid) {
			vini_switch_table(ini,t);
			return TRUE;
		}
	}

	return FALSE;
}

#include "realfile.h"
//#undef fopen
//#undef fclose
void vini_abort(char *reason)
{
	char *s = NULL;
	FILE *f;
	static int done;
	printf("vini_abort (%s)\n",reason);
	f = fopen("startstop.log","a");
	if (f!=NULL) {
		fprintf(f,"vini_abort(%s) %s\n",reason,mutex_dead ? "mutex_dead" : "mutexes_ok_still");
		if (!done) {
			done = TRUE;
			fprintf(f,"vini_mutex held at %s:%d\n",held_file,held_at);
		}
		if (lock_fname!=NULL) {
			fprintf(f,"Last held at %s:%d %s %d sec\n",lock_fname,lock_line,mstate,(int) (time(NULL)-mstate_time));
		}
		fclose(f);
	}
	*s = 0;
	lib_abort();
}

// DO NOT ADD CODE UNDER HERE


