/*-------
 * Module:			mylog.c
 *
 * Description:		This module contains miscellaneous routines
 *					such as for debugging/logging and string functions.
 *
 * Classes:			n/a
 *
 * API functions:	none
 *
 * Comments:		See "readme.txt" for copyright and license information.
 *-------
 */

#define	_MYLOG_FUNCS_IMPLEMENT_
#include "psqlodbc.h"
#include "dlg_specific.h"
#include "misc.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#ifndef WIN32
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#define	GENERAL_ERRNO		(errno)
#define	GENERAL_ERRNO_SET(e)	(errno = e)
#else
#define	GENERAL_ERRNO		(GetLastError())
#define	GENERAL_ERRNO_SET(e)	SetLastError(e)
#include <process.h>			/* Byron: is this where Windows keeps def.
								 * of getpid ? */
#endif

#ifdef WIN32
#define DIRSEPARATOR		"\\"
#define PG_BINARY			O_BINARY
#define PG_BINARY_R			"rb"
#define PG_BINARY_W			"wb"
#define PG_BINARY_A			"ab"
#else
#define DIRSEPARATOR		"/"
#define PG_BINARY			0
#define PG_BINARY_R			"r"
#define PG_BINARY_W			"w"
#define PG_BINARY_A			"a"
#endif /* WIN32 */


static char *logdir = NULL;

void
generate_filename(const char *dirname, const char *prefix, char *filename, size_t filenamelen)
{
	const char *exename = GetExeProgramName();
#ifdef	WIN32
	int	pid;

	pid = _getpid();
#else
	pid_t	pid;
	struct passwd *ptr;

	ptr = getpwuid(getuid());
	if (NULL == ptr)
		return;
	pid = getpid();
#endif
	if (dirname == 0 || filename == 0)
		return;

	snprintf(filename, filenamelen, "%s%s", dirname, DIRSEPARATOR);
	if (prefix != 0)
		strlcat(filename, prefix, filenamelen);
	if (exename[0])
		snprintfcat(filename, filenamelen, "%s_", exename);
#ifndef WIN32
	if (ptr)
		strlcat(filename, ptr->pw_name, filenamelen);
#endif
	snprintfcat(filename, filenamelen, "%u%s", pid, ".log");
	return;
}

static void
generate_homefile(const char *prefix, char *filename, size_t filenamelen)
{
	char	dir[PATH_MAX];
#ifdef	WIN32
	const char *ptr;

	dir[0] = '\0';
	if (ptr=getenv("HOMEDRIVE"), NULL != ptr)
		strlcat(dir, ptr, filenamelen);
	if (ptr=getenv("HOMEPATH"), NULL != ptr)
		strlcat(dir, ptr, filenamelen);
#else
	STRCPY_FIXED(dir, "~");
#endif /* WIN32 */
	generate_filename(dir, prefix, filename, filenamelen);

	return;
}

#ifdef  WIN32
static  char    exename[_MAX_FNAME];
#elif   defined MAXNAMELEN
static  char    exename[MAXNAMELEN];
#else
static  char    exename[256];
#endif

const char *GetExeProgramName()
{
	static  int init = 1;

	if (init)
	{
		UCHAR    *p;
#ifdef  WIN32
		char    pathname[_MAX_PATH];

		if (GetModuleFileName(NULL, pathname, sizeof(pathname)) > 0)
		_splitpath(pathname, NULL, NULL, exename, NULL);
#else
		CSTR flist[] = {"/proc/self/exe", "/proc/curproc/file", "/proc/curproc/exe" };
		int     i;
		char    path_name[256];

		for (i = 0; i < sizeof(flist) / sizeof(flist[0]); i++)
		{
			if (readlink(flist[i], path_name, sizeof(path_name)) > 0)
			{
				STRCPY_FIXED(exename, po_basename(path_name));
				break;
			}
		}
#endif /* WIN32 */
		for (p = (UCHAR *) exename; '\0' != *p; p++)
		{
			if (isalnum(*p))
				continue;
			switch (*p)
			{
				case '_':
				case '-':
					continue;
			}
			*p = '\0';      /* avoid multi bytes for safety */
			break;
		}
		init = 0;
	}
	return exename;
}

#if defined(WIN_MULTITHREAD_SUPPORT)
static	CRITICAL_SECTION	qlog_cs, mylog_cs;
#elif defined(POSIX_MULTITHREAD_SUPPORT)
static	pthread_mutex_t	qlog_cs, mylog_cs;
#endif /* WIN_MULTITHREAD_SUPPORT */
static int	mylog_on = 0, qlog_on = 0;

#if defined(WIN_MULTITHREAD_SUPPORT)
#define	INIT_QLOG_CS	InitializeCriticalSection(&qlog_cs)
#define	ENTER_QLOG_CS	EnterCriticalSection(&qlog_cs)
#define	LEAVE_QLOG_CS	LeaveCriticalSection(&qlog_cs)
#define	DELETE_QLOG_CS	DeleteCriticalSection(&qlog_cs)
#define	INIT_MYLOG_CS	InitializeCriticalSection(&mylog_cs)
#define	ENTER_MYLOG_CS	EnterCriticalSection(&mylog_cs)
#define	LEAVE_MYLOG_CS	LeaveCriticalSection(&mylog_cs)
#define	DELETE_MYLOG_CS	DeleteCriticalSection(&mylog_cs)
#elif defined(POSIX_MULTITHREAD_SUPPORT)
#define	INIT_QLOG_CS	pthread_mutex_init(&qlog_cs,0)
#define	ENTER_QLOG_CS	pthread_mutex_lock(&qlog_cs)
#define	LEAVE_QLOG_CS	pthread_mutex_unlock(&qlog_cs)
#define	DELETE_QLOG_CS	pthread_mutex_destroy(&qlog_cs)
#define	INIT_MYLOG_CS	pthread_mutex_init(&mylog_cs,0)
#define	ENTER_MYLOG_CS	pthread_mutex_lock(&mylog_cs)
#define	LEAVE_MYLOG_CS	pthread_mutex_unlock(&mylog_cs)
#define	DELETE_MYLOG_CS	pthread_mutex_destroy(&mylog_cs)
#else
#define	INIT_QLOG_CS
#define	ENTER_QLOG_CS
#define	LEAVE_QLOG_CS
#define	DELETE_QLOG_CS
#define	INIT_MYLOG_CS
#define	ENTER_MYLOG_CS
#define	LEAVE_MYLOG_CS
#define	DELETE_MYLOG_CS
#endif /* WIN_MULTITHREAD_SUPPORT */

#define MYLOGFILE			"mylog_"
#ifndef WIN32
#define MYLOGDIR			"/tmp"
#else
#define MYLOGDIR			"c:"
#endif /* WIN32 */

#define QLOGFILE			"psqlodbc_"
#ifndef WIN32
#define QLOGDIR				"/tmp"
#else
#define QLOGDIR				"c:"
#endif /* WIN32 */


int	get_mylog(void)
{
	return mylog_on;
}
int	get_qlog(void)
{
	return qlog_on;
}

const char *po_basename(const char *path)
{
	char *p;

	if (p = strrchr(path,  DIRSEPARATOR[0]), NULL != p)
		return p + 1;
	return path;
}

void
logs_on_off(int cnopen, int mylog_onoff, int qlog_onoff)
{
	static int	mylog_on_count = 0,
			mylog_off_count = 0,
			qlog_on_count = 0,
			qlog_off_count = 0;

	ENTER_MYLOG_CS;
	if (mylog_onoff)
		mylog_on_count += cnopen;
	else
		mylog_off_count += cnopen;
	if (mylog_on_count > 0)
	{
		if (mylog_onoff > mylog_on)
			mylog_on = mylog_onoff;
		else if (mylog_on < 1)
			mylog_on = 1;
	}
	else if (mylog_off_count > 0)
		mylog_on = 0;
	else if (getGlobalDebug() > 0)
		mylog_on = getGlobalDebug();
	LEAVE_MYLOG_CS;

	ENTER_QLOG_CS;
	if (qlog_onoff)
		qlog_on_count += cnopen;
	else
		qlog_off_count += cnopen;
	if (qlog_on_count > 0)
	{
		if (qlog_onoff > qlog_on)
			qlog_on = qlog_onoff;
		else if (qlog_on < 1)
			qlog_on = 1;
	}
	else if (qlog_off_count > 0)
		qlog_on = 0;
	else if (getGlobalCommlog() > 0)
		qlog_on = getGlobalCommlog();
	LEAVE_QLOG_CS;
MYLOG(0, "mylog_on=%d qlog_on=%d\n", mylog_on, qlog_on);
}

#ifdef	WIN32
#define	LOGGING_PROCESS_TIME
#include <direct.h>
#endif /* WIN32 */
#ifdef	LOGGING_PROCESS_TIME
#include <mmsystem.h>
	static	DWORD	start_time = 0;
#endif /* LOGGING_PROCESS_TIME */
static FILE *MLOGFP = NULL;

static void MLOG_open()
{
	char		filebuf[80], errbuf[160];
	BOOL		open_error = FALSE;

	if (MLOGFP) return;

	generate_filename(logdir ? logdir : MYLOGDIR, MYLOGFILE, filebuf, sizeof(filebuf));
	MLOGFP = fopen(filebuf, PG_BINARY_A);
	if (!MLOGFP)
	{
		int lasterror = GENERAL_ERRNO;
 
		open_error = TRUE;
		SPRINTF_FIXED(errbuf, "%s open error %d\n", filebuf, lasterror);
		generate_homefile(MYLOGFILE, filebuf, sizeof(filebuf));
		MLOGFP = fopen(filebuf, PG_BINARY_A);
	}
	if (MLOGFP)
	{
		setbuf(MLOGFP, NULL);
		if (open_error)
			fputs(errbuf, MLOGFP);
	}
}

static int
mylog_misc(unsigned int option, const char *fmt, va_list args)
{
	// va_list		args;
	int		gerrno;
	BOOL	log_threadid = option;

	gerrno = GENERAL_ERRNO;
	ENTER_MYLOG_CS;
#ifdef	LOGGING_PROCESS_TIME
	if (!start_time)
		start_time = timeGetTime();
#endif /* LOGGING_PROCESS_TIME */

	if (!MLOGFP)
	{
		MLOG_open();
		if (!MLOGFP)
			mylog_on = 0;
	}

	if (MLOGFP)
	{
		if (log_threadid)
		{
#ifdef	WIN_MULTITHREAD_SUPPORT
#ifdef	LOGGING_PROCESS_TIME
		DWORD	proc_time = timeGetTime() - start_time;
		fprintf(MLOGFP, "[%u-%d.%03d]", GetCurrentThreadId(), proc_time / 1000, proc_time % 1000);
#else
		fprintf(MLOGFP, "[%u]", GetCurrentThreadId());
#endif /* LOGGING_PROCESS_TIME */
#endif /* WIN_MULTITHREAD_SUPPORT */
#if defined(POSIX_MULTITHREAD_SUPPORT)
		fprintf(MLOGFP, "[%lx]", (unsigned long int) pthread_self());
#endif /* POSIX_MULTITHREAD_SUPPORT */
		}
		vfprintf(MLOGFP, fmt, args);
	}

	LEAVE_MYLOG_CS;
	GENERAL_ERRNO_SET(gerrno);

	return 1;
}

DLL_DECLARE int
mylog(const char *fmt,...)
{
	int	ret = 0;
	unsigned int option = 1;
	va_list	args;

	if (!mylog_on)	return ret;

	va_start(args, fmt);
	ret = mylog_misc(option, fmt, args);
	va_end(args);
	return	ret;
}

DLL_DECLARE int
myprintf(const char *fmt,...)
{
	int	ret = 0;
	va_list	args;

	va_start(args, fmt);
	ret = mylog_misc(0, fmt, args);
	va_end(args);
	return	ret;
}

static void mylog_initialize(void)
{
	INIT_MYLOG_CS;
}
static void mylog_finalize(void)
{
	ENTER_MYLOG_CS;
	mylog_on = 0;
	if (MLOGFP)
	{
		fclose(MLOGFP);
		MLOGFP = NULL;
	}
	DELETE_MYLOG_CS;
}


static FILE *QLOGFP = NULL;

static int
qlog_misc(unsigned int option, const char *fmt, va_list args)
{
	char		filebuf[80];
	int		gerrno;

	if (!qlog_on)	return 0;

	gerrno = GENERAL_ERRNO;
	ENTER_QLOG_CS;
#ifdef	LOGGING_PROCESS_TIME
	if (!start_time)
		start_time = timeGetTime();
#endif /* LOGGING_PROCESS_TIME */

	if (!QLOGFP)
	{
		generate_filename(logdir ? logdir : QLOGDIR, QLOGFILE, filebuf, sizeof(filebuf));
		QLOGFP = fopen(filebuf, PG_BINARY_A);
		if (!QLOGFP)
		{
			generate_homefile(QLOGFILE, filebuf, sizeof(filebuf));
			QLOGFP = fopen(filebuf, PG_BINARY_A);
		}
		if (QLOGFP)
			setbuf(QLOGFP, NULL);
		else
			qlog_on = 0;
	}

	if (QLOGFP)
	{
		if (option)
		{
#ifdef	LOGGING_PROCESS_TIME
		DWORD	proc_time = timeGetTime() - start_time;
		fprintf(QLOGFP, "[%d.%03d]", proc_time / 1000, proc_time % 1000);
#endif /* LOGGING_PROCESS_TIME */
		}
		vfprintf(QLOGFP, fmt, args);
	}

	LEAVE_QLOG_CS;
	GENERAL_ERRNO_SET(gerrno);

	return 1;
}
int
qlog(char *fmt,...)
{
	int	ret = 0;
	unsigned int option = 1;
	va_list	args;

	if (!qlog_on)	return ret;

	va_start(args, fmt);
	ret = qlog_misc(option, fmt, args);
	va_end(args);
	return	ret;
}
int
qprintf(char *fmt,...)
{
	int	ret = 0;
	va_list	args;

	va_start(args, fmt);
	ret = qlog_misc(0, fmt, args);
	va_end(args);
	return	ret;
}

static void qlog_initialize(void)
{
	INIT_QLOG_CS;
}
static void qlog_finalize(void)
{
	ENTER_QLOG_CS;
	qlog_on = 0;
	if (QLOGFP)
	{
		fclose(QLOGFP);
		QLOGFP = NULL;
	}
	DELETE_QLOG_CS;
}

static int	globalDebug = -1;
int
getGlobalDebug()
{
	char	temp[16];

	if (globalDebug >=0)
		return globalDebug;
	/* Debug is stored in the driver section */
	SQLGetPrivateProfileString(DBMS_NAME, INI_DEBUG, "", temp, sizeof(temp), ODBCINST_INI);
	if (temp[0])
		globalDebug = atoi(temp);
	else
		globalDebug = DEFAULT_DEBUG;

	return globalDebug;
}

int
setGlobalDebug(int val)
{
	return (globalDebug = val);
}

static int	globalCommlog = -1;
int
getGlobalCommlog()
{
	char	temp[16];

	if (globalCommlog >= 0)
		return globalCommlog;
	/* Commlog is stored in the driver section */
	SQLGetPrivateProfileString(DBMS_NAME, INI_COMMLOG, "", temp, sizeof(temp), ODBCINST_INI);
	if (temp[0])
		globalCommlog = atoi(temp);
	else
		globalCommlog = DEFAULT_COMMLOG;

	return globalCommlog;
}

int
setGlobalCommlog(int val)
{
	return (globalCommlog = val);
}

int
writeGlobalLogs()
{
	char	temp[10];

	ITOA_FIXED(temp, globalDebug);
	SQLWritePrivateProfileString(DBMS_NAME, INI_DEBUG, temp, ODBCINST_INI);
	ITOA_FIXED(temp, globalCommlog);
	SQLWritePrivateProfileString(DBMS_NAME, INI_COMMLOG, temp, ODBCINST_INI);
	return 0;
}

int
getLogDir(char *dir, int dirmax)
{
	return SQLGetPrivateProfileString(DBMS_NAME, INI_LOGDIR, "", dir, dirmax, ODBCINST_INI);
}

int
setLogDir(const char *dir)
{
	return SQLWritePrivateProfileString(DBMS_NAME, INI_LOGDIR, dir, ODBCINST_INI);
}

/*
 *	This function starts a logging out of connections according the ODBCINST.INI
 *	portion of the DBMS_NAME registry.
 */
static void
start_logging()
{
	/*
	 * GlobalDebug or GlobalCommlog means whether take mylog or commlog
	 * out of the connection time or not but doesn't mean the default of
	 * ci->drivers.debug(commlog).
	 */
	logs_on_off(0, 0, 0);
	mylog("\t%s:Global.debug&commlog=%d&%d\n", __FUNCTION__, getGlobalDebug(), getGlobalCommlog());
}

void InitializeLogging(void)
{
	char dir[PATH_MAX];

	getLogDir(dir, sizeof(dir));
	if (dir[0])
		logdir = strdup(dir);
	mylog_initialize();
	qlog_initialize();
	start_logging();
}

void FinalizeLogging(void)
{
	mylog_finalize();
	qlog_finalize();
	if (logdir)
	{
		free(logdir);
		logdir = NULL;
	}
}
