/* strace.cc: system/windows tracing

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include <ctype.h>
#include "cygerrno.h"
#include "pinfo.h"
#include "perprocess.h"
#include "cygwin_version.h"
#include "cygthread.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "child_info.h"
#include "sync.h"

#define PROTECT(x) {x[NT_MAX_PATH - 1] = '\0';}
#define CHECK(x) if (x[NT_MAX_PATH - 1] != '\0') \
		   { small_printf ("array bound exceeded %d\n", __LINE__); \
		     ExitProcess (1); \
		   }

class strace NO_COPY strace;

#ifndef NOSTRACE

void
strace::activate (bool isfork)
{
  if (!_active && being_debugged ())
    {
      char buf[30];
      __small_sprintf (buf, "cYg%8x %lx %d",
		       _STRACE_INTERFACE_ACTIVATE_ADDR, &_active, isfork);
      OutputDebugString (buf);
      if (_active)
	{
	  char pidbuf[80];
	  PWCHAR progname;
	  if (myself)
	    {
	      __small_sprintf (pidbuf, "(pid %d, ppid %d, windows pid %u)",
			       myself->pid, myself->ppid ?: 1,
			       GetCurrentProcessId ());
	      progname = myself->progname;
	    }
	  else
	    {
	      __small_sprintf (pidbuf, "(windows pid %u)",
			       GetCurrentProcessId ());
	      progname = global_progname;
	    }
	  prntf (1, NULL, "**********************************************");
	  prntf (1, NULL, "Program name: %W %s", progname, pidbuf);
	  prntf (1, NULL, "OS version:   Windows %s", wincap.osname ());
	  prntf (1, NULL, "**********************************************");
	}
    }
}

void
strace::dll_info ()
{
  if (active ())
    {
      prntf (1, NULL, "App version:  %d.%d, api: %d.%d",
	     user_data->dll_major, user_data->dll_minor,
	     user_data->api_major, user_data->api_minor);
      prntf (1, NULL, "DLL version:  %d.%d, api: %d.%d",
	     cygwin_version.dll_major, cygwin_version.dll_minor,
	     cygwin_version.api_major, cygwin_version.api_minor);
      prntf (1, NULL, "DLL build:    %s", cygwin_version.dll_build_date);
    }
}

int
strace::microseconds ()
{
  static LONGLONG process_start NO_COPY;
  clk_monotonic_t *clk = (clk_monotonic_t *) get_clock (CLOCK_MONOTONIC);

  if (!process_start)
    process_start = clk->strace_usecs ();
  return (int) (clk->strace_usecs () - process_start);
}

static int
getfunc (char *in_dst, const char *func)
{
  const char *p;
  const char *pe;
  char *dst = in_dst;
  for (p = func; (pe = strchr (p, '(')); p = pe + 1)
    if (isalnum ((int)pe[-1]) || pe[-1] == '_')
      break;
    else if (isspace ((int)pe[-1]))
      {
	pe--;
	break;
      }
  if (!pe)
    pe = strchr (func, '\0');
  for (p = pe; p > func; p--)
    if (p != pe && *p == ' ')
      {
	p++;
	break;
      }
  if (*p == '*')
    p++;
  while (p < pe)
    *dst++ = *p++;

  *dst++ = ':';
  *dst++ = ' ';
  *dst = '\0';

  return dst - in_dst;
}

static char *
mypid (char *buf)
{
  if (myself && myself->pid)
    __small_sprintf (buf, "%d", myself->pid);
  else
    __small_sprintf (buf, "(%d)", GetCurrentProcessId ());
  return buf;
}

/* sprintf analog for use by output routines. */
int
strace::vsprntf (char *buf, const char *func, const char *infmt, va_list ap)
{
  int count;
  char fmt[80];
  static NO_COPY bool nonewline = false;
  DWORD err = GetLastError ();
  const char *tn = mythreadname ();

  int microsec = microseconds ();
  lmicrosec = microsec;

  __small_sprintf (fmt, "%7d [%s] %s ", microsec, tn, "%W %s%s");

  SetLastError (err);

  if (nonewline)
    count = 0;
  else
    {
      PWCHAR pn = NULL;
      WCHAR progname[NAME_MAX];
      if (cygwin_finished_initializing && program_invocation_short_name)
	{
	  char *p = strrchr (program_invocation_short_name, '/');
	  if (p)
	    ++p;
	  else
	    p = program_invocation_short_name;
	  char *pe = strrchr (p, '.');
	  if (!pe || !ascii_strcasematch (pe, ".exe"))
	    pe = strrchr (p, '\0');
	  sys_mbstowcs (pn = progname, NAME_MAX, p, pe - p);
	}
      else
	{
	  PWCHAR p = wcsrchr (global_progname, L'\\');
	  ++p;
	  PWCHAR pe = wcsrchr (p, '.');
	  if (!pe || wcscasecmp (pe, L".exe"))
	    pe = wcsrchr (p, L'\0');
	  pe = wcpncpy (progname, p, pe - p);
	  *pe = L'\0';
	  pn = progname;
	}
      char tmpbuf[20];
      count = __small_sprintf (buf, fmt, pn, mypid (tmpbuf),
			       execing ? "!" : "");
      if (func)
	count += getfunc (buf + count, func);
    }

  count += __small_vsprintf (buf + count, infmt, ap);
  char *p;
  for (p = buf + count; p > buf; p--)
    switch (p[-1])
      {
	case '\n':
	  p[-1] = '\0';
	  break;
	case '\b':
	  *--p = '\0';
	   nonewline = true;
	  goto done;
	default:
	  goto addnl;
      }

addnl:
  *p++ = '\n';
  *p = '\0';
  nonewline = false;

done:
  return p - buf;
}

/* Write to strace file or strace queue. */
void
strace::write (unsigned category, const char *buf, int count)
{
# define PREFIX (3 + 8 + 1 + 8 + 1)
  char outbuf[PREFIX + 1 + count + 1];
# define outstuff (outbuf + 12)
  __small_sprintf (outstuff, "%x %s", category, buf);
  __small_sprintf (outbuf, "cYg%08x", strlen (outstuff) + 1);
  outstuff[-1] = ' ';
  OutputDebugString (outbuf);
#undef outstuff
#undef PREFIX
}

void
strace::write_childpid (pid_t pid)
{
  char buf[30];

  if (!attached () || !being_debugged ())
    return;
  __small_sprintf (buf, "cYg%8x %x", _STRACE_CHILD_PID, pid);
  OutputDebugString (buf);
}

/* Printf function used when tracing system calls.
   Warning: DO NOT SET ERRNO HERE! */
static NO_COPY muto strace_buf_guard;
static NO_COPY char *buf;

void
strace::vprntf (unsigned category, const char *func, const char *fmt, va_list ap)
{
  DWORD err = GetLastError ();
  int len;

  strace_buf_guard.init ("smallprint_buf")->acquire ();
  /* Creating buffer on Windows process heap to drop stack pressure and
     keeping our .bss small. */
  if (!buf)
    buf = (char *) HeapAlloc (GetProcessHeap (), 0, NT_MAX_PATH);
  if (!buf)
    return;
  PROTECT (buf);
  SetLastError (err);

  len = vsprntf (buf, func, fmt, ap);
  CHECK (buf);
  if (category & _STRACE_SYSTEM)
    {
      DWORD done;
      WriteFile (GetStdHandle (STD_ERROR_HANDLE), buf, len, &done, 0);
      FlushFileBuffers (GetStdHandle (STD_ERROR_HANDLE));
      /* Make sure that the message shows up on the screen, too, since this is
	 a serious error. */
      if (GetFileType (GetStdHandle (STD_ERROR_HANDLE)) != FILE_TYPE_CHAR)
	{
	  HANDLE h = CreateFile ("CONOUT$", GENERIC_READ | GENERIC_WRITE,
				 FILE_SHARE_READ | FILE_SHARE_WRITE,
				 &sec_none, OPEN_EXISTING, 0, 0);
	  if (h != INVALID_HANDLE_VALUE)
	    {
	      WriteFile (h, buf, len, &done, 0);
	      CloseHandle (h);
	    }
	}
    }

#ifndef NOSTRACE
  if (active ())
    write (category, buf, len);
#endif
  strace_buf_guard.release ();
  SetLastError (err);
}

void
strace::prntf (unsigned category, const char *func, const char *fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  vprntf (category, func, fmt, ap);
  va_end (ap);
}

extern "C" void
strace_printf (unsigned category, const char *func, const char *fmt, ...)
{
  va_list ap;

  if ((category & _STRACE_SYSTEM) || strace.active ())
    {
      va_start (ap, fmt);
      strace.vprntf (category, func, fmt, ap);
      va_end (ap);
    }
}

static NO_COPY struct tab
{
  int v;
  const char *n;
}
ta[] =
{
  {  WM_NULL, "WM_NULL"  },
  {  WM_CREATE, "WM_CREATE"  },
  {  WM_DESTROY, "WM_DESTROY"  },
  {  WM_MOVE, "WM_MOVE"  },
  {  WM_SIZE, "WM_SIZE"  },
  {  WM_ACTIVATE, "WM_ACTIVATE"  },
  {  WM_SETFOCUS, "WM_SETFOCUS"  },
  {  WM_KILLFOCUS, "WM_KILLFOCUS"  },
  {  WM_ENABLE, "WM_ENABLE"  },
  {  WM_SETREDRAW, "WM_SETREDRAW"  },
  {  WM_SETTEXT, "WM_SETTEXT"  },
  {  WM_GETTEXT, "WM_GETTEXT"  },
  {  WM_GETTEXTLENGTH, "WM_GETTEXTLENGTH"  },
  {  WM_PAINT, "WM_PAINT"  },
  {  WM_CLOSE, "WM_CLOSE"  },
  {  WM_QUERYENDSESSION, "WM_QUERYENDSESSION"  },
  {  WM_QUIT, "WM_QUIT"  },
  {  WM_QUERYOPEN, "WM_QUERYOPEN"  },
  {  WM_ERASEBKGND, "WM_ERASEBKGND"  },
  {  WM_SYSCOLORCHANGE, "WM_SYSCOLORCHANGE"  },
  {  WM_ENDSESSION, "WM_ENDSESSION"  },
  {  WM_SHOWWINDOW, "WM_SHOWWINDOW"  },
  {  WM_WININICHANGE, "WM_WININICHANGE"  },
  {  WM_DEVMODECHANGE, "WM_DEVMODECHANGE"  },
  {  WM_ACTIVATEAPP, "WM_ACTIVATEAPP"  },
  {  WM_FONTCHANGE, "WM_FONTCHANGE"  },
  {  WM_TIMECHANGE, "WM_TIMECHANGE"  },
  {  WM_CANCELMODE, "WM_CANCELMODE"  },
  {  WM_SETCURSOR, "WM_SETCURSOR"  },
  {  WM_MOUSEACTIVATE, "WM_MOUSEACTIVATE"  },
  {  WM_CHILDACTIVATE, "WM_CHILDACTIVATE"  },
  {  WM_QUEUESYNC, "WM_QUEUESYNC"  },
  {  WM_GETMINMAXINFO, "WM_GETMINMAXINFO"  },
  {  WM_PAINTICON, "WM_PAINTICON"  },
  {  WM_ICONERASEBKGND, "WM_ICONERASEBKGND"  },
  {  WM_NEXTDLGCTL, "WM_NEXTDLGCTL"  },
  {  WM_SPOOLERSTATUS, "WM_SPOOLERSTATUS"  },
  {  WM_DRAWITEM, "WM_DRAWITEM"  },
  {  WM_MEASUREITEM, "WM_MEASUREITEM"  },
  {  WM_DELETEITEM, "WM_DELETEITEM"  },
  {  WM_VKEYTOITEM, "WM_VKEYTOITEM"  },
  {  WM_CHARTOITEM, "WM_CHARTOITEM"  },
  {  WM_SETFONT, "WM_SETFONT"  },
  {  WM_GETFONT, "WM_GETFONT"  },
  {  WM_SETHOTKEY, "WM_SETHOTKEY"  },
  {  WM_GETHOTKEY, "WM_GETHOTKEY"  },
  {  WM_QUERYDRAGICON, "WM_QUERYDRAGICON"  },
  {  WM_COMPAREITEM, "WM_COMPAREITEM"  },
  {  WM_COMPACTING, "WM_COMPACTING"  },
  {  WM_WINDOWPOSCHANGING, "WM_WINDOWPOSCHANGING"  },
  {  WM_WINDOWPOSCHANGED, "WM_WINDOWPOSCHANGED"  },
  {  WM_POWER, "WM_POWER"  },
  {  WM_COPYDATA, "WM_COPYDATA"  },
  {  WM_CANCELJOURNAL, "WM_CANCELJOURNAL"  },
  {  WM_NCCREATE, "WM_NCCREATE"  },
  {  WM_NCDESTROY, "WM_NCDESTROY"  },
  {  WM_NCCALCSIZE, "WM_NCCALCSIZE"  },
  {  WM_NCHITTEST, "WM_NCHITTEST"  },
  {  WM_NCPAINT, "WM_NCPAINT"  },
  {  WM_NCACTIVATE, "WM_NCACTIVATE"  },
  {  WM_GETDLGCODE, "WM_GETDLGCODE"  },
  {  WM_NCMOUSEMOVE, "WM_NCMOUSEMOVE"  },
  {  WM_NCLBUTTONDOWN, "WM_NCLBUTTONDOWN"  },
  {  WM_NCLBUTTONUP, "WM_NCLBUTTONUP"  },
  {  WM_NCLBUTTONDBLCLK, "WM_NCLBUTTONDBLCLK"  },
  {  WM_NCRBUTTONDOWN, "WM_NCRBUTTONDOWN"  },
  {  WM_NCRBUTTONUP, "WM_NCRBUTTONUP"  },
  {  WM_NCRBUTTONDBLCLK, "WM_NCRBUTTONDBLCLK"  },
  {  WM_NCMBUTTONDOWN, "WM_NCMBUTTONDOWN"  },
  {  WM_NCMBUTTONUP, "WM_NCMBUTTONUP"  },
  {  WM_NCMBUTTONDBLCLK, "WM_NCMBUTTONDBLCLK"  },
  {  WM_KEYFIRST, "WM_KEYFIRST"  },
  {  WM_KEYDOWN, "WM_KEYDOWN"  },
  {  WM_KEYUP, "WM_KEYUP"  },
  {  WM_CHAR, "WM_CHAR"  },
  {  WM_DEADCHAR, "WM_DEADCHAR"  },
  {  WM_SYSKEYDOWN, "WM_SYSKEYDOWN"  },
  {  WM_SYSKEYUP, "WM_SYSKEYUP"  },
  {  WM_SYSCHAR, "WM_SYSCHAR"  },
  {  WM_SYSDEADCHAR, "WM_SYSDEADCHAR"  },
  {  WM_KEYLAST, "WM_KEYLAST"  },
  {  WM_INITDIALOG, "WM_INITDIALOG"  },
  {  WM_COMMAND, "WM_COMMAND"  },
  {  WM_SYSCOMMAND, "WM_SYSCOMMAND"  },
  {  WM_TIMER, "WM_TIMER"  },
  {  WM_HSCROLL, "WM_HSCROLL"  },
  {  WM_VSCROLL, "WM_VSCROLL"  },
  {  WM_INITMENU, "WM_INITMENU"  },
  {  WM_INITMENUPOPUP, "WM_INITMENUPOPUP"  },
  {  WM_MENUSELECT, "WM_MENUSELECT"  },
  {  WM_MENUCHAR, "WM_MENUCHAR"  },
  {  WM_ENTERIDLE, "WM_ENTERIDLE"  },
  {  WM_CTLCOLORMSGBOX, "WM_CTLCOLORMSGBOX"  },
  {  WM_CTLCOLOREDIT, "WM_CTLCOLOREDIT"  },
  {  WM_CTLCOLORLISTBOX, "WM_CTLCOLORLISTBOX"  },
  {  WM_CTLCOLORBTN, "WM_CTLCOLORBTN"  },
  {  WM_CTLCOLORDLG, "WM_CTLCOLORDLG"  },
  {  WM_CTLCOLORSCROLLBAR, "WM_CTLCOLORSCROLLBAR"  },
  {  WM_CTLCOLORSTATIC, "WM_CTLCOLORSTATIC"  },
  {  WM_MOUSEFIRST, "WM_MOUSEFIRST"  },
  {  WM_MOUSEMOVE, "WM_MOUSEMOVE"  },
  {  WM_LBUTTONDOWN, "WM_LBUTTONDOWN"  },
  {  WM_LBUTTONUP, "WM_LBUTTONUP"  },
  {  WM_LBUTTONDBLCLK, "WM_LBUTTONDBLCLK"  },
  {  WM_RBUTTONDOWN, "WM_RBUTTONDOWN"  },
  {  WM_RBUTTONUP, "WM_RBUTTONUP"  },
  {  WM_RBUTTONDBLCLK, "WM_RBUTTONDBLCLK"  },
  {  WM_MBUTTONDOWN, "WM_MBUTTONDOWN"  },
  {  WM_MBUTTONUP, "WM_MBUTTONUP"  },
  {  WM_MBUTTONDBLCLK, "WM_MBUTTONDBLCLK"  },
  {  WM_MOUSELAST, "WM_MOUSELAST"  },
  {  WM_PARENTNOTIFY, "WM_PARENTNOTIFY"  },
  {  WM_ENTERMENULOOP, "WM_ENTERMENULOOP"  },
  {  WM_EXITMENULOOP, "WM_EXITMENULOOP"  },
  {  WM_MDICREATE, "WM_MDICREATE"  },
  {  WM_MDIDESTROY, "WM_MDIDESTROY"  },
  {  WM_MDIACTIVATE, "WM_MDIACTIVATE"  },
  {  WM_MDIRESTORE, "WM_MDIRESTORE"  },
  {  WM_MDINEXT, "WM_MDINEXT"  },
  {  WM_MDIMAXIMIZE, "WM_MDIMAXIMIZE"  },
  {  WM_MDITILE, "WM_MDITILE"  },
  {  WM_MDICASCADE, "WM_MDICASCADE"  },
  {  WM_MDIICONARRANGE, "WM_MDIICONARRANGE"  },
  {  WM_MDIGETACTIVE, "WM_MDIGETACTIVE"  },
  {  WM_MDISETMENU, "WM_MDISETMENU"  },
  {  WM_DROPFILES, "WM_DROPFILES"  },
  {  WM_MDIREFRESHMENU, "WM_MDIREFRESHMENU"  },
  {  WM_CUT, "WM_CUT"  },
  {  WM_COPY, "WM_COPY"  },
  {  WM_PASTE, "WM_PASTE"  },
  {  WM_CLEAR, "WM_CLEAR"  },
  {  WM_UNDO, "WM_UNDO"  },
  {  WM_RENDERFORMAT, "WM_RENDERFORMAT"  },
  {  WM_RENDERALLFORMATS, "WM_RENDERALLFORMATS"  },
  {  WM_DESTROYCLIPBOARD, "WM_DESTROYCLIPBOARD"  },
  {  WM_DRAWCLIPBOARD, "WM_DRAWCLIPBOARD"  },
  {  WM_PAINTCLIPBOARD, "WM_PAINTCLIPBOARD"  },
  {  WM_VSCROLLCLIPBOARD, "WM_VSCROLLCLIPBOARD"  },
  {  WM_SIZECLIPBOARD, "WM_SIZECLIPBOARD"  },
  {  WM_ASKCBFORMATNAME, "WM_ASKCBFORMATNAME"  },
  {  WM_CHANGECBCHAIN, "WM_CHANGECBCHAIN"  },
  {  WM_HSCROLLCLIPBOARD, "WM_HSCROLLCLIPBOARD"  },
  {  WM_QUERYNEWPALETTE, "WM_QUERYNEWPALETTE"  },
  {  WM_PALETTEISCHANGING, "WM_PALETTEISCHANGING"  },
  {  WM_PALETTECHANGED, "WM_PALETTECHANGED"  },
  {  WM_HOTKEY, "WM_HOTKEY"  },
  {  WM_PENWINFIRST, "WM_PENWINFIRST"  },
  {  WM_PENWINLAST, "WM_PENWINLAST"  },
  {  WM_ASYNCIO, "ASYNCIO"  },
  {  0, 0  }};

void
strace::wm (int message, int word, int lon)
{
  if (active ())
    {
      int i;

      for (i = 0; ta[i].n; i++)
	{
	  if (ta[i].v == message)
	    {
	      prntf (_STRACE_WM, NULL, "wndproc %d %s %d %d", message, ta[i].n, word, lon);
	      return;
	    }
	}
      prntf (_STRACE_WM, NULL, "wndproc %d unknown  %d %d", message, word, lon);
    }
}
#endif /*NOSTRACE*/
