/*
Copyright (c) 2010-2012 250bpm s.r.o.
Copyright (c) 2010-2011 Other contributors as noted in the AUTHORS file
This file is part of Crossroads I/O project.
Crossroads I/O is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
Crossroads 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
*/
#include "platform.hpp"
#if defined XS_FORCE_SELECT
#define XS_SIGNALER_WAIT_BASED_ON_SELECT
#elif defined XS_FORCE_POLL
#define XS_SIGNALER_WAIT_BASED_ON_POLL
#elif defined XS_HAVE_LINUX || defined XS_HAVE_FREEBSD ||\
defined XS_HAVE_OPENBSD || defined XS_HAVE_SOLARIS ||\
defined XS_HAVE_OSX || defined XS_HAVE_QNXNTO ||\
defined XS_HAVE_HPUX || defined XS_HAVE_AIX ||\
defined XS_HAVE_NETBSD
#define XS_SIGNALER_WAIT_BASED_ON_POLL
#elif defined XS_HAVE_WINDOWS || defined XS_HAVE_OPENVMS ||\
defined XS_HAVE_CYGWIN
#define XS_SIGNALER_WAIT_BASED_ON_SELECT
#endif
// On AIX, poll.h has to be included before xs.h to get consistent
// definition of pollfd structure (AIX uses 'reqevents' and 'retnevents'
// instead of 'events' and 'revents' and defines macros to map from POSIX-y
// names to AIX-specific names).
#if defined XS_SIGNALER_WAIT_BASED_ON_POLL
#include
#elif defined XS_SIGNALER_WAIT_BASED_ON_SELECT
#if defined XS_HAVE_WINDOWS
#include "windows.hpp"
#elif defined XS_HAVE_HPUX
#include
#include
#include
#elif defined XS_HAVE_OPENVMS
#include
#include
#else
#include
#endif
#endif
#include "signaler.hpp"
#include "likely.hpp"
#include "stdint.hpp"
#include "config.hpp"
#include "err.hpp"
#include "fd.hpp"
#include "ip.hpp"
#if defined XS_HAVE_EVENTFD
#include
#endif
#if defined XS_HAVE_WINDOWS
#include "windows.hpp"
#else
#include
#include
#include
#include
#include
#endif
xs::signaler_t::signaler_t ()
{
// Create the socketpair for signaling.
int rc = make_fdpair (&r, &w);
errno_assert (rc == 0);
// Set both fds to non-blocking mode.
unblock_socket (w);
unblock_socket (r);
}
xs::signaler_t::~signaler_t ()
{
#if defined XS_HAVE_EVENTFD
int rc = close (r);
errno_assert (rc == 0);
#elif defined XS_HAVE_WINDOWS
int rc = closesocket (w);
wsa_assert (rc != SOCKET_ERROR);
rc = closesocket (r);
wsa_assert (rc != SOCKET_ERROR);
#else
int rc = close (w);
errno_assert (rc == 0);
rc = close (r);
errno_assert (rc == 0);
#endif
}
xs::fd_t xs::signaler_t::get_fd ()
{
return r;
}
void xs::signaler_t::send ()
{
#if defined XS_HAVE_EVENTFD
const uint64_t inc = 1;
ssize_t sz = write (w, &inc, sizeof (inc));
errno_assert (sz == sizeof (inc));
#elif defined XS_HAVE_WINDOWS
unsigned char dummy = 0;
int nbytes = ::send (w, (char*) &dummy, sizeof (dummy), 0);
wsa_assert (nbytes != SOCKET_ERROR);
xs_assert (nbytes == sizeof (dummy));
#else
unsigned char dummy = 0;
while (true) {
ssize_t nbytes = ::send (w, &dummy, sizeof (dummy), 0);
if (unlikely (nbytes == -1 && errno == EINTR))
continue;
xs_assert (nbytes == sizeof (dummy));
break;
}
#endif
}
int xs::signaler_t::wait (int timeout_)
{
#ifdef XS_SIGNALER_WAIT_BASED_ON_POLL
struct pollfd pfd;
pfd.fd = r;
pfd.events = POLLIN;
int rc = poll (&pfd, 1, timeout_);
if (unlikely (rc < 0)) {
xs_assert (errno == EINTR);
return -1;
}
else if (unlikely (rc == 0)) {
errno = EAGAIN;
return -1;
}
xs_assert (rc == 1);
xs_assert (pfd.revents & POLLIN);
return 0;
#elif defined XS_SIGNALER_WAIT_BASED_ON_SELECT
fd_set fds;
FD_ZERO (&fds);
FD_SET (r, &fds);
struct timeval timeout;
if (timeout_ >= 0) {
timeout.tv_sec = timeout_ / 1000;
timeout.tv_usec = timeout_ % 1000 * 1000;
}
#ifdef XS_HAVE_WINDOWS
int rc = select (0, &fds, NULL, NULL,
timeout_ >= 0 ? &timeout : NULL);
wsa_assert (rc != SOCKET_ERROR);
#else
int rc = select (r + 1, &fds, NULL, NULL,
timeout_ >= 0 ? &timeout : NULL);
if (unlikely (rc < 0)) {
xs_assert (errno == EINTR);
return -1;
}
#endif
if (unlikely (rc == 0)) {
errno = EAGAIN;
return -1;
}
xs_assert (rc == 1);
return 0;
#else
#error
#endif
}
void xs::signaler_t::recv ()
{
// Attempt to read a signal.
#if defined XS_HAVE_EVENTFD
uint64_t dummy;
ssize_t sz = read (r, &dummy, sizeof (dummy));
errno_assert (sz == sizeof (dummy));
// If we accidentally grabbed the next signal along with the current
// one, return it back to the eventfd object.
if (unlikely (dummy == 2)) {
const uint64_t inc = 1;
ssize_t sz = write (w, &inc, sizeof (inc));
errno_assert (sz == sizeof (inc));
return;
}
xs_assert (dummy == 1);
#else
unsigned char dummy;
#if defined XS_HAVE_WINDOWS
int nbytes = ::recv (r, (char*) &dummy, sizeof (dummy), 0);
wsa_assert (nbytes != SOCKET_ERROR);
#else
ssize_t nbytes = ::recv (r, &dummy, sizeof (dummy), 0);
errno_assert (nbytes >= 0);
#endif
xs_assert (nbytes == sizeof (dummy));
xs_assert (dummy == 0);
#endif
}
int xs::signaler_t::make_fdpair (fd_t *r_, fd_t *w_)
{
#if defined XS_HAVE_EVENTFD
// Create eventfd object.
fd_t fd = eventfd (0, 0);
errno_assert (fd != -1);
*w_ = fd;
*r_ = fd;
return 0;
#elif defined XS_HAVE_WINDOWS
// On Windows we are using TCP sockets for in-process communication.
// That is a security hole -- other processes on the same box may connect
// to the bound TCP port and hook into internal signal processing of
// the library. To solve this problem we should use a proper in-process
// signaling mechanism such as private semaphore. However, on Windows,
// these cannot be polled on using select(). Other functions that allow
// polling on these objects (e.g. WaitForMulitpleObjects) don't allow
// to poll on sockets. Thus, the only way to fix the problem is to
// implement IOCP polling mechanism that allows to poll on both sockets
// and in-process synchronisation objects.
// This function has to be in a system-wide critical section so that
// two instances of the library don't accidentally create signaler
// crossing the process boundary.
// We'll use named event object to implement the critical section.
HANDLE sync = CreateEvent (NULL, FALSE, TRUE, "xs-signaler-port-sync");
win_assert (sync != NULL);
// Enter the critical section.
DWORD dwrc = WaitForSingleObject (sync, INFINITE);
xs_assert (dwrc == WAIT_OBJECT_0);
// Windows has no 'socketpair' function. CreatePipe is no good as pipe
// handles cannot be polled on. Here we create the socketpair by hand.
*w_ = INVALID_SOCKET;
*r_ = INVALID_SOCKET;
// Create listening socket.
SOCKET listener;
listener = open_socket (AF_INET, SOCK_STREAM, 0);
wsa_assert (listener != INVALID_SOCKET);
// Set SO_REUSEADDR and TCP_NODELAY on listening socket.
BOOL so_reuseaddr = 1;
int rc = setsockopt (listener, SOL_SOCKET, SO_REUSEADDR,
(char *)&so_reuseaddr, sizeof (so_reuseaddr));
wsa_assert (rc != SOCKET_ERROR);
BOOL tcp_nodelay = 1;
rc = setsockopt (listener, IPPROTO_TCP, TCP_NODELAY,
(char *)&tcp_nodelay, sizeof (tcp_nodelay));
wsa_assert (rc != SOCKET_ERROR);
// Bind listening socket to the local port.
struct sockaddr_in addr;
memset (&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
addr.sin_port = htons (signaler_port);
rc = bind (listener, (const struct sockaddr*) &addr, sizeof (addr));
wsa_assert (rc != SOCKET_ERROR);
// Listen for incomming connections.
rc = listen (listener, 1);
wsa_assert (rc != SOCKET_ERROR);
// Create the writer socket.
*w_ = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
wsa_assert (*w_ != INVALID_SOCKET);
// Set TCP_NODELAY on writer socket.
rc = setsockopt (*w_, IPPROTO_TCP, TCP_NODELAY,
(char *)&tcp_nodelay, sizeof (tcp_nodelay));
wsa_assert (rc != SOCKET_ERROR);
// Connect writer to the listener.
rc = connect (*w_, (sockaddr *) &addr, sizeof (addr));
wsa_assert (rc != SOCKET_ERROR);
// Accept connection from writer.
*r_ = accept (listener, NULL, NULL);
wsa_assert (*r_ != INVALID_SOCKET);
// We don't need the listening socket anymore. Close it.
rc = closesocket (listener);
wsa_assert (rc != SOCKET_ERROR);
// Exit the critical section.
BOOL brc = SetEvent (sync);
win_assert (brc != 0);
return 0;
#elif defined XS_HAVE_OPENVMS
// Whilst OpenVMS supports socketpair - it maps to AF_INET only. Further,
// it does not set the socket options TCP_NODELAY and TCP_NODELACK which
// can lead to performance problems.
//
// The bug will be fixed in V5.6 ECO4 and beyond. In the meantime, we'll
// create the socket pair manually.
sockaddr_in lcladdr;
memset (&lcladdr, 0, sizeof (lcladdr));
lcladdr.sin_family = AF_INET;
lcladdr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
lcladdr.sin_port = 0;
int listener = open_socket (AF_INET, SOCK_STREAM, 0);
errno_assert (listener != -1);
int on = 1;
int rc = setsockopt (listener, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on));
errno_assert (rc != -1);
rc = setsockopt (listener, IPPROTO_TCP, TCP_NODELACK, &on, sizeof (on));
errno_assert (rc != -1);
rc = bind(listener, (struct sockaddr*) &lcladdr, sizeof (lcladdr));
errno_assert (rc != -1);
socklen_t lcladdr_len = sizeof (lcladdr);
rc = getsockname (listener, (struct sockaddr*) &lcladdr, &lcladdr_len);
errno_assert (rc != -1);
rc = listen (listener, 1);
errno_assert (rc != -1);
*w_ = open_socket (AF_INET, SOCK_STREAM, 0);
errno_assert (*w_ != -1);
rc = setsockopt (*w_, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on));
errno_assert (rc != -1);
rc = setsockopt (*w_, IPPROTO_TCP, TCP_NODELACK, &on, sizeof (on));
errno_assert (rc != -1);
rc = connect (*w_, (struct sockaddr*) &lcladdr, sizeof (lcladdr));
errno_assert (rc != -1);
*r_ = accept (listener, NULL, NULL);
errno_assert (*r_ != -1);
close (listener);
return 0;
#else // All other implementations support socketpair()
int sv [2];
int rc = socketpair (AF_UNIX, SOCK_STREAM, 0, sv);
errno_assert (rc == 0);
*w_ = sv [0];
*r_ = sv [1];
return 0;
#endif
}
#if defined XS_SIGNALER_WAIT_BASED_ON_SELECT
#undef XS_SIGNALER_WAIT_BASED_ON_SELECT
#endif
#if defined XS_SIGNALER_WAIT_BASED_ON_POLL
#undef XS_SIGNALER_WAIT_BASED_ON_POLL
#endif