barrier/lib/arch/CArchNetworkBSD.cpp
crs 848aee7a3a Checkpoint. Code does not run. Still converting over to new
event loop model.  Streams, stream filters, and sockets are
converted.  Client proxies are almost converted.  CServer is
in progress.  Removed all HTTP code.  Haven't converted the
necessary win32 arch stuff.
2004-02-01 21:09:22 +00:00

856 lines
16 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package 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.
*/
#include "CArchNetworkBSD.h"
#include "CArch.h"
#include "XArchUnix.h"
#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <netinet/in.h>
#include <netdb.h>
#if !defined(TCP_NODELAY)
# include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#if HAVE_POLL
# include <sys/poll.h>
#else
# if HAVE_SYS_SELECT_H
# include <sys/select.h>
# endif
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# endif
#endif
static const int s_family[] = {
PF_UNSPEC,
PF_INET
};
static const int s_type[] = {
SOCK_DGRAM,
SOCK_STREAM
};
//
// CArchNetworkBSD
//
CArchNetworkBSD::CArchNetworkBSD()
{
// create mutex to make some calls thread safe
m_mutex = ARCH->newMutex();
}
CArchNetworkBSD::~CArchNetworkBSD()
{
ARCH->closeMutex(m_mutex);
}
CArchSocket
CArchNetworkBSD::newSocket(EAddressFamily family, ESocketType type)
{
// allocate socket object
CArchSocketImpl* newSocket = new CArchSocketImpl;
// create socket
int fd = socket(s_family[family], s_type[type], 0);
if (fd == -1) {
throwError(errno);
}
newSocket->m_fd = fd;
newSocket->m_connected = false;
newSocket->m_refCount = 1;
return newSocket;
}
CArchSocket
CArchNetworkBSD::copySocket(CArchSocket s)
{
assert(s != NULL);
// ref the socket and return it
ARCH->lockMutex(m_mutex);
++s->m_refCount;
ARCH->unlockMutex(m_mutex);
return s;
}
void
CArchNetworkBSD::closeSocket(CArchSocket s)
{
assert(s != NULL);
// unref the socket and note if it should be released
ARCH->lockMutex(m_mutex);
const bool doClose = (--s->m_refCount == 0);
ARCH->unlockMutex(m_mutex);
// close the socket if necessary
if (doClose) {
do {
if (close(s->m_fd) == -1) {
// close failed
int err = errno;
if (err == EINTR) {
// interrupted system call
ARCH->testCancelThread();
continue;
}
// restore the last ref and throw
ARCH->lockMutex(m_mutex);
++s->m_refCount;
ARCH->unlockMutex(m_mutex);
throwError(err);
}
} while (false);
delete s;
}
}
void
CArchNetworkBSD::closeSocketForRead(CArchSocket s)
{
assert(s != NULL);
if (shutdown(s->m_fd, 0) == -1) {
if (errno != ENOTCONN) {
throwError(errno);
}
}
}
void
CArchNetworkBSD::closeSocketForWrite(CArchSocket s)
{
assert(s != NULL);
if (shutdown(s->m_fd, 1) == -1) {
if (errno != ENOTCONN) {
throwError(errno);
}
}
}
void
CArchNetworkBSD::bindSocket(CArchSocket s, CArchNetAddress addr)
{
assert(s != NULL);
assert(addr != NULL);
if (bind(s->m_fd, &addr->m_addr, addr->m_len) == -1) {
throwError(errno);
}
}
void
CArchNetworkBSD::listenOnSocket(CArchSocket s)
{
assert(s != NULL);
// hardcoding backlog
if (listen(s->m_fd, 3) == -1) {
throwError(errno);
}
}
CArchSocket
CArchNetworkBSD::acceptSocket(CArchSocket s, CArchNetAddress* addr)
{
assert(s != NULL);
// if user passed NULL in addr then use scratch space
CArchNetAddress dummy;
if (addr == NULL) {
addr = &dummy;
}
// create new socket and address
CArchSocketImpl* newSocket = new CArchSocketImpl;
*addr = new CArchNetAddressImpl;
// accept on socket
int fd;
do {
fd = accept(s->m_fd, &(*addr)->m_addr, &(*addr)->m_len);
if (fd == -1) {
int err = errno;
if (err == EINTR) {
// interrupted system call
ARCH->testCancelThread();
continue;
}
delete newSocket;
delete *addr;
*addr = NULL;
throwError(err);
}
} while (false);
// initialize socket
newSocket->m_fd = fd;
newSocket->m_connected = true;
newSocket->m_refCount = 1;
// discard address if not requested
if (addr == &dummy) {
ARCH->closeAddr(dummy);
}
return newSocket;
}
void
CArchNetworkBSD::connectSocket(CArchSocket s, CArchNetAddress addr)
{
assert(s != NULL);
assert(addr != NULL);
do {
if (connect(s->m_fd, &addr->m_addr, addr->m_len) == -1) {
if (errno == EINTR) {
// interrupted system call
ARCH->testCancelThread();
continue;
}
if (errno == EISCONN) {
// already connected
break;
}
throwError(errno);
}
} while (false);
ARCH->lockMutex(m_mutex);
s->m_connected = true;
ARCH->unlockMutex(m_mutex);
}
#if HAVE_POLL
int
CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout)
{
assert(pe != NULL || num == 0);
// return if nothing to do
if (num == 0) {
if (timeout > 0.0) {
ARCH->sleep(timeout);
}
return 0;
}
// allocate space for translated query
struct pollfd* pfd = reinterpret_cast<struct pollfd*>(
alloca(num * sizeof(struct pollfd)));
// translate query
for (int i = 0; i < num; ++i) {
pfd[i].fd = (pe[i].m_socket == NULL) ? -1 : pe[i].m_socket->m_fd;
pfd[i].events = 0;
if ((pe[i].m_events & kPOLLIN) != 0) {
pfd[i].events |= POLLIN;
}
if ((pe[i].m_events & kPOLLOUT) != 0) {
pfd[i].events |= POLLOUT;
}
}
// do the poll
int t = (timeout < 0.0) ? -1 : static_cast<int>(1000.0 * timeout);
int n;
do {
n = poll(pfd, num, t);
if (n == -1) {
if (errno == EINTR) {
// interrupted system call
ARCH->testCancelThread();
return 0;
}
throwError(errno);
}
} while (false);
// translate back
for (int i = 0; i < num; ++i) {
pe[i].m_revents = 0;
if ((pfd[i].revents & POLLIN) != 0) {
pe[i].m_revents |= kPOLLIN;
}
if ((pfd[i].revents & POLLOUT) != 0) {
pe[i].m_revents |= kPOLLOUT;
}
if ((pfd[i].revents & POLLERR) != 0) {
pe[i].m_revents |= kPOLLERR;
}
if ((pfd[i].revents & POLLNVAL) != 0) {
pe[i].m_revents |= kPOLLNVAL;
}
}
return n;
}
#else
int
CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout)
{
int i, n;
do {
// prepare sets for select
n = 0;
fd_set readSet, writeSet, errSet;
fd_set* readSetP = NULL;
fd_set* writeSetP = NULL;
fd_set* errSetP = NULL;
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_ZERO(&errSet);
for (i = 0; i < num; ++i) {
// reset return flags
pe[i].m_revents = 0;
// set invalid flag if socket is bogus then go to next socket
if (pe[i].m_socket == NULL) {
pe[i].m_revents |= kPOLLNVAL;
continue;
}
int fdi = pe[i].m_socket->m_fd;
if (pe[i].m_events & kPOLLIN) {
FD_SET(pe[i].m_socket->m_fd, &readSet);
readSetP = &readSet;
if (fdi > n) {
n = fdi;
}
}
if (pe[i].m_events & kPOLLOUT) {
FD_SET(pe[i].m_socket->m_fd, &writeSet);
writeSetP = &writeSet;
if (fdi > n) {
n = fdi;
}
}
if (true) {
FD_SET(pe[i].m_socket->m_fd, &errSet);
errSetP = &errSet;
if (fdi > n) {
n = fdi;
}
}
}
// if there are no sockets then don't block forever
if (n == 0 && timeout < 0.0) {
timeout = 0.0;
}
// prepare timeout for select
struct timeval timeout2;
struct timeval* timeout2P;
if (timeout < 0.0) {
timeout2P = NULL;
}
else {
timeout2P = &timeout2;
timeout2.tv_sec = static_cast<int>(timeout);
timeout2.tv_usec = static_cast<int>(1.0e+6 *
(timeout - timeout2.tv_sec));
}
// do the select
n = select((SELECT_TYPE_ARG1) n + 1,
SELECT_TYPE_ARG234 readSetP,
SELECT_TYPE_ARG234 writeSetP,
SELECT_TYPE_ARG234 errSetP,
SELECT_TYPE_ARG5 timeout2P);
// handle results
if (n == -1) {
if (errno == EINTR) {
// interrupted system call
ARCH->testCancelThread();
return 0;
}
throwError(errno);
}
n = 0;
for (i = 0; i < num; ++i) {
if (pe[i].m_socket != NULL) {
if (FD_ISSET(pe[i].m_socket->m_fd, &readSet)) {
pe[i].m_revents |= kPOLLIN;
}
if (FD_ISSET(pe[i].m_socket->m_fd, &writeSet)) {
pe[i].m_revents |= kPOLLOUT;
}
if (FD_ISSET(pe[i].m_socket->m_fd, &errSet)) {
pe[i].m_revents |= kPOLLERR;
}
}
if (pe[i].m_revents != 0) {
++n;
}
}
} while (false);
return n;
}
#endif
size_t
CArchNetworkBSD::readSocket(CArchSocket s, void* buf, size_t len)
{
assert(s != NULL);
ssize_t n;
do {
n = read(s->m_fd, buf, len);
if (n == -1) {
if (errno == EINTR) {
// interrupted system call
ARCH->testCancelThread();
continue;
}
else if (errno == EAGAIN) {
n = 0;
break;
}
throwError(errno);
}
} while (false);
ARCH->testCancelThread();
return n;
}
size_t
CArchNetworkBSD::writeSocket(CArchSocket s, const void* buf, size_t len)
{
assert(s != NULL);
ssize_t n;
do {
n = write(s->m_fd, buf, len);
if (n == -1) {
if (errno == EINTR) {
// interrupted system call
ARCH->testCancelThread();
continue;
}
else if (errno == EAGAIN) {
// no buffer space
n = 0;
break;
}
throwError(errno);
}
} while (false);
ARCH->testCancelThread();
return n;
}
void
CArchNetworkBSD::throwErrorOnSocket(CArchSocket s)
{
assert(s != NULL);
// get the error from the socket layer
int err = 0;
socklen_t size = sizeof(err);
if (getsockopt(s->m_fd, SOL_SOCKET, SO_ERROR, &err, &size) == -1) {
err = errno;
}
// throw if there's an error
if (err != 0) {
throwError(err);
}
}
bool
CArchNetworkBSD::setBlockingOnSocket(CArchSocket s, bool blocking)
{
assert(s != NULL);
int mode = fcntl(s->m_fd, F_GETFL, 0);
if (mode == -1) {
throwError(errno);
}
bool old = ((mode & O_NDELAY) == 0);
if (blocking) {
mode &= ~O_NDELAY;
}
else {
mode |= O_NDELAY;
}
if (fcntl(s->m_fd, F_SETFL, mode) == -1) {
throwError(errno);
}
return old;
}
bool
CArchNetworkBSD::setNoDelayOnSocket(CArchSocket s, bool noDelay)
{
assert(s != NULL);
// get old state
int oflag;
socklen_t size = sizeof(oflag);
if (getsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, &oflag, &size) == -1) {
throwError(errno);
}
int flag = noDelay ? 1 : 0;
size = sizeof(flag);
if (setsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, &flag, size) == -1) {
throwError(errno);
}
return (oflag != 0);
}
std::string
CArchNetworkBSD::getHostName()
{
char name[256];
if (gethostname(name, sizeof(name)) == -1) {
name[0] = '\0';
}
else {
name[sizeof(name) - 1] = '\0';
}
return name;
}
CArchNetAddress
CArchNetworkBSD::newAnyAddr(EAddressFamily family)
{
// allocate address
CArchNetAddressImpl* addr = new CArchNetAddressImpl;
// fill it in
switch (family) {
case kINET: {
struct sockaddr_in* ipAddr =
reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
ipAddr->sin_family = AF_INET;
ipAddr->sin_port = 0;
ipAddr->sin_addr.s_addr = INADDR_ANY;
addr->m_len = sizeof(struct sockaddr_in);
break;
}
default:
delete addr;
assert(0 && "invalid family");
}
return addr;
}
CArchNetAddress
CArchNetworkBSD::copyAddr(CArchNetAddress addr)
{
assert(addr != NULL);
// allocate and copy address
return new CArchNetAddressImpl(*addr);
}
CArchNetAddress
CArchNetworkBSD::nameToAddr(const std::string& name)
{
// allocate address
CArchNetAddressImpl* addr = new CArchNetAddressImpl;
// try to convert assuming an IPv4 dot notation address
struct sockaddr_in inaddr;
memset(&inaddr, 0, sizeof(inaddr));
if (inet_aton(name.c_str(), &inaddr.sin_addr) != 0) {
// it's a dot notation address
addr->m_len = sizeof(struct sockaddr_in);
inaddr.sin_family = AF_INET;
inaddr.sin_port = 0;
memcpy(&addr->m_addr, &inaddr, addr->m_len);
}
else {
// mutexed address lookup (ugh)
ARCH->lockMutex(m_mutex);
struct hostent* info = gethostbyname(name.c_str());
if (info == NULL) {
ARCH->unlockMutex(m_mutex);
delete addr;
throwNameError(h_errno);
}
// copy over address (only IPv4 currently supported)
if (info->h_addrtype == AF_INET) {
addr->m_len = sizeof(struct sockaddr_in);
inaddr.sin_family = info->h_addrtype;
inaddr.sin_port = 0;
memcpy(&inaddr.sin_addr, info->h_addr_list[0],
sizeof(inaddr.sin_addr));
memcpy(&addr->m_addr, &inaddr, addr->m_len);
}
// done with static buffer
ARCH->unlockMutex(m_mutex);
}
return addr;
}
void
CArchNetworkBSD::closeAddr(CArchNetAddress addr)
{
assert(addr != NULL);
delete addr;
}
std::string
CArchNetworkBSD::addrToName(CArchNetAddress addr)
{
assert(addr != NULL);
// mutexed name lookup (ugh)
ARCH->lockMutex(m_mutex);
struct hostent* info = gethostbyaddr(
reinterpret_cast<const char*>(&addr->m_addr),
addr->m_len, addr->m_addr.sa_family);
if (info == NULL) {
ARCH->unlockMutex(m_mutex);
throwNameError(h_errno);
}
// save (primary) name
std::string name = info->h_name;
// done with static buffer
ARCH->unlockMutex(m_mutex);
return name;
}
std::string
CArchNetworkBSD::addrToString(CArchNetAddress addr)
{
assert(addr != NULL);
switch (getAddrFamily(addr)) {
case kINET: {
struct sockaddr_in* ipAddr =
reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
ARCH->lockMutex(m_mutex);
std::string s = inet_ntoa(ipAddr->sin_addr);
ARCH->unlockMutex(m_mutex);
return s;
}
default:
assert(0 && "unknown address family");
return "";
}
}
IArchNetwork::EAddressFamily
CArchNetworkBSD::getAddrFamily(CArchNetAddress addr)
{
assert(addr != NULL);
switch (addr->m_addr.sa_family) {
case AF_INET:
return kINET;
default:
return kUNKNOWN;
}
}
void
CArchNetworkBSD::setAddrPort(CArchNetAddress addr, int port)
{
assert(addr != NULL);
switch (getAddrFamily(addr)) {
case kINET: {
struct sockaddr_in* ipAddr =
reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
ipAddr->sin_port = htons(port);
break;
}
default:
assert(0 && "unknown address family");
break;
}
}
int
CArchNetworkBSD::getAddrPort(CArchNetAddress addr)
{
assert(addr != NULL);
switch (getAddrFamily(addr)) {
case kINET: {
struct sockaddr_in* ipAddr =
reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
return ntohs(ipAddr->sin_port);
}
default:
assert(0 && "unknown address family");
return 0;
}
}
bool
CArchNetworkBSD::isAnyAddr(CArchNetAddress addr)
{
assert(addr != NULL);
switch (getAddrFamily(addr)) {
case kINET: {
struct sockaddr_in* ipAddr =
reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
return (ipAddr->sin_addr.s_addr == INADDR_ANY &&
addr->m_len == sizeof(struct sockaddr_in));
}
default:
assert(0 && "unknown address family");
return true;
}
}
void
CArchNetworkBSD::throwError(int err)
{
switch (err) {
case EAGAIN:
throw XArchNetworkWouldBlock(new XArchEvalUnix(err));
case EACCES:
case EPERM:
throw XArchNetworkAccess(new XArchEvalUnix(err));
case ENFILE:
case EMFILE:
case ENODEV:
case ENOBUFS:
case ENOMEM:
case ENETDOWN:
#if defined(ENOSR)
case ENOSR:
#endif
throw XArchNetworkResource(new XArchEvalUnix(err));
case EPROTOTYPE:
case EPROTONOSUPPORT:
case EAFNOSUPPORT:
case EPFNOSUPPORT:
case ESOCKTNOSUPPORT:
case EINVAL:
case ENOPROTOOPT:
case EOPNOTSUPP:
case ESHUTDOWN:
#if defined(ENOPKG)
case ENOPKG:
#endif
throw XArchNetworkSupport(new XArchEvalUnix(err));
case EIO:
throw XArchNetworkIO(new XArchEvalUnix(err));
case EADDRNOTAVAIL:
throw XArchNetworkNoAddress(new XArchEvalUnix(err));
case EADDRINUSE:
throw XArchNetworkAddressInUse(new XArchEvalUnix(err));
case EHOSTUNREACH:
case ENETUNREACH:
throw XArchNetworkNoRoute(new XArchEvalUnix(err));
case ENOTCONN:
throw XArchNetworkNotConnected(new XArchEvalUnix(err));
case EPIPE:
throw XArchNetworkShutdown(new XArchEvalUnix(err));
case ECONNABORTED:
case ECONNRESET:
throw XArchNetworkDisconnected(new XArchEvalUnix(err));
case ECONNREFUSED:
throw XArchNetworkConnectionRefused(new XArchEvalUnix(err));
case EINPROGRESS:
case EALREADY:
throw XArchNetworkConnecting(new XArchEvalUnix(err));
case EHOSTDOWN:
case ETIMEDOUT:
throw XArchNetworkTimedOut(new XArchEvalUnix(err));
default:
throw XArchNetwork(new XArchEvalUnix(err));
}
}
void
CArchNetworkBSD::throwNameError(int err)
{
static const char* s_msg[] = {
"The specified host is unknown",
"The requested name is valid but does not have an IP address",
"A non-recoverable name server error occurred",
"A temporary error occurred on an authoritative name server",
"An unknown name server error occurred"
};
switch (err) {
case HOST_NOT_FOUND:
throw XArchNetworkNameUnknown(s_msg[0]);
case NO_DATA:
throw XArchNetworkNameNoAddress(s_msg[1]);
case NO_RECOVERY:
throw XArchNetworkNameFailure(s_msg[2]);
case TRY_AGAIN:
throw XArchNetworkNameUnavailable(s_msg[3]);
default:
throw XArchNetworkName(s_msg[4]);
}
}