/*
 *  trace.c
 *
 *  $Id$
 *
 *  Trace functions
 *
 *  The iODBC driver manager.
 *
 *  Copyright (C) 1996-2021 OpenLink Software <iodbc@openlinksw.com>
 *  All Rights Reserved.
 *
 *  This software is released under the terms of either of the following
 *  licenses:
 *
 *      - GNU Library General Public License (see LICENSE.LGPL)
 *      - The BSD License (see LICENSE.BSD).
 *
 *  Note that the only valid version of the LGPL license as far as this
 *  project is concerned is the original GNU Library General Public License
 *  Version 2, dated June 1991.
 *
 *  While not mandated by the BSD license, any patches you make to the
 *  iODBC source code may be contributed back into the iODBC project
 *  at your discretion. Contributions will benefit the Open Source and
 *  Data Access community as a whole. Submissions may be made at:
 *
 *      http://www.iodbc.org
 *
 *
 *  GNU Library Generic Public License Version 2
 *  ============================================
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; only
 *  Version 2 of the License dated June 1991.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *
 *  The BSD License
 *  ===============
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. Neither the name of OpenLink Software Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENLINK OR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <iodbc.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <pwd.h>
#include <unistd.h>
#include <fcntl.h>

#include <time.h>
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#endif

#include <sql.h>
#include <sqlext.h>
#include <sqlucode.h>

#include "unicode.h"
#include "herr.h"
#include "henv.h"
#include "ithread.h"
#include "trace.h"

#define NO_CARBON 1
#if defined(macintosh)
# include       <Errors.h>
# include       <OSUtils.h>
# include       <Processes.h>
#elif defined(__APPLE__) && !defined(NO_CARBON)
# include       <Carbon/Carbon.h>
#endif


/*
 *  Limit the size of the tracefile, to avoid a core dump when the 
 *  the RLIMIT_FSIZE is reached.
 */
#define	MAX_TRACEFILE_LEN	1000000000L	/* about 1GB */


/*
 *  Global trace flag
 */
int ODBCSharedTraceFlag = SQL_OPT_TRACE_OFF;

static char *trace_appname = NULL;
static char *trace_fname = NULL;
static char *trace_fname_template = NULL;
static FILE *trace_fp = NULL;
static int   trace_fp_close = 0;

void trace_emit (char *fmt, ...);

#ifdef HAVE_GETTIMEOFDAY
static struct timeval starttime = {0};
#endif


/*
 * Internal functions
 */
void
trace_set_appname (char *appname)
{
  MEM_FREE (trace_appname);
  trace_appname = STRDUP (appname);
}


char *
trace_get_filename (void)
{
  return STRDUP (trace_fname);
}


static void
trace_strftime_now (char *buf, size_t buflen, char *format)
{
  time_t now;
  struct tm *timeNow;
#ifdef HAVE_LOCALTIME_R
  struct tm keeptime;
#endif

  tzset ();
  time (&now);

#ifdef HAVE_LOCALTIME_R
  timeNow = localtime_r (&now, &keeptime);
#else
  timeNow = localtime (&now);
#endif

  strftime (buf, buflen, format, timeNow);
}




void
trace_set_filename (char *template)
{
  char *s, *p;
  struct passwd *pwd;
  char *buf;
  size_t buf_len, buf_pos;
  char tmp[255];

  /* Make copy of template */
  if (template)
    {
      MEM_FREE (trace_fname_template)
      trace_fname_template = STRDUP (template);
    }

  /*  Initialize */
  MEM_FREE (trace_fname);
  trace_fname = NULL;
  buf = (char *) malloc (buf_len = strlen (trace_fname_template) + sizeof (tmp) + 1);
  if (!buf)
    return;			/* No more memory */
  buf_pos = 0;
  buf[0] = '\0';

  for (s = trace_fname_template; *s;)
    {
      /*
       *  Make sure we can fit at least 1 more tmp buffer inside
       */
      if (buf_len - buf_pos < sizeof (tmp))
	buf = realloc (buf, buf_len += sizeof (tmp) + 1);
      if (!buf)
	return;			/* No more memory */

      if (*s != '$')
	{
	  buf[buf_pos++] = *s++;
	}
      else
	{
	  /* Handle Escape sequences */
	  switch (*(s + 1))
	    {
	    case '$':
	      {
		buf[buf_pos++] = '$';
		break;
	      }

	    case 'p':
	    case 'P':
	      {
#if defined (HAVE_SNPRINTF)
		snprintf (tmp, sizeof (tmp), "%ld", (long) getpid ());
#else
		sprintf (tmp, "%ld", (long) getpid ());
#endif
		strcpy (&buf[buf_pos], tmp);
		buf_pos += strlen (tmp);
		break;
	      }

	    case 'u':
	    case 'U':
	      {
		if ((pwd = getpwuid (getuid ())) != NULL)
		  {
#if defined (HAVE_SNPRINTF)
		    snprintf (tmp, sizeof (tmp), "%s", pwd->pw_name);
#else
		    sprintf (tmp, "%s", pwd->pw_name);
#endif
		    strcpy (&buf[buf_pos], tmp);
		    buf_pos += strlen (tmp);
		  }
		break;
	      }

	    case 'h':
	    case 'H':
	      {
		p = NULL;
		if ((p = getenv ("HOME")) == NULL)
		  {
		    if ((pwd = getpwuid (getuid ())) != NULL)
		      p = pwd->pw_dir;
		  }

		if (p)
		  {
#if defined (HAVE_SNPRINTF)
		    snprintf (tmp, sizeof (tmp), "%s", p);
#else
		    sprintf (tmp, "%s", p);
#endif
		    strcpy (&buf[buf_pos], tmp);
		    buf_pos += strlen (tmp);
		  }
		break;
	      }

	    case 't':
	    case 'T':
	      {
		trace_strftime_now (tmp, sizeof (tmp), "%Y%m%d-%H%M%S");
		strcpy (&buf[buf_pos], tmp);
		buf_pos += strlen (tmp);
		break;
	      }

	    case 's':
	    case 'S':
	      {
		static unsigned int counter = 0;
#if defined (HAVE_SNPRINTF)
		snprintf (tmp, sizeof (tmp), "%d", ++counter);
#else
		sprintf (tmp, "%d", ++counter);
#endif
		strcpy (&buf[buf_pos], tmp);
		buf_pos += strlen (tmp);
	      }

	    default:
	      /* Skip unknown escapes */
	      break;
	    }
	  s += 2;
	}
    }

  buf[buf_pos] = '\0';
  trace_fname = buf;

  return;
}


void
trace_start(void)
{
  /*
   *  First stop any previous trace
   */
  trace_stop ();

#ifdef HAVE_GETTIMEOFDAY
  gettimeofday (&starttime, NULL);
#endif

  /*
   *  If no trace filename is specified, use the default
   */
  if (!trace_fname)
    trace_fname = STRDUP (SQL_OPT_TRACE_FILE_DEFAULT);

#if defined (stderr)
  else if (STRCASEEQ (trace_fname, "stderr"))
    {
      trace_fp = stderr;
    }
#endif

  else
    {
      int fd;
      int fd_flags = O_WRONLY | O_CREAT | O_TRUNC;
      int fd_mode = 0644;


#if defined (unix)
      /*
       *  As this is a security risk, we do not allow root to overwrite a file
       */
      if (geteuid () == 0)
	{
	  fd_flags |= O_EXCL;
	}
#endif

      fd = open (trace_fname, fd_flags, fd_mode);
      if (fd < 0 || (trace_fp = fdopen (fd, "w")) == NULL)
	{
	  return;		/* no tracing */
	}

      trace_fp_close = 1;

      /*
       *  Set trace stream to line buffered
       */
      setvbuf (trace_fp, NULL, _IOLBF, 0);
    }

  /*
   *  Initialize the debug stream
   */
  if (trace_fp == NULL)
    {
      return;
    }
  else
    {
      char mesgBuf[200];

      trace_emit ("** iODBC Trace file\n");

      /*
       *  Show start time
       */
      trace_strftime_now (mesgBuf, sizeof (mesgBuf),
	  "** Trace started on %a %b %d %H:%M:%S %Y");
      trace_emit ("%s\n", mesgBuf);

      /*
       *  Show Driver Manager version similar to SQLGetInfo (SQL_DM_VER)
       */
      sprintf ((char *) mesgBuf, "%02d.%02d.%04d.%04d",
	  SQL_SPEC_MAJOR,
	  SQL_SPEC_MINOR, IODBC_BUILD / 10000, IODBC_BUILD % 10000);
      trace_emit ("** Driver Manager: %s\n\n", mesgBuf);
    }

#if defined (linux)
  {
    extern char *__progname;
    trace_set_appname (__progname);
  }
#elif defined(macintosh) || (defined(__APPLE__) && !defined(NO_CARBON))
  {
    ProcessSerialNumber PSN;
    ProcessInfoRec prec;
    unsigned char processName[40];

    GetCurrentProcess (&PSN);

    prec.processInfoLength = sizeof (ProcessInfoRec);
    prec.processName = processName;
    prec.processAppSpec = NULL;

    if (GetProcessInformation (&PSN, &prec) == noErr)
      {
	processName[processName[0] + 1] = '\0';
	trace_set_appname (processName + 1);
      }
    else
      trace_set_appname ("{No Application Name}");
  }
#elif defined(__APPLE__)
  {
    trace_set_appname ((char *) getprogname ());
  }
#endif

  /*
   *  Turn on tracing flag
   */
  ODBCSharedTraceFlag = SQL_OPT_TRACE_ON;

  return;
}


void
trace_stop(void)
{
  char mesgBuf[200];

  if (trace_fp)
    {
      /*
       * Show end time
       */
      trace_strftime_now (mesgBuf, sizeof (mesgBuf),
	  "** Trace finished on %a %b %d %H:%M:%S %Y");
      trace_emit ("\n%s\n", mesgBuf);

      if (trace_fp_close)
	fclose (trace_fp);
    }

  ODBCSharedTraceFlag = SQL_OPT_TRACE_OFF;
  trace_fp = NULL;
  trace_fp_close = 0;
}


void
trace_emitc (char c)
{
  /*
   * Make sure tracing is enabled
   */
  if (!trace_fp)
    {
      ODBCSharedTraceFlag = SQL_OPT_TRACE_OFF;
      return;
    }

  fputc (c, trace_fp);
}


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

  /*
   * Make sure tracing is enabled
   */
  if (!trace_fp)
    {
      ODBCSharedTraceFlag = SQL_OPT_TRACE_OFF;
      return;
    }

  va_start (ap, fmt);
  vfprintf (trace_fp, fmt, ap);
  va_end (ap);
}


void
trace_emit_string (SQLCHAR *str, ssize_t len, int is_utf8)
{
  ssize_t length = len;
  int i, j;
  int col;
  SQLCHAR *ptr;
  int bytes;
  int truncated = 0;

  if (!str)
    return;

  if (len == SQL_NTS)
    length = STRLEN ((char *) str);
  if (len <= 0)
    return;

  /*
   *  Guard against very long strings
   */
  if (length > MAX_EMIT_STRING)
    {
      length = MAX_EMIT_STRING;
      truncated = 1;
    }

  /*
   *  Dump the (optional UTF-8) string in chunks of 40 characters
   */
  ptr = str;
  col = 0;
  for (i = 0; i < length; i += bytes)
    {
      register int c = *ptr;

      /*
       *   Assume this is a nul-terminated string
       */
      if (!c)
        break;

      /*
       *  Print prefix
       */
      if (col == 0)
	trace_emit ("\t\t\t\t  | ");

      /*
       *  Take care of UTF-8 encoding
       */
      if (!is_utf8)
	bytes = 1;
      else if (c < 128)
	bytes = 1;
      else if ((c & 0xE0) == 0xC0)
	bytes = 2;
      else if ((c & 0xF0) == 0xE0)
	bytes = 3;
      else if ((c & 0xF8) == 0xF0)
	bytes = 4;
      else if ((c & 0xFC) == 0xF8)
        bytes = 5;
      else if ((c & 0xFE) == 0xFC)
        bytes = 6;
      else
	bytes = -1;	/* Wrong UTF8 character */

      if (bytes > 0)
        {
	  /*
	   *  Emit the number of bytes calculated
	   */
	  for (j = 0; j < bytes; j++)
	    trace_emitc (*ptr++);
	}
      else
        {
	  /*
	   *  Skip this bogus UTF8 character sequence and emit a single #
	   */
	  for (bytes = 1, ptr++; (*ptr & 0xC0) == 0x80; bytes++)
	    ptr++;
	  trace_emitc ('#');
	}

      /*
       *  After 40 characters, start a new line
       */
      if (++col >= 40)
	{
	  trace_emit (" |\n");
	  col = 0;
	}
    }

  /*
   *  Pad the last part of the string with spaces
   */
  if (col > 0)
    {
      for (i = col; i < 40; i++)
	trace_emitc (' ');
      trace_emit (" |\n");
    }

  if (truncated)
    trace_emit ("\t\t\t\t  | %-40.40s |\n", "(truncated)");
}


void
trace_emit_binary (unsigned char *str, ssize_t  len)
{
  ssize_t length = len;
  int i;
  int col;
  unsigned char *ptr;
  int truncated = 0;
  char buf[80];
  char *HEX = "0123456789ABCDEF";

  if (!str || len <= 0)
    return;

  /*
   *  Guard against very long binary buffers
   */
  if (length > MAX_EMIT_BINARY)
    {
      length = MAX_EMIT_BINARY;
      truncated = 1;
    }

  ptr = str;
  col = 0;
  memset (buf, ' ', sizeof (buf));
  buf[40] = '\0';
  for (i = 0; i < length; i++)
    {
      unsigned char c = *ptr++;

      /*
       *  Put data into buffer
       */
      buf[3 * col] = HEX[(c >> 4) & 0xF];
      buf[3 * col + 1] = HEX[c & 0xF];
      if (isprint (c))
	buf[30 + col] = c;
      else
	buf[30 + col] = '.';

      /*
       *  After 10 bytes, start a new line
       */
      if (++col > 9)
	{
	  trace_emit_string ((SQLCHAR *) buf, 40, 0);
	  col = 0;
	  memset (buf, ' ', sizeof (buf));
	}
    }

  /*
   *  Pad the last part of the string with spaces
   */
  if (col > 0)
    trace_emit_string ((SQLCHAR *) buf, 40, 0);

  if (truncated)
    trace_emit ("\t\t\t\t  | %-40.40s |\n", "(truncated)");
}


void
_trace_print_function (int func, int trace_leave, int retcode)
{
  extern char *odbcapi_symtab[];
  char *ptr = "invalid retcode";
#ifdef HAVE_GETTIMEOFDAY
  struct timeval tv;
#endif

  /*
   * Guard against tracefile getting too big
   */
  if (trace_fp && ftell (trace_fp) > MAX_TRACEFILE_LEN)
    {
 	trace_emit ("\n*** TRACEFILE LIMIT REACHED ***\n");
	trace_stop ();
	trace_set_filename (NULL);
	trace_start ();	
 	trace_emit ("\n*** TRACEFILE CONTINUED ***\n\n");
 	return;
    }

  /*
   * Calculate timestamp
   */
#ifdef HAVE_GETTIMEOFDAY
  gettimeofday (&tv, NULL);
  tv.tv_sec -= starttime.tv_sec;
  tv.tv_usec -= starttime.tv_usec;
  if (tv.tv_usec < 0)
    {
      tv.tv_sec--;
      tv.tv_usec += 1000000L;
    }
  trace_emit ("\n[%06ld.%06ld]\n", (long) tv.tv_sec, (long) tv.tv_usec);
#else
  trace_emit ("\n");
#endif

  switch (retcode)
    {
      _S (SQL_SUCCESS);
      _S (SQL_SUCCESS_WITH_INFO);
      _S (SQL_NO_DATA_FOUND);
      _S (SQL_NEED_DATA);
      _S (SQL_INVALID_HANDLE);
      _S (SQL_ERROR);
      _S (SQL_STILL_EXECUTING);
    }

#ifndef THREAD_IDENT
#define THREAD_IDENT 0UL
#endif

  if (trace_leave == TRACE_LEAVE)
    trace_emit ("%-15.15s %08lX EXIT  %s with return code %d (%s)\n",
	trace_appname ? trace_appname : "Application",
	THREAD_IDENT, odbcapi_symtab[func], retcode, ptr);
  else
    trace_emit ("%-15.15s %08lX ENTER %s\n",
	trace_appname ? trace_appname : "Application",
	THREAD_IDENT, odbcapi_symtab[func]);
}


static char *_trace_sym_handletype[] = {
  "SQLHANDLE",
  "SQLHENV",
  "SQLHDBC",
  "SQLHSTMT",
  "SQLDESC",
  "SQLSENV"
};


void
_trace_handletype (SQLSMALLINT type)
{
  char *ptr = "invalid handle type";
  switch (type)
    {
      _S (SQL_HANDLE_ENV);
      _S (SQL_HANDLE_DBC);
      _S (SQL_HANDLE_STMT);
#if ODBCVER >= 0x0300
      _S (SQL_HANDLE_DESC);
      _S (SQL_HANDLE_SENV);
#endif
    }

  trace_emit ("\t\t%-15.15s   %d (%s)\n", "SQLSMALLINT", (int) type, ptr);
}


void
_trace_handle_p (SQLSMALLINT type, SQLHANDLE * handle, int output)
{
  if (!handle)
    trace_emit ("\t\t%-15.15s * 0x0 (%s)\n",
	_trace_sym_handletype[type], "SQL_NULL_HANDLE");
  else if (output)
    trace_emit ("\t\t%-15.15s * %p (%p)\n",
	_trace_sym_handletype[type], handle, *handle);
  else
    trace_emit ("\t\t%-15.15s * %p\n", _trace_sym_handletype[type], handle);
}


void
_trace_handle (SQLSMALLINT type, SQLHANDLE handle)
{
  if (!handle)
    trace_emit ("\t\t%-15.15s   0x0 (%s)\n",
	_trace_sym_handletype[type], "SQL_NULL_HANDLE");
  else
    trace_emit ("\t\t%-15.15s   %p\n", _trace_sym_handletype[type], handle);
}


/*
 *  Trace basic data types
 */
void
_trace_smallint (SQLSMALLINT i)
{
  trace_emit ("\t\t%-15.15s   %ld\n", "SQLSMALLINT", (long) i);
}


void
_trace_usmallint (SQLUSMALLINT i)
{
  trace_emit ("\t\t%-15.15s   %lu\n", "SQLUSMALLINT", (unsigned long) i);
}


void
_trace_integer (SQLINTEGER i)
{
  trace_emit ("\t\t%-15.15s   %ld\n", "SQLINTEGER", (long) i);
}


void
_trace_uinteger (SQLUINTEGER i)
{
  trace_emit ("\t\t%-15.15s   %lu\n", "SQLUINTEGER", (unsigned long) i);
}


void
_trace_pointer (SQLPOINTER p)
{
  if (!p)
    trace_emit ("\t\t%-15.15s   0x0\n", "SQLPOINTER");
  else
    trace_emit ("\t\t%-15.15s   %p\n", "SQLPOINTER", p);
}


void
_trace_smallint_p (SQLSMALLINT *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLSMALLINT");
  else if (output)
    trace_emit ("\t\t%-15.15s * %p (%ld)\n", "SQLSMALLINT", p, (long) *p);
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLSMALLINT", p);
}


void
_trace_usmallint_p (SQLUSMALLINT *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLUSMALLINT");
  else if (output)
    trace_emit ("\t\t%-15.15s * %p (%lu)\n", "SQLUSMALLINT", p, (unsigned long) *p);
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLUSMALLINT", p);
}


void
_trace_integer_p (SQLINTEGER *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLINTEGER");
  else if (output)
    trace_emit ("\t\t%-15.15s * %p (%ld)\n", "SQLINTEGER", p, (long) *p);
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLINTEGER", p);
}


void
_trace_uinteger_p (SQLUINTEGER *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLUINTEGER");
  else if (output)
    trace_emit ("\t\t%-15.15s * %p (%lu)\n", "SQLUINTEGER", p, (unsigned long) *p);
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLUINTEGER", p);
}


void
_trace_stringlen (char *type, SQLINTEGER len)
{
  if (len == SQL_NTS)
    trace_emit ("\t\t%-15.15s   %ld (SQL_NTS)\n", type, (long) len);
  else
    trace_emit ("\t\t%-15.15s   %ld\n", type, (long) len);
}

void
_trace_len (SQLLEN i)
{
#ifdef _WIN64
  trace_emit ("\t\t%-15.15s   %I64d\n", "SQLLEN", (INT64) i);
#else
  trace_emit ("\t\t%-15.15s   %ld\n", "SQLLEN", (long) i);
#endif
}


void
_trace_ulen (SQLULEN i)
{
#ifdef _WIN64
  trace_emit ("\t\t%-15.15s   %I64u\n", "SQLLEN", (UINT64) i);
#else
  trace_emit ("\t\t%-15.15s   %ld\n", "SQLLEN", (unsigned long) i);
#endif
}


void
_trace_len_p (SQLLEN *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLLEN");
  else if (output)
#ifdef _WIN64
    trace_emit ("\t\t%-15.15s * %p (%I64d)\n", "SQLLEN", p, (INT64) *p);
#else
    trace_emit ("\t\t%-15.15s * %p (%ld)\n", "SQLLEN", p, (long) *p);
#endif
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLLEN", p);
}


void
_trace_ulen_p (SQLULEN *p, int output)
{
  if (!p)
    trace_emit ("\t\t%-15.15s * 0x0\n", "SQLULEN");
  else if (output)
#ifdef _WIN64
    trace_emit ("\t\t%-15.15s * %p (%I64u)\n", "SQLULEN", p, (UINT64) *p);
#else
    trace_emit ("\t\t%-15.15s * %p (%lu)\n", "SQLULEN", p, (unsigned long) *p);
#endif
  else
    trace_emit ("\t\t%-15.15s * %p\n", "SQLULEN", p);
}


void
_trace_string (SQLCHAR * str, SQLSMALLINT len, SQLSMALLINT * lenptr, int output)
{
  ssize_t length;

  if (!str)
    {
      trace_emit ("\t\t%-15.15s * 0x0\n", "SQLCHAR");
      return;
    }

  trace_emit ("\t\t%-15.15s * %p\n", "SQLCHAR", str);

  if (!output)
    return;

  /*
   *  Calculate length of string
   */
  if (lenptr)
    length = *lenptr;
  else
    length = len;

  if (length == SQL_NTS)
    length = STRLEN (str);

  if (*str && length)
    trace_emit_string (str, length, 0);
  else
    trace_emit_string ( (SQLCHAR *) "(empty string)", SQL_NTS, 0);

  return;
}


void
_trace_string_w (SQLWCHAR * str, SQLSMALLINT len, SQLSMALLINT * lenptr,
    int output)
{
  ssize_t length;

  if (!str)
    {
      trace_emit ("\t\t%-15.15s * 0x0\n", "SQLWCHAR");
      return;
    }

  trace_emit ("\t\t%-15.15s * %p\n", "SQLWCHAR", str);

  if (!output)
    return;

  /*
   *  Calculate length of string
   */
  if (lenptr)
    length = *lenptr;
  else
    length = len;

  if (length == SQL_NTS)
    length = wcslen (str);

  /*
   *  Translate wide string into UTF-8 format and print it
   */
  if (*str && length)
    {
      SQLCHAR *str_u8 = dm_SQL_W2A (str, length);
      trace_emit_string (str_u8, SQL_NTS, 1);
      free (str_u8);
    }
  else
    trace_emit_string ((SQLCHAR *)"(empty string)", SQL_NTS, 0);

  return;
}


void
_trace_c_type (SQLSMALLINT type)
{
  char *ptr = "unknown C type";

  switch (type)
    {
      _S (SQL_C_BINARY);
      _S (SQL_C_BIT);
      /* _S (SQL_C_BOOKMARK); */
      _S (SQL_C_CHAR);
      _S (SQL_C_DATE);
      _S (SQL_C_DEFAULT);
      _S (SQL_C_DOUBLE);
      _S (SQL_C_FLOAT);
#if (ODBCVER >= 0x0350)
      _S (SQL_C_GUID);
#endif
#if ODBCVER >= 0x0300
      _S (SQL_C_INTERVAL_DAY);
      _S (SQL_C_INTERVAL_DAY_TO_HOUR);
      _S (SQL_C_INTERVAL_DAY_TO_MINUTE);
      _S (SQL_C_INTERVAL_DAY_TO_SECOND);
      _S (SQL_C_INTERVAL_HOUR);
      _S (SQL_C_INTERVAL_HOUR_TO_MINUTE);
      _S (SQL_C_INTERVAL_HOUR_TO_SECOND);
      _S (SQL_C_INTERVAL_MINUTE);
      _S (SQL_C_INTERVAL_MINUTE_TO_SECOND);
      _S (SQL_C_INTERVAL_MONTH);
      _S (SQL_C_INTERVAL_SECOND);
      _S (SQL_C_INTERVAL_YEAR);
      _S (SQL_C_INTERVAL_YEAR_TO_MONTH);
#endif
      _S (SQL_C_LONG);
#if ODBCVER >= 0x0300
      _S (SQL_C_NUMERIC);
      _S (SQL_C_SBIGINT);
#endif
      _S (SQL_C_SHORT);
      _S (SQL_C_SLONG);
      _S (SQL_C_SSHORT);
      _S (SQL_C_STINYINT);
      _S (SQL_C_TIME);
      _S (SQL_C_TIMESTAMP);
      _S (SQL_C_TINYINT);
#if ODBCVER >= 0x0300
      _S (SQL_C_TYPE_DATE);
      _S (SQL_C_TYPE_TIME);
      _S (SQL_C_TYPE_TIMESTAMP);
      _S (SQL_C_UBIGINT);
#endif
      _S (SQL_C_ULONG);
      _S (SQL_C_USHORT);
      _S (SQL_C_UTINYINT);
      /* _S (SQL_C_VARBOOKMARK); */
      _S (SQL_C_WCHAR);

#if ODBCVER >= 0x0300
      _S (SQL_ARD_TYPE);
#endif
    }

  trace_emit ("\t\t%-15.15s   %d (%s)\n", "SQLSMALLINT ", type, ptr);
}


void
_trace_inouttype (SQLSMALLINT type)
{
  char *ptr = "unknown Input/Output type";

  switch (type)
    {
    	_S (SQL_PARAM_INPUT);
	_S (SQL_PARAM_OUTPUT);
	_S (SQL_PARAM_INPUT_OUTPUT);
    }

  trace_emit ("\t\t%-15.15s   %d (%s)\n", "SQLSMALLINT ", type, ptr);
}


void
_trace_sql_type (SQLSMALLINT type)
{
  char *ptr = "unknown SQL type";

  switch (type)
    {
      _S (SQL_UNKNOWN_TYPE);
      _S (SQL_BIGINT);
      _S (SQL_BINARY);
      _S (SQL_BIT);
      _S (SQL_CHAR);
#if (ODBCVER < 0x0300)
      _S (SQL_DATE);
#else
      _S (SQL_DATETIME);
#endif
      _S (SQL_DECIMAL);
      _S (SQL_DOUBLE);
      _S (SQL_FLOAT);
#if (ODBCVER >= 0x0350)
      _S (SQL_GUID);
#endif
      _S (SQL_INTEGER);
      _S (SQL_LONGVARBINARY);
      _S (SQL_LONGVARCHAR);
      _S (SQL_NUMERIC);
      _S (SQL_REAL);
      _S (SQL_SMALLINT);
#if (ODBCVER < 0x0300)
      _S (SQL_TIME);
#else
      _S (SQL_INTERVAL);
#endif
      _S (SQL_TIMESTAMP);
      _S (SQL_TINYINT);
#if ODBCVER >= 0x0300
      _S (SQL_TYPE_DATE);
      _S (SQL_TYPE_TIME);
      _S (SQL_TYPE_TIMESTAMP);
#endif
      _S (SQL_VARBINARY);
      _S (SQL_VARCHAR);
      _S (SQL_WCHAR);
      _S (SQL_WLONGVARCHAR);
      _S (SQL_WVARCHAR);
    }

  trace_emit ("\t\t%-15.15s   %d (%s)\n", "SQLSMALLINT", (int) type, ptr);
}


void
_trace_sql_type_p (SQLSMALLINT *p, int output)
{
  char *ptr = "unknown SQL type";

  if (!p)
    {
      trace_emit ("\t\t%-15.15s * 0x0\n", "SQLSMALLINT");
      return;
    }

  if (!output)
    {
      trace_emit ("\t\t%-15.15s * %p\n", "SQLSMALLINT", p);
      return;
    }

  switch (*p)
    {
      _S (SQL_UNKNOWN_TYPE);
      _S (SQL_BIGINT);
      _S (SQL_BINARY);
      _S (SQL_BIT);
      _S (SQL_CHAR);
#if (ODBCVER < 0x0300)
      _S (SQL_DATE);
#else
      _S (SQL_DATETIME);
#endif
      _S (SQL_DECIMAL);
      _S (SQL_DOUBLE);
      _S (SQL_FLOAT);

#if (ODBCVER >= 0x0350)
      _S (SQL_GUID);
#endif
      _S (SQL_INTEGER);
      _S (SQL_LONGVARBINARY);
      _S (SQL_LONGVARCHAR);
      _S (SQL_NUMERIC);
      _S (SQL_REAL);
      _S (SQL_SMALLINT);
#if (ODBCVER < 0x0300)
      _S (SQL_TIME);
#else
      _S (SQL_INTERVAL);
#endif
      _S (SQL_TIMESTAMP);
      _S (SQL_TINYINT);
#if ODBCVER >= 0x0300
      _S (SQL_TYPE_DATE);
      _S (SQL_TYPE_TIME);
      _S (SQL_TYPE_TIMESTAMP);
#endif
      _S (SQL_VARBINARY);
      _S (SQL_VARCHAR);
      _S (SQL_WCHAR);
      _S (SQL_WLONGVARCHAR);
      _S (SQL_WVARCHAR);
    }

  trace_emit ("\t\t%-15.15s * %p (%s)\n", "SQLSMALLINT", p, ptr);
}


#if ODBCVER >= 0x0300
void
_trace_sql_subtype (SQLSMALLINT *type, SQLSMALLINT *sub, int output)
{
  char *ptr = NULL;

  if (!type || !sub)
    {
      trace_emit ("\t\t%-15.15s * 0x0\n", "SQLSMALLINT");
      return;
    }

  if (!output)
    {
      trace_emit ("\t\t%-15.15s * %p\n", "SQLSMALLINT", sub);
      return;
    }

  if (*type == SQL_DATETIME)
    {
      switch (*sub)
	{
	  _S (SQL_CODE_DATE);
	  _S (SQL_CODE_TIME);
	  _S (SQL_CODE_TIMESTAMP);
	}
    }
  else if (*type == SQL_INTERVAL)
    {
      switch (*sub)
	{
	  _S (SQL_CODE_YEAR);
	  _S (SQL_CODE_MONTH);
	  _S (SQL_CODE_DAY);
	  _S (SQL_CODE_HOUR);
	  _S (SQL_CODE_MINUTE);
	  _S (SQL_CODE_SECOND);
	  _S (SQL_CODE_YEAR_TO_MONTH);
	  _S (SQL_CODE_DAY_TO_HOUR);
	  _S (SQL_CODE_DAY_TO_MINUTE);
	  _S (SQL_CODE_DAY_TO_SECOND);
	  _S (SQL_CODE_HOUR_TO_MINUTE);
	  _S (SQL_CODE_HOUR_TO_SECOND);
	  _S (SQL_CODE_MINUTE_TO_SECOND);
	}
    }

  if (ptr)
    trace_emit ("\t\t%-15.15s * %p (%s)\n", "SQLSMALLINT", sub, ptr);
  else
    trace_emit ("\t\t%-15.15s * %p (%d)\n", "SQLSMALLINT", sub, *sub);
}
#endif


void
_trace_bufferlen (SQLINTEGER length)
{
  char buf[255];
  char *ptr = NULL;

  switch (length)
   {
	_S (SQL_NTS);
#if ODBCVER >= 0x0300
	_S (SQL_IS_POINTER);
	_S (SQL_IS_UINTEGER);
	_S (SQL_IS_INTEGER);
	_S (SQL_IS_USMALLINT);
	_S (SQL_IS_SMALLINT);
#endif
   }

  /*
   *  Translate binary buffer length
   */
  if (length <= SQL_LEN_BINARY_ATTR_OFFSET)
    {
        sprintf (buf, "SQL_LEN_BINARY_ATTR(%ld)", (long) SQL_LEN_BINARY_ATTR(length));
	ptr = buf;
    }

  if (ptr)
    trace_emit ("\t\t%-15.15s * %ld (%s)\n", "SQLINTEGER", (long) length, ptr);
  else
    trace_emit ("\t\t%-15.15s * %ld\n", "SQLINTEGER", (long) length);
}
