#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#ifdef WIN32
#include <io.h>
#endif
#include "lib.h"
#include "lib_spawn.h"
#include <time.h>
#include "lprintf.h"
#include "trace.h"
#ifdef DO_THREADS
#include "thread.h"
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#include "vsnprintf.h"

#define MAXFPRINTF 66000
#define FNAMESZ 80
int dfopen_die(char *reason, char *src, int line, int n);
typedef struct FTABLE {
	char *lastsrc; int lastline; char *srcfile; int srcline; char *closedfile; int closedline; int fd;
	FILE *h; int isopen; time_t opened_time; char fname[FNAMESZ];
	char info[100];
	char clue[100];
	int handle;
#ifdef DO_THREADS
	o_thread thid;
#else
	int thid;
#endif
	int logonly;
} Ftable;
#define MAXFTABLE 8000
#ifdef DO_THREADS
static Mutex *m;
#endif
static Ftable ftable[MAXFTABLE];
static int nopen = 0;
static int logging=FALSE;
static int (*emsg)( const char *format ,...);
static int (*vmutex_diskio_check)(char *file, int line);
int dfopen_history(int handle, char *fname, char *etext);

typedef struct TIMEIT {char *src; const char *fnname; int msec; int count; char *thing; int total; } Timeit;
#define MAXTIMEIT 50000
static Timeit vtimeit[MAXTIMEIT];
static Ftable fhistory[MAXFTABLE];
static int histat;
int (*die_fn)(char *reason);
static int die_limit;
static FILE *extra,*extralog,*savehandle[10];

#define abort_safe lib_abort
static FILE *fsaved;


void dfopen_die_limit(void)
{
	int i;
	die_limit = TRUE;
	if (extra==NULL) extra = fopen("dfopen.xtra","a");
	if (extralog==NULL) {
		extralog = fopen("dfopen_open.log","a");
		for (i=0; i<10; i++) {
			if (savehandle[i]==NULL) savehandle[i] = fopen("dfopen.xtra","r");
		}
	}
}
void dfopen_set_die(int (*my_die_fn)(char *reason))
{
	die_fn = my_die_fn;
}
//void dfopen_die_on
int dfopen_emsg( const char *arg_list ,...)
{
	va_list arg_ptr;
    char *format;
    int len;
	char output[2000];
	FILE *f;
    va_start(arg_ptr, arg_list);
    format = (char *) arg_list;

    len = vsnprintf(output,BFSZ-1, format, arg_ptr);

	f = fopen("mail.err","a");
	if (f!=NULL) {
		fprintf(f,"DFOPEN: %s",output);
		fclose(f);
	}
	return TRUE;
}
void dfopen_init_check(int (*vcheck)( char *file, int line))
{
	vmutex_diskio_check = vcheck;
}
void dfopen_init(int (*vemsg)( const char *format ,...))
{
//	emsg = vemsg;
	emsg = dfopen_emsg;
#ifdef DO_THREADS
	if (m==NULL) m = th_mutex_new("dfopen");
#endif

}

#ifdef MUTEX_TIME
#undef MUTEX_TIME
#endif
// Reduced from 10 minutes to 5 minutes, so we can see if it's causing other problems.   (it should die first)
#ifndef MUTEX_TIME
#define MUTEX_TIME 300000
#endif


static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds, char *file, int line);
int dfopen_set_mutex(int (*cb)(int vm, int isget, int seconds, char *file, int line), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}
//	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600);
//	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600); return; }

static int held_at;

int dfopen_mutex_wait(int line)
{
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600,__FILE__,line);
#ifdef DO_THREADS
		if (m==NULL) m = th_mutex_new("dfopen");
		if (!th_mutex_wait(m,MUTEX_TIME)) { /* 10 minutes */
//			printf("Cannot get mutex, fatal problem, dieing\n",m);
			emsg("Cannot get mutex, fatal problem, dieing, held at line %d, wanted line: %d\n",held_at, line);
			abort_safe();
		}
#endif
		held_at = line;
	return TRUE;
}
void dfopen_mutex_release(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600,__FILE__,__LINE__); return; }
#ifdef DO_THREADS
	if (!th_mutex_release(m)) {
		emsg("Cannot free mutex, fatal problem\n");
//		printf("Cannot free mutex, fatal problem\n");
		abort_safe();
	}
#endif
}
int dfopen_time_end(const char *srcfile, int line, const char *fnname, int msec, char *thing, int size)
{
	int diff = lib_msec_now()-msec;
	if (diff<0) diff = 0;
	if (line<0) return FALSE;
	if (line>=MAXTIMEIT) return FALSE;
	vtimeit[line].src = ( char *) srcfile;
	vtimeit[line].fnname = ( char *) fnname;
	vtimeit[line].thing = ( char *) thing;
	vtimeit[line].msec += diff;
	vtimeit[line].count++;
	if (size>0) vtimeit[line].total+=size;
	return TRUE;
}
int dfrename(const char *a, const char *b, char *file, int line)
{
	int r;
	int msec = lib_msec_now();
	r = rename(a,b);
	if (vmutex_diskio_check) vmutex_diskio_check(file,line);
	dfopen_time_end(file,line,"none",msec,"rename",0);
	return r;

}
char *dfopen_fname(FILE *f)
{
	Ftable *ft;
	int n = fileno(f);

	if (n>0 && n<MAXFTABLE) {
		ft = &ftable[n];
		if (ft->fname!=NULL) return ft->fname;
	}
	return "unknown";
}
int dfopen_init_hist(void)
{
	if (fsaved==NULL) {
		fsaved = fopen("fsaved.tmp","a"); // save a file handle so I can free one to write history log file...

	}
	return TRUE;
}
static int slow_opens; // slow opens during crash so we will still have file handles left...
FILE *dfopen( const char *filename, const char *mode , const char *srcfile, int srcline, const char *fnname)
{
	FILE *f;
	char bf[1000];
	int n;
	static int done;
	int msec;
	Ftable *ft;
	char info[BFSZ];

	if (vmutex_diskio_check) vmutex_diskio_check((char *) srcfile,srcline);
	strcpy(info,"");
	if (slow_opens) lib_msleep(100);
	if (emsg==NULL) emsg = printf;
	msec = lib_msec_now();

#ifdef SURGEMAILxx
	{
		char *s;
		int some=FALSE;
		char reason[BFSZ];
		for (s=(char *) filename;*s!=0;s++) {
			if (!sisdigit(*s)) goto isok;
			some = TRUE;
		}
		if (some) {
			emsg("Bad file name %s %s %d",filename,srcfile,srcline);
			lprintf(reason,BFSZ-1,"Bad file name %s %s %d",filename,srcfile,srcline);
			if (die_fn!=NULL) die_fn(reason);
			s = NULL;
			*s = 0;
		}
isok:;
	}
#endif

	f = spawnfopen(filename,mode);
	dfopen_time_end(srcfile,srcline,fnname,msec,"fopen",0);
	if (!done) {
		if (f==NULL) if (die_limit) if (errno==ENFILE || errno==EMFILE) {
			done = TRUE;
			// change enoent to file handle limit thing, then dump file handle table
#ifdef is_freebsd
			dfopen_die("handle limit - note freebsd 5.x has handle leak, upgrade to later freebsd distribution" ,(char *) srcfile,srcline,0);
#else
			dfopen_die("handle limit" ,(char *) srcfile,srcline,0);
#endif
		}
	}
	if (f!=NULL) {
		dfopen_mutex_wait(__LINE__);
		nopen++;
		n = fileno(f);


#ifdef SURGEMAIL
		lprintf(info,BFSZ-1,"%s fd=%d","Open",n);
//		trace_add((char *)srcfile, srcline,info);
#endif


		/* Record history of file opens too */
		ft = &fhistory[histat++];
		if (histat>=MAXFTABLE) histat = 0;

		ft->logonly = FALSE;
		ft->srcfile = (char *) srcfile;
		ft->srcline = srcline;
		strcpy(ft->info,"");
		strcpy(ft->clue,"");
		ft->closedfile = NULL; // As file is 'opened' we remove the 'closed' info I think to make log clearer (chrisp)
		ft->closedline = 0;

		ft->fd = n;
		ft->h = f;
		ft->isopen = TRUE;
#ifdef DO_THREADS
		ft->thid = th_oid();
#endif
		ft->opened_time = time(NULL);
		strncpy(ft->fname,filename,FNAMESZ-1); ft->fname[FNAMESZ-1] = 0;
		strncpy(ft->clue,"",99-1);
		/* end history  */


		if (logging) printf("dfopen [%d] %s  %s:%d\n",n,filename,srcfile,srcline);
		if (n>0 && n<MAXFTABLE) {
			ft = &ftable[n];
			if (ft->isopen) {
				sprintf(bf,"File handle already open (%.100s) %s:%d",filename,ft->srcfile,ft->srcline);
				dfopen_die(bf,(char *) srcfile,srcline,n);
			}
			strcpy(ft->info,"");

			strcpy(ft->clue,"");
			ft->closedfile = NULL; // As file is 'opened' we remove the 'closed' info I think to make log clearer (chrisp)
			ft->closedline = 0;

			ft->srcfile = (char *) srcfile;
			ft->srcline = srcline;
			ft->fd = n;
			ft->h = f;
			ft->isopen = TRUE;
			ft->opened_time = time(NULL);
			strncpy(ft->fname,filename,FNAMESZ-1); ft->fname[FNAMESZ-1] = 0;
		}
		dfopen_mutex_release();
	}
	return f;
}
int dfopen_handle_opened(FILE *f,  char *filename, char *srcfile, int srcline) 
{
	char bf[1000];
	int n;
	Ftable *ft;
	if (f==NULL) return FALSE;

	n = fileno(f);
	if (n>0 && n<MAXFTABLE) {
		ft = &ftable[n];
		if (ft->isopen) {
			sprintf(bf,"File handle already open (%.100s) %s:%d",filename,ft->srcfile,ft->srcline);
			dfopen_die(bf,(char *) srcfile,srcline,n);
		}
		strcpy(ft->info,"");
		ft->srcfile = (char *) srcfile;
		ft->srcline = srcline;
		ft->fd = n;
		ft->h = f;
		ft->isopen = TRUE;
		ft->opened_time = time(NULL);
		strncpy(ft->fname,filename,FNAMESZ-1); ft->fname[FNAMESZ-1] = 0;
	}
	return TRUE;
}
int dfopen_hist_info(IHANDLE handle, int isopen, int logonly,  char *filename, int line, char *xinfo)
{
	Ftable *ft;
	char bf[BFSZ];
	int n = (int) handle;
	char info[BFSZ];
	dfopen_mutex_wait(__LINE__);
	if (n < 0) n = 0;
	ft = &fhistory[histat++];
	if (histat>=MAXFTABLE) histat = 0;
	ft->logonly = FALSE;
	ft->srcfile = (char *) filename;
	ncpy(ft->info,xinfo,99);
	ft->srcline = line;
	ft->fd = handle;
	strcpy(info,"");
#ifdef DO_THREADS
	ft->thid = th_oid();
#endif
	ft->h = NULL;
	ft->logonly = logonly;
	if (!logonly) ft->isopen = isopen;
	if ((n>0) && (n<MAXFTABLE) ) {
		ft = &ftable[n];
		if (!logonly) {
			if ((isopen == ft->isopen)) {
				char xreason[BFSZ];
				lprintf(bf,BFSZ-1,"%s:%d",filename,line);
				lprintf(xreason,BFSZ-1,"raw handle was not in right state %d==%d %s:%d %s:%d",isopen,ft->isopen,nullok(ft->srcfile),ft->srcline,filename,line);
				dfopen_history(n,"dfopen_hist",bf);
				dfopen_die(xreason,filename,line,n);
			}
			ft->h = NULL;
			ft->handle = n;
			ft->isopen = isopen;
			if (isopen) {
				strncpy(ft->fname,"OPENSK",FNAMESZ-1);
				strncpy(ft->clue,"OPENSK",99-1);
				ft->srcfile = filename;
				ft->srcline = line;
				ft->closedfile = NULL;
				ft->closedline = 0;
				ft->lastsrc = NULL;
				ft->lastline = 0;
			} else {
				ft->closedfile = filename;
				ft->closedline = line;
				ft->lastsrc = ft->srcfile;
				ft->lastline = ft->srcline;
			}
			ncpy(ft->info,xinfo,99);
		}
		strncpy(ft->clue,xinfo,99-1);
	}
	dfopen_mutex_release();
	return TRUE;
}
FILE *dftmpfile(void)
{
	FILE *f;
	f = tmpfile();
	if (f!=NULL) {
		dfopen_hist_info(fileno(f),TRUE,FALSE,"tempfile",11,"dftmpfile");
	}
	return f;
}
static time_t disk_fault;
int dfclose( FILE *stream, char *closefile, int closeline, const char *fnname )
{
	int r;
	Ftable *ft;
	int n;
	int msec=lib_msec_now();

	if (vmutex_diskio_check) vmutex_diskio_check(closefile,closeline);
	
	if (stream == NULL) {
		dfopen_die("Stream is null", closefile, closeline, 0); lib_abort();
	}

	dfopen_mutex_wait(__LINE__);
	n = fileno(stream);

	/* Record history of file opens too */
	ft = &fhistory[histat++];
	if (histat>=MAXFTABLE) histat = 0;
	ft->logonly = FALSE;
	ft->srcfile = (char *) closefile;
	ft->srcline = closeline;
	ft->fd = n;
	ft->h = stream;
#ifdef DO_THREADS
	ft->thid = th_oid();
#endif
	ft->isopen = FALSE;
	ft->opened_time = time(NULL);
	strncpy(ft->fname,"CLOSE",FNAMESZ-1); ft->fname[FNAMESZ-1] = 0;
	/* end history  */

	if (n>0 && n<MAXFTABLE) {
		ft = &ftable[n];
		if (!ft->isopen) dfopen_die("Handle is not open",closefile,closeline,n);
		if (ft->h!=NULL) {
			if (ft->h!=stream) dfopen_die("Handle does not match",closefile,closeline,n);
			if (ft->fd!=n) dfopen_die("Handle no does not match",closefile,closeline,n);
		}

		if (logging) printf("dfclose [%d] %s   %s:%d\n",n,ft->fname,closefile,closeline);

		ft->isopen = FALSE;
		ft->closedfile = closefile;
		ft->closedline = closeline;
		ft->lastsrc = ft->srcfile;
		ft->lastline = ft->srcline;

	}
	nopen--;
	dfopen_mutex_release();
#ifdef SURGEMAIL
	r = spawnfclose_x(stream,closefile,closeline);
#else
	r = spawnfclose(stream);
#endif
	dfopen_time_end(closefile,closeline,fnname,msec,"fopen",0);
	if (r!=0) {
		emsg("fclose failed %s:%d %s\n",closefile,closeline,strerror(errno));
		if (errno==ENOSPC) {
			disk_fault = time(NULL); // remember there is a disk fault. 
		}
	}
	return r;
}
int is_disk_full(void)
{
	if (disk_fault==0) return FALSE;
	if ((time(NULL)-disk_fault) < 60*3) return TRUE; // 2 minutes after fault clears it self fixes.
	return FALSE;
}

typedef struct  UNIT_SCALE{
	char *unit;
	int scale;
}unit_scale;

unit_scale unittype2[] = {
	{"s",            1},  /* second time scale */
	{"mins",        60},
	{"hours",     3600},
	{"days",     86400},
	{"weeks",   604800},
	{"years", 31449600},
	{"ms",            1}, /* ms time scale */
	{"s",          1000},
	{"mins",      60000},
	{"hours",   3600000},
	{"days",   86400000},
	{"weeks", 604800000},
	{"",              1},
	{"b",             1},
	{" single unit",  1},
	{" ",             1},
	{"K",          1000},
	{"M",       1000000},
	{"B",    1000000000},
	{NULL, 1}
};

char *frmt_min_length_str2(char *output, int sz, int fit_len, int x, char *unit){
	int n, i;
	n = fit_len+2;
	i = 0;
	while(strcmp(unittype2[i].unit, unit) != 0){i++;}
	while (n>fit_len && unittype2[i].unit != NULL){
		n = lprintf(output, sz, "%d%s", x / unittype2[i].scale, unittype2[i].unit);
		i++;
	}
	if (n>fit_len) return "lots";
	return output;
}
void get_dfopen_list(void *param, void (*print_line)(void *param, char *line)){
	int i, n = 0;
	Ftable *ft;
	char *myfile_name, tmp2[50], line[200];
	time_t now;
	dfopen_mutex_wait(__LINE__);
	now = time(NULL);
	lprintf(line, 199, "  %8s  %8s  %-30s  %s\n"
		, "File", "Duration", "File Name and Path", "Debug File Name (Line)");
	print_line(param, line);
	for (i = 0; i < MAXFTABLE; i++){
		ft = &ftable[i];
		if (ft->isopen) {
			n++;
			myfile_name = ft->srcfile;
			if (strstr(myfile_name, "src")!=NULL) myfile_name = strstr(myfile_name, "src");
			lprintf(line, 199, "  % 8d  %8s  %-30s  %s (%d) %s\n"
				, i,
				frmt_min_length_str2(tmp2, 48, 7, (int) (now - ft->opened_time), "s")
				, ft->fname, myfile_name, ft->srcline,ft->info);
			print_line(param, line);
		}
	}
	lprintf(line, 199, "(%d/%d) Files open\n", n, nopen);
	print_line(param, line);
	dfopen_mutex_release();
}

int dfopen_history(int handle, char *fname, char *etext)
{
	int i;
	int dorename = FALSE;
	Ftable *ft,*ft2;
	char tmp[BFSZ];
	FILE *f=NULL;
	ft2 = NULL;
//	f = fopen("history.log","a");
	if (f==NULL) {
		if (fsaved!=NULL) {
			//printf("Using fsaved handle\n");
			f = fsaved;
			fsaved = NULL;
			dorename = TRUE;
		}
	}
	if (f!=NULL) {
		fprintf(f,"%s handle=%d (%s) (%s)\n",lib_emsg_time(tmp),handle,fname,etext);
		for (i=histat; i<MAXFTABLE; i++) {
			ft = &fhistory[i];
			if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
			if (ft->srcfile==NULL) continue;
			if ((ft->fd==handle) || (handle==0)) fprintf(f,"[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
				(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
				,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->logonly ? "LOGONLY" : "",ft->info);
		}
		for (i=0; i<histat; i++) {
			ft = &fhistory[i];
			if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
			if (ft->srcfile==NULL) continue;
			if ((ft->fd==handle) || (handle==0))  fprintf(f,"[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
				(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
				,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->logonly ? "LOGONLY" : "",ft->info);
		}

		fclose(f);
	}
	if (dorename) {
		remove("history.log");
		rename("fsaved.tmp","history.log");
	}

#ifdef SURGEMAIL
	trace_show("trace.log",888,th_id());
	lib_tail_file("trace.log","history.log",60000,TRUE);
#endif


	return TRUE;
}
#ifndef WIN32
#include <dirent.h>
#undef readdir
int dfopen_log_fd(FILE *f) // this fails because it must be user 'root' to work...
{
	// File system logging...
#ifdef LINUX
    DIR *d;
    int maxpathlength=256;
    struct dirent *dir;
    char path[maxpathlength];
    sprintf(path,"/proc/%i/fd/",getpid() );

    d = opendir(path);
    if (d) {
        while (dir = readdir(d) != NULL) {
            if (dir->d_type == DT_LNK) {
                const int maxlength = 256;
                char hardfile[maxlength];
                int len;
                char tempath[maxlength];
                sprintf(tempath,"%s%s",path,dir->d_name);
                if (len=readlink(tempath,hardfile,maxlength-1)!=-1) {
                    hardfile[len]='\0';
                    fprintf(f,"%s -> %s\n", dir->d_name,hardfile);
                } else
                    fprintf(f,"error when executing readlink() on %s\n",tempath);
            }
        }
        closedir(d);
    } else fprintf(f,"open failed (%s) %s\n",path,strerror(errno));
#endif
    return 0;
}
#endif

int dfopen_die(char *reason, char *src, int line, int n)
{
	int i;
	static int done;
	Ftable *ft;
	FILE *f;
	char tmp8[BFSZ];

	// Don't double crash as it confuses the log files.
	if (done) return TRUE;
	done = TRUE;
	if (extra!=NULL) {
		fclose(extra); extra = NULL;
	}
	f = fopen(lib_startstop_logfile(),"a");
	if (f!=NULL) {
		fprintf(f,"%s Signal dfopen fault (%s) %s:%d  fd=%d - see log file for details\n",lib_emsg_time(tmp8),reason,src,line,n);
		fclose(f);
		f = NULL;
	}

	emsg("Signal dfopen fault (%s) %s:%d  fd=%d\n",reason,src,line,n);
	dfopen_history(n,reason,"dfopen_die");
	slow_opens = TRUE;
	for (i=0; i<10; i++) {
		if (savehandle[i]!=NULL) fclose(savehandle[i]); savehandle[i] = NULL;
	}

	f = fopen("dfopen.log","a");
	if (f==NULL) f = extralog;
	if (f!=NULL) {
		fprintf(f,"Signal dfopen fault (%s) %s:%d  fd=%d\n",reason,src,line,n);
		for (i=0; i<MAXFTABLE; i++) {
			ft = &ftable[i];
			if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
			if (ft->srcfile==NULL) continue;
			fprintf(f,"[%d] dfopen: [%d] %s:%d %s:%d %s %p %s %s %s\n",
				(int) ft->thid,i,ft->srcfile,ft->srcline
				,ft->closedfile,ft->closedline
				,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->info,ft->clue);
			if (ft->lastsrc!=NULL) fprintf(f,"[%d] dfopen: [%d] last open %s:%d\n",(int) ft->thid,i,ft->lastsrc==NULL ? "NULLptr" : ft->lastsrc,ft->lastline);
		}
/*
		for (i=histat; i<MAXFTABLE; i++) {
			ft = &fhistory[i];
			if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
			if (ft->srcfile==NULL) continue;
			fprintf(f,"[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
				(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
				,ft->fname,ft->h,ft->isopen ? "Open" : "Closed",ft->logonly ? "LOGONLY" : "",ft->info);
		}
		for (i=0; i<histat; i++) {
			ft = &fhistory[i];
			if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
			if (ft->srcfile==NULL) continue;
			fprintf(f,"[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
				(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
				,ft->fname,ft->h,ft->isopen ? "Open" : "Closed",ft->logonly ? "LOGONLY" : "",ft->info);
		}
*/
#ifndef WIN32
		// Lets try and log the info from the file system...
		dfopen_log_fd(f);
#endif

		fprintf(f,"%s Signal dfopen fault (%s) %s:%d  fd=%d\n",lib_emsg_time(tmp8),reason,src,line,n);

		fclose(f);
		f = NULL;
	}


	// First lets list the operations on that file.
	for (i=histat; i<MAXFTABLE; i++) {
		ft = &fhistory[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		if (ft->fd!=n) continue;
		emsg("[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
			(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
			,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->logonly ? "LOGONLY" : "",ft->info);
	}
	for (i=0; i<histat; i++) {
		ft = &fhistory[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		if (ft->fd!=n) continue;
		emsg("[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
			(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
			,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->logonly ? "LOGONLY" : "",ft->info);
	}
	emsg("fhist: END OF HANDLE specific log, look above for main clues\n");
#ifdef FULL_HISTORY_LOG
	for (i=histat; i<MAXFTABLE; i++) {
		ft = &fhistory[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		emsg("[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
			(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
			,ft->fname,ft->h,ft->isopen ? "Open" : "Closed",ft->logonly ? "LOGONLY" : "",ft->info);
	}
	for (i=0; i<histat; i++) {
		ft = &fhistory[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		emsg("[%d] fhist: [%d] fd=%d %s:%d %s %p %s %s %s\n",
			(int) ft->thid,i,ft->fd,ft->srcfile,ft->srcline
			,ft->fname,ft->h,ft->isopen ? "Open" : "Closed" ,ft->logonly ? "LOGONLY" : "",ft->info);
	}

	for (i=0; i<MAXFTABLE; i++) {
		ft = &ftable[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		emsg("[%d] dfopen: [%d] %s:%d %s:%d %s %p %s %s %s\n",
			(int) ft->thid,i,ft->srcfile,ft->srcline
			,ft->closedfile,ft->closedline
			,ft->fname,ft->h,ft->isopen ? "Open" : "Closed",ft->info,ft->clue);
//		emsg("[%d] dfopen: [%d] last open %s:%d\n",(int) ft->thid,i,ft->lastsrc==NULL ? "NULLptr" : ft->lastsrc,ft->lastline);
	}
#endif


	emsg("Signal dfopen fault (%s) %s:%d  fd=%d\n",reason,src,line,n);
//	fflush(NULL);
//	printf("dfopen dieing %s:%d\n",src,line);
	if (die_fn!=NULL) die_fn(reason);
	abort_safe();
	return 0;
}
int dfopen_dump_stats(void)
{
	FILE *f;
	int i;
	static time_t last;
	int total=0;
//typedef struct vtimeit {char *src; int msec; int count; } vtimeit;
	f = fopen("dfopen.stats","w");
	if (f==NULL) return FALSE;
	for (i=0; i<MAXTIMEIT; i++) {
		if (vtimeit[i].src!=NULL) fprintf(f,"%10d %10d %10d %.3f %s:%d \t%s %s\n",vtimeit[i].total,vtimeit[i].msec,vtimeit[i].count,(double) vtimeit[i].msec/((intptr_t)vtimeit[i].count+1),vtimeit[i].src,i,vtimeit[i].thing,vtimeit[i].fnname);
		total += vtimeit[i].total;
	
		vtimeit[i].total = 0;
		vtimeit[i].msec = 0;
		vtimeit[i].count = 0; // reset to zero...
	}
	fprintf(f,"TOTAL %dmb %dk/sec\n",total/1000000,(int)   ((total/1000) / (time(NULL)-last)));
	last = time(NULL);
	fclose(f);
	return TRUE;
}
void dfopen_dump(char *dump_file)
{
	int i;
	int nbad = 0;
	Ftable *ft;
	FILE *f;
	f = fopen(dump_file,"w"); // for ftorrent would like to define the path...
	if (f==NULL) return;
	for (i=0; i<MAXFTABLE; i++) {
		char *old;
		ft = &ftable[i];
		old = "";
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
//		if (!ft->isopen) continue;
		if (time(NULL)-ft->opened_time > 60) {
			old = "SUSPECT";
		}
		fprintf(f,"[%d] dfopen [%d] %s:%d %s:%d %s %p %s i=(%s) %s %s\n",
			(int) ft->thid,i,ft->srcfile,ft->srcline
			,ft->closedfile,ft->closedline
			,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->info,ft->clue,old);
		nbad++;
	}
	fclose(f);
}
void dfopen_exit(void)
{
	int i;
	int nbad = 0;
	Ftable *ft;
	for (i=0; i<MAXFTABLE; i++) {
		ft = &ftable[i];
		if (ft->closedfile==NULL) ft->closedfile = "(NULL)";
		if (ft->srcfile==NULL) continue;
		if (!ft->isopen) continue;
		emsg("[%d] dfopen [%d] %s:%d %s:%d %s %p %s %s\n",
			(int) ft->thid,i,ft->srcfile,ft->srcline
			,ft->closedfile,ft->closedline
			,ft->fname,ft->h,ft->isopen ? "IsOpen" : "IsClosed",ft->info);
		nbad++;
	}
	if (nbad>0) emsg("dfopen %d Lost file handles\n",nbad);
	else emsg("dfopen no dangling handles found\n");

	dfopen_dump_stats();
#ifdef DO_THREADS
//	if (m!=NULL) 		{th_mutex_free(m); m = NULL;}
#endif
}
#ifdef TESTING_DFOPEN

#include "/src/sam/dfopen.h"
int main(int argc, char *argv[])
{
	FILE *f;

	if (argc<2) {
		printf("Usage: dfopen n\n");
		exit(0);
	}
	if (atoi(argv[1])==1) {
		f = fopen("test.dat","w");
		fclose(f);
		fclose(f);
	}

	if (atoi(argv[1])==2) {
		f = fopen("test.dat","w");
		close(fileno(f));
		f = fopen("test2.dat","w");
		fclose(f);
	}

	if (atoi(argv[1])==3) {
		f = fopen("test.dat","w");
		fclose(f);
		f = fopen("test2.dat","w");
		f = fopen("test2.dat","w");
	}

	dfopen_exit();
	return 0;
}
#endif

//#include "../../sam/realfile.h"
//#undef fopen
int dfopen_count(char *reason)
{
	FILE *f[4002];
	int i;
	int save;
	for (i=0; i<4000; i++) {
		f[i] = fopen(lib_startstop_logfile(),"r");
		if (f[i]==NULL) {
			sprintf(reason,"%d %s\n",errno,strerror(errno));
			break;
		}
	}
	i--;
	save = i;
	for (; i>=0; --i) {
		if (f[i]!=NULL) fclose(f[i]);
	}
	return save;
}

FILE *real_fopen(const char *fname, const char *mode)
{
	return fopen(fname,mode);
}

int real_fclose(FILE *f)
{
	return fclose(f);
}

static int faster=TRUE; // turn off some low level stats unless we need them for debugging a specific issue...
void dfopen_enable_timing(int x)
{
	faster = !x;
}

size_t df_fread(void *data, size_t size, size_t n , FILE *f, char *source, int line,const  char *fnname)
{
  	size_t r;
	int msec;
	if (!faster) msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = fread(data,size,n,f);
	if (!faster) dfopen_time_end(source,line,fnname,msec,"fread",r*size);
	return r;
}
size_t df_fwrite(const void *data, size_t size, size_t n , FILE *f, char *source, int line, const char *fnname)
{
  	size_t r;
	int msec;
	if (!faster) msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = fwrite(data,size,n,f);
	if (!faster) dfopen_time_end(source,line,fnname,msec,"fwrite",r*size);
	return r;
}

char *df_fgets(char *a, int b, FILE *c, char *source, int line, const char *fnname)
{
  	char *r;
	int msec;
	if (!faster) msec = lib_msec_now();
	r = fgets(a,b,c);
	if (!faster) dfopen_time_end(source,line,fnname,msec,"fgets",strlen(nullok(r)));
	return r;
}
#include "vsnprintf.h"
static int f_line;
static char *f_src;
int df_fprintf_src(char *src, int line)
{
	f_src = src;
	f_line = line;
	return 0;
}
//int emsg_nolock(const char * arg_list, ...);
int df_fprintf(char *source, int line, const char *fnname, FILE *f, char *format, ...)
{
	va_list args;
	char bf[BFSZ];   
	char *pbf = bf;
	int len;
	int wrote;
	va_start(args,format);

	len = vsnprintf(pbf,BFSZ-1,format,args); // MAY RETURN -1 OR >BFSZ DEPENDING ON ISUES
	if (len==0) return len; // Nothing to print.  
	if (len>=BFSZ-1) {
		pbf = calloc(len+2,1);
		len = vsnprintf(pbf,len+1,format,args);
	} else if (len<=0) {
		len = vsnprintf(NULL,0,format,args);
		if (len<0) return len;
		pbf = calloc(len+2,1);
		len = vsnprintf(pbf,len+1,format,args);
	}
	wrote = df_fwrite(pbf,1,len,f,source,line,fnname);
	if (pbf!=bf) free(pbf);
	if (wrote!=len) return -1;
	return len;
}

// int _fstat( int handle, struct _stat *buffer );
// int _stat( const char *path, struct _stat *buffer );
// long _findfirst( char *filespec, struct _finddata_t *fileinfo );
// int _findnext( long handle, struct _finddata_t *fileinfo );


#ifdef WIN32
#ifndef __BORLANDC__
//#define _stat stat
//#define _fstat fstat
#endif
#endif

int fd_fstat(int handle, struct stat *buffer, char *source, int line)
{
  	int r;
	int msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = fstat(handle,buffer);
	dfopen_time_end(source,line,"none",msec,"fstat",0);
	return r;
}
int fd_stat(const char *path, struct stat *buffer, char *source, int line)
{
  	int r;
	int msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = stat(path,buffer);
	dfopen_time_end(source,line,"none",msec,"stat",0);
	return r;
}

#ifdef WIN32
//#ifndef __BORLANDC__
// 9/11/2020 removed ifdef to add function if compiling surgemail installer under borland
intptr_t fd_findfirst( char *filespec, struct _finddata_t *fileinfo, char *source, int line)
{
  	intptr_t r;
	int msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = _findfirst(filespec,fileinfo);
	dfopen_time_end(source,line,"none",msec,"findfirst",0);
	return r;
}
int fd_findnext( intptr_t handle, struct _finddata_t *fileinfo , char *source, int line)
{
  	int r;
	int msec;
	if (!faster) msec = lib_msec_now();
	if (vmutex_diskio_check) vmutex_diskio_check(source,line);
	r = _findnext(handle,fileinfo);
	if (!faster) dfopen_time_end(source,line,"none",msec,"findnext",0);
	return r;
}
//#endif
#endif
