/* Serial interface for a selectable event.
   Copyright (C) 2016-2022 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "ser-event.h"
#include "serial.h"
#include "gdbsupport/filestuff.h"

/* On POSIX hosts, a serial_event is basically an abstraction for the
   classical self-pipe trick.

   On Windows, a serial_event is a wrapper around a native Windows
   event object.  Because we want to interface with gdb_select, which
   takes file descriptors, we need to wrap that Windows event object
   in a file descriptor.  As _open_osfhandle can not be used with
   event objects, we instead create a dummy file wrap that in a file
   descriptor with _open_osfhandle, and pass that as selectable
   descriptor to callers.  As Windows' gdb_select converts file
   descriptors back to Windows handles by calling serial->wait_handle,
   nothing ever actually waits on that file descriptor.  */

struct serial_event_state
  {
#ifdef USE_WIN32API
    /* The Windows event object, created with CreateEvent.  */
    HANDLE event;
#else
    /* The write side of the pipe.  The read side is in
       serial->fd.  */
    int write_fd;
#endif
  };

/* Open a new serial event.  */

static int
serial_event_open (struct serial *scb, const char *name)
{
  struct serial_event_state *state;

  state = XNEW (struct serial_event_state);
  scb->state = state;

#ifndef USE_WIN32API
  {
    int fds[2];

    if (gdb_pipe_cloexec (fds) == -1)
      internal_error (__FILE__, __LINE__,
		      "creating serial event pipe failed.");

    fcntl (fds[0], F_SETFL, O_NONBLOCK);
    fcntl (fds[1], F_SETFL, O_NONBLOCK);

    scb->fd = fds[0];
    state->write_fd = fds[1];
  }
#else
  {
    /* A dummy file object that can be wrapped in a file descriptor.
       We don't need to store this handle because closing the file
       descriptor automatically closes this.  */
    HANDLE dummy_file;

    /* A manual-reset event.  */
    state->event = CreateEvent (0, TRUE, FALSE, 0);

    /* The dummy file handle.  Created just so we have something
       wrappable in a file descriptor.  */
    dummy_file = CreateFile ("nul", 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    scb->fd = _open_osfhandle ((intptr_t) dummy_file, 0);
  }
#endif

  return 0;
}

static void
serial_event_close (struct serial *scb)
{
  struct serial_event_state *state = (struct serial_event_state *) scb->state;

  close (scb->fd);
#ifndef USE_WIN32API
  close (state->write_fd);
#else
  CloseHandle (state->event);
#endif

  scb->fd = -1;

  xfree (state);
  scb->state = NULL;
}

#ifdef USE_WIN32API

/* Implementation of the wait_handle method.  Returns the native
   Windows event object handle.  */

static void
serial_event_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
{
  struct serial_event_state *state = (struct serial_event_state *) scb->state;

  *read = state->event;
}

#endif

/* The serial_ops for struct serial_event objects.  Note we never
   register this serial type with serial_add_interface, because this
   is internal implementation detail never to be used by remote
   targets for protocol transport.  */

static const struct serial_ops serial_event_ops =
{
  "event",
  serial_event_open,
  serial_event_close,
  NULL, /* fdopen */
  NULL, /* readchar */
  NULL, /* write */
  NULL, /* flush_output */
  NULL, /* flush_input */
  NULL, /* send_break */
  NULL, /* go_raw */
  NULL, /* get_tty_state */
  NULL, /* copy_tty_state */
  NULL, /* set_tty_state */
  NULL, /* print_tty_state */
  NULL, /* setbaudrate */
  NULL, /* setstopbits */
  NULL, /* setparity */
  NULL, /* drain_output */
  NULL, /* async */
  NULL, /* read_prim */
  NULL, /* write_prim */
  NULL, /* avail */
#ifdef USE_WIN32API
  serial_event_wait_handle,
#endif
};

/* See ser-event.h.  */

struct serial_event *
make_serial_event (void)
{
  return (struct serial_event *) serial_open_ops (&serial_event_ops);
}

/* See ser-event.h.  */

int
serial_event_fd (struct serial_event *event)
{
  struct serial *ser = (struct serial *) event;

  return ser->fd;
}

/* See ser-event.h.  */

void
serial_event_set (struct serial_event *event)
{
  struct serial *ser = (struct serial *) event;
  struct serial_event_state *state = (struct serial_event_state *) ser->state;
#ifndef USE_WIN32API
  int r;
  char c = '+';		/* Anything.  */

  do
    {
      r = write (state->write_fd, &c, 1);
    }
  while (r < 0 && errno == EINTR);
#else
  SetEvent (state->event);
#endif
}

/* See ser-event.h.  */

void
serial_event_clear (struct serial_event *event)
{
  struct serial *ser = (struct serial *) event;
#ifndef USE_WIN32API
  int r;

  do
    {
      char c;

      r = read (ser->fd, &c, 1);
    }
  while (r > 0 || (r < 0 && errno == EINTR));
#else
  struct serial_event_state *state = (struct serial_event_state *) ser->state;
  ResetEvent (state->event);
#endif
}
