/****************************************************************************
 *                                                                          *
 *                         GNAT COMPILER COMPONENTS                         *
 *                                                                          *
 *                               E X P E C T                                *
 *                                                                          *
 *                          C Implementation File                           *
 *                                                                          *
 *                     Copyright (C) 2001-2005, AdaCore                     *
 *                                                                          *
 * GNAT is free software;  you can  redistribute it  and/or modify it under *
 * terms of the  GNU General Public License as published  by the Free Soft- *
 * ware  Foundation;  either version 2,  or (at your option) any later ver- *
 * sion.  GNAT is distributed in the hope that it will be useful, but WITH- *
 * OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License *
 * for  more details.  You should have  received  a copy of the GNU General *
 * Public License  distributed with GNAT;  see file COPYING.  If not, write *
 * to  the  Free Software Foundation,  51  Franklin  Street,  Fifth  Floor, *
 * Boston, MA 02110-1301, USA.                                              *
 *                                                                          *
 * As a  special  exception,  if you  link  this file  with other  files to *
 * produce an executable,  this file does not by itself cause the resulting *
 * executable to be covered by the GNU General Public License. This except- *
 * ion does not  however invalidate  any other reasons  why the  executable *
 * file might be covered by the  GNU Public License.                        *
 *                                                                          *
 * GNAT was originally developed  by the GNAT team at  New York University. *
 * Extensive contributions were provided by Ada Core Technologies Inc.      *
 *                                                                          *
 ****************************************************************************/

#ifdef __alpha_vxworks
#include "vxWorks.h"
#endif

#ifdef IN_RTS
#define POSIX
#include "tconfig.h"
#include "tsystem.h"
#else
#include "config.h"
#include "system.h"
#endif

#include <sys/types.h>

#ifdef __MINGW32__
#if OLD_MINGW
#include <sys/wait.h>
#endif
#elif defined (__vxworks) && defined (__RTP__)
#include <wait.h>
#else
#include <sys/wait.h>
#endif

/* This file provides the low level functionalities needed to implement Expect
   capabilities in GNAT.Expect.
   Implementations for unix and windows systems is provided.
   Dummy stubs are also provided for other systems. */

#ifdef _AIX
/* Work around the fact that gcc/cpp does not define "__unix__" under AiX.  */
#define __unix__
#endif

#ifdef __APPLE__
/* Work around the fact that gcc/cpp does not define "__unix__" on Darwin.  */
#define __unix__
#endif

#ifdef _WIN32

#include <windows.h>
#include <process.h>

void
__gnat_kill (int pid, int sig, int close)
{
  if (sig == 9)
    {
      if ((HANDLE)pid != NULL)
	{
	  TerminateProcess ((HANDLE)pid, 0);
	  if (close)
	    CloseHandle ((HANDLE)pid);
	}
    }
}

int
__gnat_waitpid (int pid)
{
  DWORD exitcode = 1;
  DWORD res;

  if ((HANDLE)pid != NULL)
    {
      res = WaitForSingleObject ((HANDLE)pid, INFINITE);
      GetExitCodeProcess ((HANDLE)pid, &exitcode);
      CloseHandle ((HANDLE)pid);
    }

  return (int) exitcode;
}

int
__gnat_expect_fork (void)
{
  return 0;
}

void
__gnat_expect_portable_execvp (int *pid, char *cmd, char *argv[])
{
  BOOL result;
  STARTUPINFO SI;
  PROCESS_INFORMATION PI;
  SECURITY_ATTRIBUTES SA;
  int csize = 1;
  char *full_command;
  int k;

  /* compute the total command line length.  */
  k = 0;
  while (argv[k])
    {
      csize += strlen (argv[k]) + 1;
      k++;
    }

  full_command = (char *) malloc (csize);
  full_command[0] = '\0';

  /* Startup info. */
  SI.cb          = sizeof (STARTUPINFO);
  SI.lpReserved  = NULL;
  SI.lpReserved2 = NULL;
  SI.lpDesktop   = NULL;
  SI.cbReserved2 = 0;
  SI.lpTitle     = NULL;
  SI.dwFlags     = 0;
  SI.wShowWindow = SW_HIDE;

  /* Security attributes. */
  SA.nLength = sizeof (SECURITY_ATTRIBUTES);
  SA.bInheritHandle = TRUE;
  SA.lpSecurityDescriptor = NULL;

  k = 0;
  while (argv[k])
    {
      strcat (full_command, argv[k]);
      strcat (full_command, " ");
      k++;
    }

  result = CreateProcess
	     (NULL, (char *) full_command, &SA, NULL, TRUE,
              GetPriorityClass (GetCurrentProcess()), NULL, NULL, &SI, &PI);

  free (full_command);

  if (result == TRUE)
    {
      CloseHandle (PI.hThread);
      *pid = (int) PI.hProcess;
    }
  else
    *pid = -1;
}

int
__gnat_pipe (int *fd)
{
  HANDLE read, write;

  CreatePipe (&read, &write, NULL, 0);
  fd[0]=_open_osfhandle ((long)read, 0);
  fd[1]=_open_osfhandle ((long)write, 0);
  return 0;  /* always success */
}

int
__gnat_expect_poll (int *fd, int num_fd, int timeout, int *is_set)
{
#define MAX_DELAY 100

  int i, delay, infinite = 0;
  DWORD avail;
  HANDLE handles[num_fd];

  for (i = 0; i < num_fd; i++)
    is_set[i] = 0;

  for (i = 0; i < num_fd; i++)
    handles[i] = (HANDLE) _get_osfhandle (fd [i]);

  /* Start with small delays, and then increase them, to avoid polling too
     much when waiting a long time */
  delay = 5;

  if (timeout < 0)
    infinite = 1;

  while (1)
    {
      for (i = 0; i < num_fd; i++)
        {
          if (!PeekNamedPipe (handles [i], NULL, 0, NULL, &avail, NULL))
            return -1;

          if (avail > 0)
            {
              is_set[i] = 1;
              return 1;
            }
        }

      if (!infinite && timeout <= 0)
        return 0;

      Sleep (delay);
      timeout -= delay;

      if (delay < MAX_DELAY)
        delay += 10;
    }
}

#elif defined (VMS)
#include <unistd.h>
#include <stdio.h>
#include <unixio.h>
#include <stdlib.h>
#include <string.h>
#include <vms/descrip.h>
#include <stdio.h>
#include <vms/stsdef.h>
#include <vms/iodef.h>

int
__gnat_waitpid (int pid)
{
  int status = 0;

  waitpid (pid, &status, 0);
  status = WEXITSTATUS (status);

  return status;
}

int
__gnat_pipe (int *fd)
{
  return pipe (fd);
}

int
__gnat_expect_fork (void)
{
  return -1;
}

void
__gnat_expect_portable_execvp (int *pid, char *cmd, char *argv[])
{
  *pid = (int) getpid ();
  /* Since cmd is fully qualified, it is incorrect to call execvp */
  execv (cmd, argv);
  _exit (1);
}

int
__gnat_expect_poll (int *fd, int num_fd, int timeout, int *is_set)
{
  int i, num, ready = 0;
  unsigned int status;
  int mbxchans [num_fd];
  struct dsc$descriptor_s mbxname;
  struct io_status_block {
    short int condition;
    short int count;
    int dev;
  } iosb;
  char buf [256];

  for (i = 0; i < num_fd; i++)
    is_set[i] = 0;

  for (i = 0; i < num_fd; i++)
    {

      /* Get name of the mailbox used in the pipe */
      getname (fd [i], buf);

      /* Assign a channel to the mailbox */
      if (strlen (buf) > 0)
	{
	  mbxname.dsc$w_length = strlen (buf);
	  mbxname.dsc$b_dtype = DSC$K_DTYPE_T;
	  mbxname.dsc$b_class = DSC$K_CLASS_S;
	  mbxname.dsc$a_pointer = buf;

	  status = SYS$ASSIGN (&mbxname, &mbxchans[i], 0, 0, 0);

	  if ((status & 1) != 1)
	    {
	      ready = -1;
	      return ready;
	    }
	}
    }

  num = timeout / 100;

  while (1)
    {
      for (i = 0; i < num_fd; i++)
	{
	  if (mbxchans[i] > 0)
	    {

	      /* Peek in the mailbox to see if there's data */
	      status = SYS$QIOW
		(0, mbxchans[i], IO$_SENSEMODE|IO$M_READERCHECK,
		 &iosb, 0, 0, 0, 0, 0, 0, 0, 0);

	      if ((status & 1) != 1)
		{
		  ready = -1;
		  goto deassign;
		}

	      if (iosb.count > 0)
		{
		  is_set[i] = 1;
		  ready = 1;
		  goto deassign;
		}
	    }
	}

      if (timeout > 0 && num == 0)
	{
	  ready = 0;
	  goto deassign;
	}

      usleep (100000);
      num--;
    }

 deassign:

  /* Deassign channels assigned above */
  for (i = 0; i < num_fd; i++)
    {
      if (mbxchans[i] > 0)
	status = SYS$DASSGN (mbxchans[i]);
    }

  return ready;
}

#elif defined (__unix__)

#ifdef __hpux__
#include <sys/ptyio.h>
#endif

#include <sys/time.h>

#ifndef NO_FD_SET
#define SELECT_MASK fd_set
#else /* !NO_FD_SET */
#ifndef _AIX
typedef long fd_mask;
#endif /* _AIX */
#ifdef _IBMR2
#define SELECT_MASK void
#else /* !_IBMR2 */
#define SELECT_MASK int
#endif /* !_IBMR2 */
#endif /* !NO_FD_SET */

void
__gnat_kill (int pid, int sig, int close)
{
  kill (pid, sig);
}

int
__gnat_waitpid (int pid)
{
  int status = 0;

  waitpid (pid, &status, 0);
  status = WEXITSTATUS (status);

  return status;
}

int
__gnat_pipe (int *fd)
{
  return pipe (fd);
}

int
__gnat_expect_fork (void)
{
  return fork ();
}

void
__gnat_expect_portable_execvp (int *pid, char *cmd, char *argv[])
{
  *pid = (int) getpid ();
  /* Since cmd is fully qualified, it is incorrect to call execvp */
  execv (cmd, argv);
  _exit (1);
}

int
__gnat_expect_poll (int *fd, int num_fd, int timeout, int *is_set)
{
  struct timeval tv;
  SELECT_MASK rset;
  SELECT_MASK eset;

  int max_fd = 0;
  int ready;
  int i;
  int received;

  tv.tv_sec  = timeout / 1000;
  tv.tv_usec = (timeout % 1000) * 1000;

  do {
    FD_ZERO (&rset);
    FD_ZERO (&eset);

    for (i = 0; i < num_fd; i++)
      {
        FD_SET (fd[i], &rset);
        FD_SET (fd[i], &eset);

        if (fd[i] > max_fd)
	  max_fd = fd[i];
      }

    ready =
      select (max_fd + 1, &rset, NULL, &eset, timeout == -1 ? NULL : &tv);

    if (ready > 0)
      {
	received = 0;

        for (i = 0; i < num_fd; i++)
	  {
	    if (FD_ISSET (fd[i], &rset))
	      {
		is_set[i] = 1;
		received = 1;
	      }
	    else
	      is_set[i] = 0;
	  }

#ifdef __hpux__
        for (i = 0; i < num_fd; i++)
	  {
	    if (FD_ISSET (fd[i], &eset))
	      {
	        struct request_info ei;

	        /* Only query and reset error state if no file descriptor
	           is ready to be read, otherwise we will be signalling a
	           died process too early */

	        if (!received)
		  {
	            ioctl (fd[i], TIOCREQCHECK, &ei);

	            if (ei.request == TIOCCLOSE)
		      {
		        ioctl (fd[i], TIOCREQSET, &ei);
		        return -1;
		      }

	            ioctl (fd[i], TIOCREQSET, &ei);
		  }
	        ready--;
	      }
	  }
#endif
      }
  } while (timeout == -1 && ready == 0);

  return ready;
}

#else

void
__gnat_kill (int pid, int sig, int close)
{
}

int
__gnat_waitpid (int pid, int sig)
{
  return 0;
}

int
__gnat_pipe (int *fd)
{
  return -1;
}

int
__gnat_expect_fork (void)
{
  return -1;
}

void
__gnat_expect_portable_execvp (int *pid, char *cmd, char *argv[])
{
  *pid = 0;
}

int
__gnat_expect_poll (int *fd, int num_fd, int timeout, int *is_set)
{
  return -1;
}
#endif
