/* fhandler_fifo.cc - See fhandler.h for a description of the fhandler classes.

   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 "miscfuncs.h"

#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "sigproc.h"
#include "cygtls.h"
#include "shared_info.h"
#include "ntdll.h"
#include "cygwait.h"

fhandler_fifo::fhandler_fifo ():
  fhandler_base_overlapped (),
  read_ready (NULL), write_ready (NULL)
{
  max_atomic_write = DEFAULT_PIPEBUFSIZE;
  need_fork_fixup (true);
}

#define fnevent(w) fifo_name (npbuf, w "-event")
#define fnpipe() fifo_name (npbuf, "fifo")
#define create_pipe(r, w) \
  fhandler_pipe::create (sa_buf, (r), (w), 0, fnpipe (), open_mode)

char *
fhandler_fifo::fifo_name (char *buf, const char *what)
{
  /* Generate a semi-unique name to associate with this fifo. */
  __small_sprintf (buf, "%s.%08x.%016X", what, get_dev (),
		   get_ino ());
  return buf;
}

inline PSECURITY_ATTRIBUTES
sec_user_cloexec (bool cloexec, PSECURITY_ATTRIBUTES sa, PSID sid)
{
  return cloexec ? sec_user_nih (sa, sid) : sec_user (sa, sid);
}

bool inline
fhandler_fifo::arm (HANDLE h)
{
#ifdef DEBUGGING
  const char *what;
  if (h == read_ready)
    what = "reader";
  else if (h == write_ready)
    what = "writer";
  else
    what = "overlapped event";
  debug_only_printf ("arming %s", what);
#endif

  bool res = SetEvent (h);
  if (!res)
#ifdef DEBUGGING
    debug_printf ("SetEvent for %s failed, %E", what);
#else
    debug_printf ("SetEvent failed, %E");
#endif
  return res;
}

int
fhandler_fifo::open (int flags, mode_t)
{
  enum
  {
    success,
    error_errno_set,
    error_set_errno
  } res;
  bool reader, writer, duplexer;
  DWORD open_mode = FILE_FLAG_OVERLAPPED;

  /* Determine what we're doing with this fhandler: reading, writing, both */
  switch (flags & O_ACCMODE)
    {
    case O_RDONLY:
      reader = true;
      writer = false;
      duplexer = false;
      break;
    case O_WRONLY:
      writer = true;
      reader = false;
      duplexer = false;
      break;
    case O_RDWR:
      open_mode |= PIPE_ACCESS_DUPLEX;
      reader = true;
      writer = false;
      duplexer = true;
      break;
    default:
      set_errno (EINVAL);
      res = error_errno_set;
      goto out;
    }

  debug_only_printf ("reader %d, writer %d, duplexer %d", reader, writer, duplexer);
  set_flags (flags);
  char char_sa_buf[1024];
  LPSECURITY_ATTRIBUTES sa_buf;
  sa_buf = sec_user_cloexec (flags & O_CLOEXEC, (PSECURITY_ATTRIBUTES) char_sa_buf,
		      cygheap->user.sid());
  char npbuf[MAX_PATH];

  /* Create control events for this named pipe */
  if (!(read_ready = CreateEvent (sa_buf, duplexer, false, fnevent ("r"))))
    {
      debug_printf ("CreatEvent for %s failed, %E", npbuf);
      res = error_set_errno;
      goto out;
    }
  if (!(write_ready = CreateEvent (sa_buf, false, false, fnevent ("w"))))
    {
      debug_printf ("CreatEvent for %s failed, %E", npbuf);
      res = error_set_errno;
      goto out;
    }

  /* If we're reading, create the pipe, signal that we're ready and wait for
     a writer.
     FIXME: Probably need to special case O_RDWR case.  */
  if (!reader)
    /* We are not a reader */;
  else if (create_pipe (&get_io_handle (), NULL))
    {
      debug_printf ("create of reader failed");
      res = error_set_errno;
      goto out;
    }
  else if (!arm (read_ready))
    {
      res = error_set_errno;
      goto out;
    }
  else if (!duplexer && !wait (write_ready))
    {
      res = error_errno_set;
      goto out;
    }

  /* If we're writing, it's a little tricky since it is possible that
     we're attempting to open the other end of a pipe which is already
     connected.  In that case, we detect ERROR_PIPE_BUSY, reset the
     read_ready event and wait for the reader to allow us to connect
     by signalling read_ready.

     Once the pipe has been set up, we signal write_ready.  */
  if (writer)
    {
      int err;
      while (1)
	if (!wait (read_ready))
	  {
	    res = error_errno_set;
	    goto out;
	  }
	else if ((err = create_pipe (NULL, &get_io_handle ())) == 0)
	  break;
	else if (err == ERROR_PIPE_BUSY)
	  {
	    debug_only_printf ("pipe busy");
	    ResetEvent (read_ready);
	  }
	else
	  {
	    debug_printf ("create of writer failed");
	    res = error_set_errno;
	    goto out;
	  }
      if (!arm (write_ready))
	{
	  res = error_set_errno;
	  goto out;
	}
    }

  /* If setup_overlapped() succeeds (and why wouldn't it?) we are all set. */
  if (setup_overlapped () == 0)
    res = success;
  else
    {
      debug_printf ("setup_overlapped failed, %E");
      res = error_set_errno;
    }

out:
  if (res == error_set_errno)
    __seterrno ();
  if (res != success)
    {
      if (read_ready)
	{
	  CloseHandle (read_ready);
	  read_ready = NULL;
	}
      if (write_ready)
	{
	  CloseHandle (write_ready);
	  write_ready = NULL;
	}
      if (get_io_handle ())
	CloseHandle (get_io_handle ());
    }
  debug_printf ("res %d", res);
  return res == success;
}

bool
fhandler_fifo::wait (HANDLE h)
{
#ifdef DEBUGGING
  const char *what;
  if (h == read_ready)
    what = "reader";
  else if (h == write_ready)
    what = "writer";
  else
    what = "overlapped event";
#endif
  /* Set the wait to zero for non-blocking I/O-related events. */
  DWORD wait = ((h == read_ready || h == write_ready)
		&& get_flags () & O_NONBLOCK) ? 0 : INFINITE;

  debug_only_printf ("waiting for %s", what);
  /* Wait for the event.  Set errno, as appropriate if something goes wrong. */
  switch (cygwait (h, wait))
    {
    case WAIT_OBJECT_0:
      debug_only_printf ("successfully waited for %s", what);
      return true;
    case WAIT_SIGNALED:
      debug_only_printf ("interrupted by signal while waiting for %s", what);
      set_errno (EINTR);
      return false;
    case WAIT_CANCELED:
      debug_only_printf ("cancellable interruption while waiting for %s", what);
      pthread::static_cancel_self ();	/* never returns */
      break;
    case WAIT_TIMEOUT:
      if (h == write_ready)
	{
	  debug_only_printf ("wait timed out waiting for write but will still open reader since non-blocking mode");
	  return true;
	}
      else
	{
	  set_errno (ENXIO);
	  return false;
	}
      break;
    default:
      debug_only_printf ("unknown error while waiting for %s", what);
      __seterrno ();
      return false;
   }
}

void __reg3
fhandler_fifo::raw_read (void *in_ptr, size_t& len)
{
  size_t orig_len = len;
  for (int i = 0; i < 2; i++)
    {
      fhandler_base_overlapped::raw_read (in_ptr, len);
      if (len || i || WaitForSingleObject (read_ready, 0) != WAIT_OBJECT_0)
	break;
      /* If we got here, then fhandler_base_overlapped::raw_read returned 0,
	 indicating "EOF" and something has set read_ready to zero.  That means
	 we should have a client waiting to connect.
	 FIXME: If the client CTRL-C's the open during this time then this
	 could hang indefinitely.  Maybe implement a timeout?  */
      if (!DisconnectNamedPipe (get_io_handle ()))
	{
	  debug_printf ("DisconnectNamedPipe failed, %E");
	  goto errno_out;
	}
      else if (!ConnectNamedPipe (get_io_handle (), get_overlapped ())
	       && GetLastError () != ERROR_IO_PENDING)
	{
	  debug_printf ("ConnectNamedPipe failed, %E");
	  goto errno_out;
	}
      else if (!arm (read_ready))
	goto errno_out;
      else if (!wait (get_overlapped_buffer ()->hEvent))
	goto errout;	/* If wait() fails, errno is set so no need to set it */
      len = orig_len;	/* Reset since raw_read above set it to zero. */
    }
  return;

errno_out:
  __seterrno ();
errout:
  len = -1;
}

int __reg2
fhandler_fifo::fstatvfs (struct statvfs *sfs)
{
  fhandler_disk_file fh (pc);
  fh.get_device () = FH_FS;
  return fh.fstatvfs (sfs);
}

int
fhandler_fifo::close ()
{
  CloseHandle (read_ready);
  CloseHandle (write_ready);
  return fhandler_base::close ();
}

int
fhandler_fifo::dup (fhandler_base *child, int flags)
{
  if (fhandler_base_overlapped::dup (child, flags))
    {
      __seterrno ();
      return -1;
    }
  fhandler_fifo *fhf = (fhandler_fifo *) child;
  if (!DuplicateHandle (GetCurrentProcess (), read_ready,
			GetCurrentProcess (), &fhf->read_ready,
			0, true, DUPLICATE_SAME_ACCESS))
    {
      fhf->close ();
      __seterrno ();
      return -1;
    }
  if (!DuplicateHandle (GetCurrentProcess (), write_ready,
			GetCurrentProcess (), &fhf->write_ready,
			0, true, DUPLICATE_SAME_ACCESS))
    {
      CloseHandle (fhf->read_ready);
      fhf->close ();
      __seterrno ();
      return -1;
    }
  return 0;
}

void
fhandler_fifo::fixup_after_fork (HANDLE parent)
{
  fhandler_base_overlapped::fixup_after_fork (parent);
  fork_fixup (parent, read_ready, "read_ready");
  fork_fixup (parent, write_ready, "write_ready");
}

void
fhandler_fifo::set_close_on_exec (bool val)
{
  fhandler_base::set_close_on_exec (val);
  set_no_inheritance (read_ready, val);
  set_no_inheritance (write_ready, val);
}
