2405 lines
52 KiB
C
Executable File
2405 lines
52 KiB
C
Executable File
/* $Id$
|
|
|
|
Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: wielemak@science.uva.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 1985-2007, University of Amsterdam
|
|
|
|
This library 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 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library 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 library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#define O_DEBUG 1
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
This module is extracted from socket.c to provide a common ground for
|
|
accessing sockets and possibly other devices in non-blocking mode,
|
|
allowing for GUI (XPCE) event dispatching, timeout handling and
|
|
multi-threaded signal and timeout handling.
|
|
|
|
Besides dealing with nonblocking aspects, an important facet of this
|
|
library is to hide OS differences.
|
|
|
|
|
|
API
|
|
---
|
|
|
|
The API is completely the same as for blocking IO. It is however built
|
|
on top of sockets used in non-blocking mode which enables the layer to
|
|
listen to Prolog events such as timeouts, GUI processing and thread
|
|
interaction. The functions are modelled after the POSIX socket API,
|
|
prefixed with nbio_*:
|
|
|
|
nbio_socket()
|
|
nbio_connect()
|
|
nbio_bind()
|
|
nbio_listen()
|
|
nbio_accept()
|
|
nbio_closesocket()
|
|
|
|
and IO is realised using
|
|
|
|
nbio_read() See also below
|
|
nbio_write()
|
|
|
|
Overall control of the library:
|
|
|
|
nbio_init()
|
|
nbio_cleanup()
|
|
nbio_debug()
|
|
|
|
Error handling
|
|
|
|
nbio_error() Raises a Prolog exception
|
|
|
|
Settings
|
|
|
|
nbio_setopt()
|
|
nbio_get_flags()
|
|
|
|
Address Converstion
|
|
|
|
nbio_get_sockaddr()
|
|
nbio_get_ip4()
|
|
|
|
Waiting
|
|
|
|
nbio_select()
|
|
|
|
Alternative to nbio_read() and nbio_write(), the application program may
|
|
call the low-level I/O routines in non-blocking mode and call
|
|
nbio_wait(int socket, nbio_request request). This function returns 0 if
|
|
it thinks the call might now succeed and -1 if an error occurred,
|
|
leaving the exception context in Prolog. On receiving -1, the user must
|
|
return an I/O error as soon as possible.
|
|
|
|
|
|
Windows issues
|
|
--------------
|
|
|
|
Winsock is hard to handle in blocking mode without blocking the whole
|
|
lot, notably (timeout) signals. We therefore have a seperate thread
|
|
dealing with I/O and operating the sockets through WSAAsyncSelect()
|
|
generated events. Requests are registered with the plsocket structure,
|
|
handled and handled in the socket thread. Upon completion, a message is
|
|
sent back to the waiting thread.
|
|
|
|
Unix issues
|
|
-----------
|
|
|
|
In the Unix version we simply call PL_dispatch() before doing recv() and
|
|
leave the details to this function.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef __CYGWIN__
|
|
#undef HAVE_H_ERRNO
|
|
#endif
|
|
|
|
#include "nonblockio.h"
|
|
|
|
#include <SWI-Stream.h>
|
|
#include "clib.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#ifdef __WINDOWS__
|
|
#include <malloc.h>
|
|
#endif
|
|
|
|
|
|
#ifndef __WINDOWS__
|
|
#define closesocket(n) close((n)) /* same on Unix */
|
|
#endif
|
|
|
|
#ifndef SD_SEND
|
|
#define SD_RECEIVE 0 /* shutdown() parameters */
|
|
#define SD_SEND 1
|
|
#define SD_BOTH 2
|
|
#endif
|
|
|
|
#ifndef SOCKET_ERROR
|
|
#define SOCKET_ERROR (-1)
|
|
#endif
|
|
|
|
#ifdef _REENTRANT
|
|
#if __WINDOWS__
|
|
static CRITICAL_SECTION mutex;
|
|
static CRITICAL_SECTION mutex_free;
|
|
|
|
#define LOCK() EnterCriticalSection(&mutex)
|
|
#define UNLOCK() LeaveCriticalSection(&mutex)
|
|
#define LOCK_FREE() EnterCriticalSection(&mutex_free)
|
|
#define UNLOCK_FREE() LeaveCriticalSection(&mutex_free)
|
|
#define INITLOCK() (InitializeCriticalSection(&mutex), \
|
|
InitializeCriticalSection(&mutex_free))
|
|
#else
|
|
#include <pthread.h>
|
|
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
#define LOCK() pthread_mutex_lock(&mutex)
|
|
#define UNLOCK() pthread_mutex_unlock(&mutex)
|
|
#define LOCK_FREE()
|
|
#define UNLOCK_FREE()
|
|
#define INITLOCK()
|
|
#endif
|
|
#else
|
|
#define LOCK()
|
|
#define UNLOCK()
|
|
#define LOCK_FREE()
|
|
#define UNLOCK_FREE()
|
|
#define INITLOCK()
|
|
#endif
|
|
|
|
#define set(s, f) ((s)->flags |= (f))
|
|
#define clear(s, f) ((s)->flags &= ~(f))
|
|
#define true(s, f) ((s)->flags & (f))
|
|
#define false(s, f) (!true(s, f))
|
|
|
|
#define PLSOCK_MAGIC 0x38da3f2c
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
NOTE: We must lock the structure to avoid freeSocket() called from
|
|
Prolog deleting the socket while there are still pending events on it
|
|
that are concurrently executed in the socket thread.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
typedef struct _plsocket
|
|
{ int magic; /* PLSOCK_MAGIC */
|
|
nbio_sock_t id; /* Integer id */
|
|
SOCKET socket; /* The OS socket */
|
|
int flags; /* Misc flags */
|
|
IOSTREAM * input; /* input stream */
|
|
IOSTREAM * output; /* output stream */
|
|
#ifdef __WINDOWS__
|
|
nbio_request request; /* our request */
|
|
DWORD thread; /* waiting thread */
|
|
DWORD error; /* error while executing request */
|
|
int done; /* request completed */
|
|
int w32_flags; /* or of received FD_* */
|
|
union
|
|
{ struct
|
|
{ struct sockaddr_in addr; /* accepted address */
|
|
int addrlen; /* address length */
|
|
nbio_sock_t slave; /* descriptor of slave */
|
|
} accept;
|
|
struct
|
|
{ struct sockaddr_in addr; /* accepted address */
|
|
size_t addrlen; /* address length */
|
|
} connect;
|
|
struct
|
|
{ int bytes; /* byte count */
|
|
char *buffer; /* the buffer */
|
|
size_t size; /* buffer size */
|
|
} read;
|
|
struct
|
|
{ int bytes; /* byte count */
|
|
char *buffer; /* the buffer */
|
|
size_t written;
|
|
size_t size; /* buffer size */
|
|
} write;
|
|
struct
|
|
{ int bytes; /* byte count */
|
|
void *buffer; /* the buffer */
|
|
size_t size; /* buffer size */
|
|
int flags;
|
|
struct sockaddr *from;
|
|
socklen_t *fromlen;
|
|
} recvfrom;
|
|
struct
|
|
{ int bytes; /* byte count */
|
|
void *buffer; /* the buffer */
|
|
int size; /* buffer size */
|
|
int flags;
|
|
const struct sockaddr *to;
|
|
int tolen;
|
|
} sendto;
|
|
} rdata;
|
|
#endif
|
|
} plsocket;
|
|
|
|
static plsocket *allocSocket(SOCKET socket);
|
|
#ifdef __WINDOWS__
|
|
static plsocket *lookupOSSocket(SOCKET socket);
|
|
static const char *WinSockError(unsigned long eno);
|
|
#endif
|
|
|
|
static int
|
|
need_retry(int error)
|
|
{ if ( error == EINTR || error == EAGAIN || error == EWOULDBLOCK )
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef O_DEBUG
|
|
static int debugging;
|
|
|
|
NBIO_EXPORT(int)
|
|
nbio_debug(int level)
|
|
{ int old = debugging;
|
|
|
|
if ( level >= 0 ) /* -1 --> return current setting */
|
|
debugging = level;
|
|
|
|
return old;
|
|
}
|
|
|
|
#define DEBUG(l, g) if ( debugging >= l ) g
|
|
#else
|
|
#define DEBUG(l, g) (void)0
|
|
|
|
int
|
|
nbio_debug(int level)
|
|
{ return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
void /* allow debugger breakpoint */
|
|
tcp_debug()
|
|
{ Sdprintf("Trapping debugger\n");
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* COMPATIBILITY *
|
|
*******************************/
|
|
|
|
#ifdef __WINDOWS__
|
|
static UINT WM_SOCKET = WM_APP+20;
|
|
static UINT WM_REQUEST = WM_APP+21;
|
|
static UINT WM_READY = WM_APP+22;
|
|
static UINT WM_DONE = WM_APP+23;
|
|
|
|
#if O_DEBUG
|
|
static char *
|
|
request_name(nbio_request request)
|
|
{ switch(request)
|
|
{ case REQ_NONE: return "req_none";
|
|
case REQ_ACCEPT: return "req_accept";
|
|
case REQ_CONNECT: return "req_connect";
|
|
case REQ_READ: return "req_read";
|
|
case REQ_WRITE: return "req_write";
|
|
case REQ_RECVFROM:return "req_recvfrom";
|
|
case REQ_SENDTO: return "req_sendto";
|
|
default: return "req_???";
|
|
}
|
|
}
|
|
|
|
static char *
|
|
event_name(int ev)
|
|
{ char buf[256];
|
|
char *o = buf;
|
|
|
|
o[0] = '\0';
|
|
if ( (ev & FD_READ) ) strcat(o, "|FD_READ");
|
|
if ( (ev & FD_WRITE) ) strcat(o, "|FD_WRITE");
|
|
if ( (ev & FD_ACCEPT) ) strcat(o, "|FD_ACCEPT");
|
|
if ( (ev & FD_CONNECT) ) strcat(o, "|FD_CONNECT");
|
|
if ( (ev & FD_CLOSE) ) strcat(o, "|FD_CLOSE");
|
|
if ( (ev & FD_OOB) ) strcat(o, "|FD_OOB");
|
|
if ( (ev & ~(FD_READ|FD_WRITE|FD_ACCEPT|FD_CONNECT|FD_CLOSE)) )
|
|
strcat(o, "|FD_???");
|
|
|
|
return strdup(buf);
|
|
}
|
|
|
|
#endif
|
|
|
|
#define F_SETFL 0
|
|
#define O_NONBLOCK 0
|
|
|
|
static int
|
|
nbio_fcntl(nbio_sock_t socket, int op, int arg)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s=nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
switch(op)
|
|
{ case F_SETFL:
|
|
switch(arg)
|
|
{ case O_NONBLOCK:
|
|
{ int rval;
|
|
int non_block;
|
|
|
|
non_block = 1;
|
|
rval = ioctlsocket(s->socket, FIONBIO, &non_block);
|
|
if ( rval )
|
|
{ s->flags |= PLSOCK_NONBLOCK;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
default:
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static HINSTANCE hinstance; /* hinstance */
|
|
|
|
typedef struct
|
|
{ HWND hwnd; /* our window */
|
|
DWORD tid; /* thread id */
|
|
} local_state;
|
|
|
|
static local_state nbio_state;
|
|
#define State() (&nbio_state)
|
|
|
|
static int
|
|
doneRequest(plsocket *s)
|
|
{ SOCKET sock;
|
|
|
|
s->done = TRUE;
|
|
s->request = REQ_NONE;
|
|
|
|
if ( (s->w32_flags & FD_CLOSE) && (sock=s->socket) >= 0 )
|
|
{ s->socket = -1;
|
|
closesocket(sock);
|
|
}
|
|
|
|
if ( s->thread )
|
|
{ DEBUG(2, Sdprintf("doneRequest(): posting %d\n", s->thread));
|
|
PostThreadMessage(s->thread, WM_DONE, 0, (WPARAM)s);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static int
|
|
waitRequest(plsocket *s)
|
|
{ assert(s->magic == PLSOCK_MAGIC);
|
|
|
|
DEBUG(2, Sdprintf("[%d] (%ld): Waiting for %s on %d ...",
|
|
PL_thread_self(), s->thread,
|
|
request_name(s->request), (int)s->socket));
|
|
|
|
for(;;)
|
|
{ MSG msg;
|
|
|
|
if ( PL_handle_signals() < 0 )
|
|
{ DEBUG(1, Sdprintf("[%d]: Exception\n", PL_thread_self()));
|
|
return FALSE;
|
|
}
|
|
if ( s->done )
|
|
{ DEBUG(2, Sdprintf("[%d]: Done\n", PL_thread_self()));
|
|
return TRUE;
|
|
}
|
|
|
|
if ( false(s, PLSOCK_DISPATCH) )
|
|
{ if ( !GetMessage(&msg, NULL, WM_DONE, WM_DONE) )
|
|
return FALSE;
|
|
} else if ( GetMessage(&msg, NULL, 0, 0) )
|
|
{ TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
} else
|
|
{ ExitThread(0); /* WM_QUIT received */
|
|
return FALSE; /* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
nbio_wait(nbio_sock_t socket, nbio_request request)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s=nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
s->flags |= PLSOCK_WAITING;
|
|
s->done = FALSE;
|
|
s->error = 0;
|
|
s->thread = GetCurrentThreadId();
|
|
s->request = request;
|
|
|
|
SendMessage(State()->hwnd, WM_REQUEST, 1, (LPARAM)&s);
|
|
|
|
DEBUG(2, Sdprintf("[%d] (%ld): Waiting ...",
|
|
PL_thread_self(), s->thread));
|
|
|
|
for(;;)
|
|
{ MSG msg;
|
|
|
|
if ( PL_handle_signals() < 0 )
|
|
{ DEBUG(1, Sdprintf("[%d]: Exception\n", PL_thread_self()));
|
|
return -1;
|
|
}
|
|
if ( s->done )
|
|
{ DEBUG(2, Sdprintf("[%d]: Done\n", PL_thread_self()));
|
|
return 0;
|
|
}
|
|
|
|
if ( false(s, PLSOCK_DISPATCH) )
|
|
{ if ( !GetMessage(&msg, NULL, WM_DONE, WM_DONE) )
|
|
return FALSE;
|
|
} else if ( GetMessage(&msg, NULL, 0, 0) )
|
|
{ TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
} else
|
|
{ ExitThread(0); /* WM_QUIT received */
|
|
return -1; /* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
nbio_select() selects using a set of socket streams.
|
|
|
|
NOTE: The Windows versions uses our nbio_sock_t abstraction, while the
|
|
other version uses the raw Unix file descriptors referencing the
|
|
underlying socket.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
int
|
|
nbio_select(int n,
|
|
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|
struct timeval *timeout)
|
|
{ plsocket **sockets = alloca(n * sizeof(plsocket*));
|
|
int i;
|
|
DWORD t_end;
|
|
|
|
if ( !sockets )
|
|
{ errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
sockets[i] = NULL;
|
|
|
|
if ( readfds )
|
|
{ for(i=0; i<n; i++)
|
|
{ if ( FD_ISSET(i, readfds) )
|
|
{ plsocket *s = nbio_to_plsocket(i);
|
|
|
|
if ( s )
|
|
{ s->flags |= PLSOCK_WAITING;
|
|
s->done = FALSE;
|
|
s->error = 0;
|
|
s->thread = GetCurrentThreadId();
|
|
s->request = (s->flags & PLSOCK_LISTEN) ? REQ_ACCEPT : REQ_READ;
|
|
sockets[i] = s;
|
|
} else
|
|
{ DEBUG(2, Sdprintf("nbio_select(): no socket for %d\n", i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( writefds )
|
|
return -1; /* not yet implemented */
|
|
if ( exceptfds )
|
|
return -1; /* idem (might never be) */
|
|
|
|
if ( timeout )
|
|
{ t_end = GetTickCount();
|
|
t_end += timeout->tv_sec*1000;
|
|
t_end += timeout->tv_usec/1000;
|
|
}
|
|
|
|
FD_ZERO(readfds);
|
|
SendMessage(State()->hwnd, WM_REQUEST, n, (LPARAM)sockets);
|
|
|
|
for(;;)
|
|
{ MSG msg;
|
|
plsocket **s;
|
|
int ready;
|
|
|
|
if ( PL_handle_signals() < 0 )
|
|
{ DEBUG(1, Sdprintf("[%d]: Exception\n", PL_thread_self()));
|
|
return -1;
|
|
}
|
|
|
|
for(ready=0, i=0, s=sockets; i<n; i++, s++)
|
|
{ if ( *s && (*s)->done )
|
|
{ ready++;
|
|
FD_SET((unsigned)i, readfds);
|
|
}
|
|
}
|
|
if ( ready > 0 )
|
|
return ready;
|
|
|
|
if ( timeout )
|
|
{ DWORD rc;
|
|
DWORD t = GetTickCount();
|
|
long msec = t_end - t;
|
|
|
|
if ( msec < 0 )
|
|
msec = -msec; /* wrapped around */
|
|
|
|
rc = MsgWaitForMultipleObjects(0, NULL, FALSE, msec, QS_ALLINPUT);
|
|
if ( rc == WAIT_OBJECT_0 )
|
|
{ if ( GetMessage(&msg, NULL, 0, 0) )
|
|
{ TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
} else
|
|
{ ExitThread(0); /* WM_QUIT received */
|
|
return -1; /* NOTREACHED */
|
|
}
|
|
} else if ( rc == WAIT_TIMEOUT )
|
|
{ return 0;
|
|
} else
|
|
{ assert(0);
|
|
}
|
|
} else
|
|
{ if ( GetMessage(&msg, NULL, 0, 0) )
|
|
{ TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
} else
|
|
{ ExitThread(0); /* WM_QUIT received */
|
|
return -1; /* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
placeRequest(plsocket *s, nbio_request request)
|
|
{ if ( s->magic != PLSOCK_MAGIC )
|
|
Sdprintf("placeRequest: %p has bad magic\n", s);
|
|
|
|
s->error = 0;
|
|
s->done = FALSE;
|
|
s->thread = GetCurrentThreadId();
|
|
s->request = request;
|
|
clear(s, PLSOCK_WAITING);
|
|
|
|
SendMessage(State()->hwnd, WM_REQUEST, 1, (LPARAM)&s);
|
|
DEBUG(2, Sdprintf("%d (%ld): Placed %s request for %d\n",
|
|
PL_thread_self(), s->thread,
|
|
request_name(request), (int)s->socket));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
doRequest(plsocket *s)
|
|
{ if ( s->magic != PLSOCK_MAGIC )
|
|
Sdprintf("doRequest: %p has bad magic\n", s);
|
|
|
|
switch(s->request)
|
|
{ case REQ_NONE:
|
|
break;
|
|
case REQ_CONNECT:
|
|
if ( s->w32_flags & FD_CONNECT )
|
|
{ s->w32_flags &= ~FD_CONNECT;
|
|
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
if ( connect(s->socket,
|
|
(struct sockaddr*)&s->rdata.connect.addr,
|
|
(int)s->rdata.connect.addrlen) )
|
|
{ s->error = WSAGetLastError();
|
|
|
|
switch(s->error)
|
|
{ case WSAEWOULDBLOCK:
|
|
case WSAEINVAL:
|
|
case WSAEALREADY:
|
|
break;
|
|
case WSAEISCONN:
|
|
s->error = 0;
|
|
doneRequest(s);
|
|
break;
|
|
default:
|
|
doneRequest(s);
|
|
}
|
|
} else
|
|
{ s->error = 0;
|
|
doneRequest(s);
|
|
}
|
|
}
|
|
break;
|
|
case REQ_ACCEPT:
|
|
if ( s->w32_flags & FD_ACCEPT )
|
|
{ SOCKET slave;
|
|
|
|
s->w32_flags &= ~FD_ACCEPT;
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
slave = accept(s->socket,
|
|
(struct sockaddr*)&s->rdata.accept.addr,
|
|
&s->rdata.accept.addrlen);
|
|
|
|
if ( slave == SOCKET_ERROR )
|
|
{ s->error = WSAGetLastError();
|
|
|
|
DEBUG(2, Sdprintf("Accept(%d): %s\n",
|
|
(int)s->socket, WinSockError(s->error)));
|
|
|
|
if ( s->error != WSAEWOULDBLOCK )
|
|
{ s->rdata.accept.slave = (SOCKET)-1;
|
|
doneRequest(s);
|
|
}
|
|
} else
|
|
{ plsocket *pls;
|
|
|
|
DEBUG(2, Sdprintf("Accept(%d) --> %d\n",
|
|
(int)s->socket, (int)slave));
|
|
if ( (pls = allocSocket(slave)) )
|
|
{ pls->flags |= PLSOCK_ACCEPT; /* requests */
|
|
|
|
s->rdata.accept.slave = pls->id;
|
|
s->error = 0;
|
|
doneRequest(s);
|
|
} else
|
|
{ DEBUG(1, Sdprintf("Socket %d already registered; "
|
|
"considering bogus\n", (int)slave));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case REQ_READ:
|
|
if ( s->w32_flags & (FD_READ|FD_CLOSE) )
|
|
{ s->w32_flags &= ~FD_READ;
|
|
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
s->rdata.read.bytes = recv(s->socket,
|
|
s->rdata.read.buffer,
|
|
(int)s->rdata.read.size,
|
|
0);
|
|
if ( s->rdata.read.bytes < 0 )
|
|
{ s->error = WSAGetLastError();
|
|
|
|
if ( s->error != WSAEWOULDBLOCK )
|
|
{ DEBUG(1, Sdprintf("Error reading from %d: %s\n",
|
|
s->socket, WinSockError(s->error)));
|
|
doneRequest(s);
|
|
}
|
|
} else
|
|
{ doneRequest(s);
|
|
}
|
|
}
|
|
break;
|
|
case REQ_RECVFROM:
|
|
if ( s->w32_flags & (FD_READ|FD_CLOSE) )
|
|
{ int iflen = (int)*s->rdata.recvfrom.fromlen;
|
|
|
|
s->w32_flags &= ~FD_READ;
|
|
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
s->rdata.recvfrom.bytes =
|
|
recvfrom(s->socket,
|
|
s->rdata.recvfrom.buffer,
|
|
(int)s->rdata.recvfrom.size,
|
|
s->rdata.recvfrom.flags,
|
|
s->rdata.recvfrom.from,
|
|
&iflen);
|
|
|
|
if ( s->rdata.recvfrom.bytes < 0 )
|
|
{ s->error = WSAGetLastError();
|
|
|
|
if ( s->error != WSAEWOULDBLOCK )
|
|
{ DEBUG(1, Sdprintf("Error recvfrom from %d: %s\n",
|
|
s->socket, WinSockError(s->error)));
|
|
doneRequest(s);
|
|
}
|
|
} else
|
|
{ *s->rdata.recvfrom.fromlen = iflen;
|
|
doneRequest(s);
|
|
}
|
|
}
|
|
break;
|
|
case REQ_WRITE:
|
|
if ( s->w32_flags & FD_WRITE )
|
|
{ int n;
|
|
int len = (int)(s->rdata.write.size - s->rdata.write.written);
|
|
|
|
s->w32_flags &= ~FD_WRITE;
|
|
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
DEBUG(2, Sdprintf("send() %d bytes\n", s->rdata.write.size));
|
|
n = send(s->socket,
|
|
s->rdata.write.buffer + s->rdata.write.written,
|
|
len, 0);
|
|
DEBUG(2, Sdprintf("Wrote %d bytes\n", n));
|
|
if ( n < 0 )
|
|
{ s->error = WSAGetLastError();
|
|
if ( s->error == WSAEWOULDBLOCK )
|
|
break;
|
|
s->rdata.write.bytes = n;
|
|
DEBUG(1, Sdprintf("[%d]: send(%d, %d bytes): %s\n",
|
|
PL_thread_self(), s->socket, len,
|
|
WinSockError(s->error)));
|
|
doneRequest(s);
|
|
} else
|
|
s->error = 0;
|
|
|
|
s->rdata.write.written += n;
|
|
if ( s->rdata.write.written >= s->rdata.write.size )
|
|
{ s->rdata.write.bytes = (int)s->rdata.write.written;
|
|
doneRequest(s);
|
|
}
|
|
}
|
|
case REQ_SENDTO:
|
|
if ( s->w32_flags & FD_WRITE )
|
|
{ int n;
|
|
|
|
s->w32_flags &= ~FD_WRITE;
|
|
|
|
if ( true(s, PLSOCK_WAITING) )
|
|
{ doneRequest(s);
|
|
break;
|
|
}
|
|
|
|
DEBUG(2, Sdprintf("sendto() %d bytes\n", s->rdata.write.size));
|
|
n = sendto(s->socket,
|
|
s->rdata.sendto.buffer,
|
|
s->rdata.sendto.size,
|
|
s->rdata.sendto.flags,
|
|
s->rdata.sendto.to,
|
|
s->rdata.sendto.tolen);
|
|
DEBUG(2, Sdprintf("Wrote %d bytes\n", n));
|
|
if ( n < 0 )
|
|
{ s->error = WSAGetLastError();
|
|
s->rdata.write.bytes = n;
|
|
DEBUG(1, Sdprintf("[%d]: send(%d, %d bytes): %s\n",
|
|
PL_thread_self(), s->socket,
|
|
s->rdata.sendto.size,
|
|
WinSockError(s->error)));
|
|
doneRequest(s);
|
|
} else
|
|
s->error = 0;
|
|
|
|
s->rdata.sendto.bytes = n;
|
|
doneRequest(s);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static LRESULT WINAPI
|
|
socket_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{ if ( message == WM_REQUEST )
|
|
{ plsocket **s = (plsocket **)lParam;
|
|
int i, n = (int)wParam;
|
|
|
|
for(i=0; i<n; i++)
|
|
{ if ( s[i] )
|
|
{
|
|
#if __MINGW32__
|
|
{ if ( s[i]->magic != PLSOCK_MAGIC )
|
|
{ goto nosocket;
|
|
}
|
|
}
|
|
#else
|
|
__try
|
|
{ if ( s[i]->magic != PLSOCK_MAGIC )
|
|
{ goto nosocket;
|
|
}
|
|
} __except(EXCEPTION_EXECUTE_HANDLER)
|
|
{ goto nosocket;
|
|
}
|
|
#endif
|
|
|
|
doRequest(s[i]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
nosocket:
|
|
Sdprintf("%p@%d is not a socket!?\n", s[i], i);
|
|
return 0;
|
|
} else if ( message == WM_SOCKET )
|
|
{ SOCKET sock = (SOCKET) wParam;
|
|
int err = WSAGETSELECTERROR(lParam);
|
|
int evt = WSAGETSELECTEVENT(lParam);
|
|
plsocket *s;
|
|
|
|
if ( evt&FD_CLOSE )
|
|
{ DEBUG(1,
|
|
{ char *nm = event_name(evt);
|
|
Sdprintf("WM_SOCKET on %d: ev=(%s); err=%s\n",
|
|
(int)sock, nm, err ? WinSockError(err) : "none");
|
|
free(nm);
|
|
});
|
|
} else
|
|
{ DEBUG(3,
|
|
{ char *nm = event_name(evt);
|
|
Sdprintf("WM_SOCKET on %d: ev=(%s); err=%s\n",
|
|
(int)sock, nm, err ? WinSockError(err) : "none");
|
|
free(nm);
|
|
});
|
|
}
|
|
|
|
LOCK_FREE();
|
|
s = lookupOSSocket(sock);
|
|
|
|
if ( s )
|
|
{ if ( (s->w32_flags & FD_CLOSE) )
|
|
{ DEBUG(1,
|
|
{ char *nm = event_name(evt);
|
|
Sdprintf("Got event %s (err=%s) on closed socket %d=%d\n",
|
|
nm, WinSockError(err), s->id, sock);
|
|
free(nm);
|
|
})
|
|
}
|
|
|
|
s->w32_flags |= evt;
|
|
if ( err == WSAECONNABORTED && s->request == REQ_READ )
|
|
{ s->rdata.read.bytes = 0;
|
|
doneRequest(s);
|
|
} else if ( err )
|
|
{ SOCKET sock = s->socket;
|
|
|
|
s->error = err;
|
|
if ( sock )
|
|
{ s->socket = -1;
|
|
closesocket(sock);
|
|
}
|
|
|
|
switch(s->request)
|
|
{ case REQ_CONNECT:
|
|
break;
|
|
case REQ_ACCEPT:
|
|
s->rdata.accept.slave = SOCKET_ERROR;
|
|
break;
|
|
case REQ_READ:
|
|
s->rdata.read.bytes = -1;
|
|
break;
|
|
case REQ_RECVFROM:
|
|
s->rdata.recvfrom.bytes = -1;
|
|
break;
|
|
case REQ_WRITE:
|
|
s->rdata.write.bytes = -1;
|
|
break;
|
|
case REQ_SENDTO:
|
|
s->rdata.sendto.bytes = -1;
|
|
break;
|
|
}
|
|
doneRequest(s);
|
|
} else if ( s->socket >= 0 )
|
|
{ doRequest(s);
|
|
} else
|
|
{ doneRequest(s);
|
|
}
|
|
} else
|
|
{ DEBUG(1, Sdprintf("Socket %d is gone (error=%s)\n",
|
|
sock, WinSockError(err)));
|
|
}
|
|
|
|
UNLOCK_FREE();
|
|
}
|
|
|
|
return DefWindowProc(hwnd, message, wParam, lParam);
|
|
}
|
|
|
|
static char *
|
|
HiddenFrameClass()
|
|
{ static char *name;
|
|
static WNDCLASS wndClass;
|
|
|
|
if ( !name )
|
|
{ char buf[50];
|
|
|
|
sprintf(buf, "PlSocketWin%d", (int)hinstance);
|
|
name = strdup(buf);
|
|
|
|
wndClass.style = 0;
|
|
wndClass.lpfnWndProc = (LPVOID) socket_wnd_proc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = 0;
|
|
wndClass.hInstance = hinstance;
|
|
wndClass.hIcon = NULL;
|
|
wndClass.hCursor = NULL;
|
|
wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
|
|
wndClass.lpszMenuName = NULL;
|
|
wndClass.lpszClassName = name;
|
|
|
|
RegisterClass(&wndClass);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
static void
|
|
destroyHiddenWindow(int rval, void *closure)
|
|
{ local_state *s = State();
|
|
|
|
if ( s->hwnd )
|
|
{ DestroyWindow(s->hwnd);
|
|
s->hwnd = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static HWND
|
|
SocketHiddenWindow()
|
|
{ local_state *s = State();
|
|
|
|
if ( !s->hwnd )
|
|
{ s->hwnd = CreateWindow(HiddenFrameClass(),
|
|
"SWI-Prolog socket window",
|
|
WS_POPUP,
|
|
0, 0, 32, 32,
|
|
NULL, NULL, hinstance, NULL);
|
|
assert(s->hwnd);
|
|
DEBUG(1, Sdprintf("%d created hidden window %p\n",
|
|
PL_thread_self(), s->hwnd));
|
|
}
|
|
|
|
return s->hwnd;
|
|
}
|
|
|
|
|
|
DWORD WINAPI
|
|
socket_thread(LPVOID arg)
|
|
{ DWORD parent = (DWORD)arg;
|
|
|
|
SocketHiddenWindow();
|
|
PostThreadMessage(parent, WM_READY, (WPARAM)0, (LPARAM)0);
|
|
|
|
for(;;)
|
|
{ MSG msg;
|
|
|
|
if ( GetMessage(&msg, NULL, 0, 0) )
|
|
{ TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
startSocketThread()
|
|
{ DWORD me = GetCurrentThreadId();
|
|
MSG msg;
|
|
|
|
CreateThread(NULL, /* security */
|
|
2048, /* stack */
|
|
socket_thread, (LPVOID)me, /* proc+arg */
|
|
0, /* flags */
|
|
&State()->tid);
|
|
|
|
GetMessage(&msg, NULL, WM_READY, WM_READY);
|
|
DEBUG(1, Sdprintf("Socket thread started\n"));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
wait_socket() is the Unix way to wait for input on the socket. By
|
|
default event-dispatching on behalf of XPCE is performed. If this is not
|
|
desired, you can use tcp_setopt(Socket, dispatch(false)), in which case
|
|
this call returns immediately, assuming the actual TCP call will block
|
|
without dispatching if no input is available.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
static int
|
|
wait_socket(plsocket *s)
|
|
{ if ( true(s, PLSOCK_DISPATCH) )
|
|
{ int fd = s->socket;
|
|
|
|
if ( true(s, PLSOCK_NONBLOCK) && !PL_dispatch(fd, PL_DISPATCH_INSTALLED) )
|
|
{ fd_set rfds;
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 250000;
|
|
|
|
select(fd+1, &rfds, NULL, NULL, &tv);
|
|
return TRUE;
|
|
} else
|
|
{ return PL_dispatch(fd, PL_DISPATCH_WAIT);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_wait(nbio_sock_t socket, nbio_request request)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s=nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
return wait_socket(s) ? 0 : -1;
|
|
}
|
|
|
|
|
|
static int
|
|
nbio_fcntl(nbio_sock_t socket, int op, int arg)
|
|
{ plsocket *s;
|
|
int rc;
|
|
|
|
if ( !(s=nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
rc = fcntl(s->socket, op, arg);
|
|
|
|
if ( rc == 0 )
|
|
{ if ( op == F_SETFL && arg == O_NONBLOCK )
|
|
s->flags |= PLSOCK_NONBLOCK;
|
|
} else
|
|
nbio_error(errno, TCP_ERRNO);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
NOTE: when called througb wait_for_input/3, the descriptors in the sets
|
|
are real underlying Unix socket descriptors and *not* the nbio_sock_t
|
|
psuedo descriptors.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
int
|
|
nbio_select(int n,
|
|
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|
struct timeval *timeout)
|
|
{ return select(n, readfds, writefds, exceptfds, timeout);
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
|
|
/*******************************
|
|
* ADMINISTRATION *
|
|
*******************************/
|
|
|
|
static functor_t FUNCTOR_module2;
|
|
static functor_t FUNCTOR_ip4;
|
|
static functor_t FUNCTOR_ip1;
|
|
static atom_t ATOM_any;
|
|
static atom_t ATOM_broadcast;
|
|
static atom_t ATOM_loopback;
|
|
|
|
static plsocket **sockets = NULL; /* id --> plsocket* */
|
|
static size_t socks_count = 0; /* #registered sockets */
|
|
static size_t socks_allocated = 0; /* #allocated entries */
|
|
static int initialised = FALSE; /* Windows only */
|
|
|
|
#ifdef __WINDOWS__
|
|
|
|
static plsocket *
|
|
lookupOSSocket(SOCKET socket)
|
|
{ plsocket *p;
|
|
size_t i;
|
|
|
|
LOCK();
|
|
for(i=0; i<socks_allocated; i++)
|
|
{ if ( (p=sockets[i]) && p->socket == socket )
|
|
{ UNLOCK();
|
|
|
|
if ( p->magic != PLSOCK_MAGIC )
|
|
{ errno = EINVAL;
|
|
DEBUG(1, Sdprintf("Invalid OS socket: %d\n", socket));
|
|
return NULL;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
}
|
|
UNLOCK();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
|
|
static plsocket *
|
|
nbio_to_plsocket_nolock(nbio_sock_t socket)
|
|
{ plsocket *p;
|
|
|
|
if ( socket < 0 || (size_t)socket >= socks_allocated )
|
|
{ errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
p = sockets[socket];
|
|
|
|
if ( !p || p->magic != PLSOCK_MAGIC )
|
|
{ DEBUG(1, Sdprintf("Invalid NBIO socket: %d\n", socket));
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
SOCKET
|
|
plsocket_handle(plsocket_ptr pls)
|
|
{ return pls->socket;
|
|
}
|
|
|
|
|
|
static plsocket *
|
|
nbio_to_plsocket_raw(nbio_sock_t socket)
|
|
{ plsocket *s;
|
|
|
|
LOCK();
|
|
s = nbio_to_plsocket_nolock(socket);
|
|
UNLOCK();
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
plsocket *
|
|
nbio_to_plsocket(nbio_sock_t socket)
|
|
{ plsocket *p;
|
|
|
|
if ( !(p=nbio_to_plsocket_raw(socket)) )
|
|
return NULL;
|
|
|
|
#ifdef __WINDOWS__
|
|
if ( p->socket < 0 )
|
|
{ p->error = WSAECONNRESET;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
NBIO_EXPORT(SOCKET)
|
|
nbio_fd(nbio_sock_t socket)
|
|
{ plsocket *p;
|
|
|
|
if ( !(p=nbio_to_plsocket_nolock(socket)) )
|
|
return -1;
|
|
|
|
return p->socket;
|
|
}
|
|
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
Allocate a wrapper for an OS socket. The wrapper is allocated in an
|
|
array of pointers, to keep small integer identifiers we can use with
|
|
FD_SET, etc. for implementing a compatible nbio_select().
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
static plsocket *
|
|
allocSocket(SOCKET socket)
|
|
{ plsocket *p = NULL;
|
|
size_t i;
|
|
|
|
#ifdef __WINDOWS__
|
|
if ( (p=lookupOSSocket(socket)) )
|
|
{ DEBUG(1, Sdprintf("WinSock %d already registered on %d\n",
|
|
(int)socket, p->id));
|
|
p->socket = (SOCKET)-1;
|
|
}
|
|
#endif
|
|
|
|
LOCK();
|
|
if ( socks_count+1 > socks_allocated )
|
|
{ if ( socks_allocated )
|
|
{ size_t newa = socks_allocated*2;
|
|
|
|
sockets = PL_realloc(sockets, sizeof(plsocket*)*newa);
|
|
for(i=socks_allocated; i<newa; i++)
|
|
sockets[i] = NULL;
|
|
socks_allocated = newa;
|
|
} else
|
|
{ socks_allocated = 32;
|
|
sockets = PL_malloc(sizeof(plsocket*)*socks_allocated);
|
|
memset(sockets, 0, sizeof(plsocket*)*socks_allocated);
|
|
}
|
|
}
|
|
|
|
for(i=0; i<socks_allocated; i++)
|
|
{ if ( sockets[i] == NULL )
|
|
{ sockets[i] = p = PL_malloc(sizeof(*p));
|
|
socks_count++;
|
|
break;
|
|
}
|
|
}
|
|
UNLOCK();
|
|
|
|
assert(i<socks_allocated);
|
|
|
|
memset(p, 0, sizeof(*p));
|
|
p->id = (int)i; /* place in the array */
|
|
p->socket = socket;
|
|
p->flags = PLSOCK_DISPATCH; /* by default, dispatch */
|
|
p->magic = PLSOCK_MAGIC;
|
|
#ifdef __WINDOWS__
|
|
p->w32_flags = 0;
|
|
p->request = REQ_NONE;
|
|
#endif
|
|
p->input = p->output = (IOSTREAM*)NULL;
|
|
|
|
DEBUG(2, Sdprintf("[%d]: allocSocket(%d): bound to %p\n",
|
|
PL_thread_self(), socket, p));
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static int
|
|
freeSocket(plsocket *s)
|
|
{ int rval;
|
|
nbio_sock_t socket;
|
|
SOCKET sock;
|
|
|
|
DEBUG(2, Sdprintf("Closing %d\n", s->id));
|
|
if ( !s || s->magic != PLSOCK_MAGIC )
|
|
{ errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
LOCK_FREE();
|
|
LOCK();
|
|
sockets[s->id] = NULL;
|
|
socks_count--;
|
|
UNLOCK();
|
|
|
|
sock = s->socket;
|
|
socket = s->id;
|
|
s->magic = 0;
|
|
PL_free(s);
|
|
UNLOCK_FREE();
|
|
|
|
if ( sock >= 0 )
|
|
{ again:
|
|
if ( (rval=closesocket(sock)) == SOCKET_ERROR )
|
|
{ if ( errno == EINTR )
|
|
goto again;
|
|
}
|
|
DEBUG(2, Sdprintf("freeSocket(%d=%d) returned %d\n",
|
|
socket, (int)sock, rval));
|
|
} else
|
|
{ rval = 0; /* already closed. Use s->error? */
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* ERRORS *
|
|
*******************************/
|
|
|
|
#ifdef __WINDOWS__
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
The code in BILLY_GETS_BETTER is, according to various documents the
|
|
right code, but it doesn't work, so we do it by hand.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#ifdef BILLY_GETS_BETTER
|
|
|
|
static const char *
|
|
WinSockError(unsigned long eno)
|
|
{ char buf[1024];
|
|
static HMODULE netmsg = 0;
|
|
static int netmsg_loaded = FALSE;
|
|
unsigned long flags = (FORMAT_MESSAGE_FROM_SYSTEM|
|
|
FORMAT_MESSAGE_IGNORE_INSERTS);
|
|
|
|
if ( !netmsg_loaded )
|
|
{ netmsg_loaded = TRUE;
|
|
netmsg = LoadLibraryEx("netmsg.dll", 0, LOAD_LIBRARY_AS_DATAFILE);
|
|
if ( !netmsg )
|
|
Sdprintf("failed to load netmsg.dll\n");
|
|
else
|
|
Sdprintf("Loaded netmsg.dll as %p\n", netmsg);
|
|
}
|
|
|
|
if ( netmsg )
|
|
flags |= FORMAT_MESSAGE_FROM_HMODULE;
|
|
|
|
if ( !FormatMessage(flags,
|
|
netmsg,
|
|
eno,
|
|
GetUserDefaultLangID(),
|
|
/*MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),*/
|
|
buf, sizeof(buf),
|
|
0))
|
|
{ sprintf(buf, "Unknown socket error (%u)", eno);
|
|
}
|
|
|
|
buf[sizeof(buf)-1]='\0';
|
|
|
|
return strdup(buf);
|
|
}
|
|
|
|
#else /*BILLY_GETS_BETTER*/
|
|
|
|
static const char *
|
|
WinSockError(unsigned long error)
|
|
{ struct
|
|
{ int index;
|
|
const char *string;
|
|
} *ep, edefs[] =
|
|
{ { WSAEACCES, "Permission denied" },
|
|
{ WSAEADDRINUSE, "Address already in use" },
|
|
{ WSAEADDRNOTAVAIL, "Cannot assign requested address" },
|
|
{ WSAEAFNOSUPPORT, "Address family not supported by protocol family" },
|
|
{ WSAEALREADY, "Operation already in progress" },
|
|
{ WSAECONNABORTED, "Software caused connection abort" },
|
|
{ WSAECONNREFUSED, "Connection refused" },
|
|
{ WSAECONNRESET, "Connection reset by peer" },
|
|
{ WSAEDESTADDRREQ, "Destination address required" },
|
|
{ WSAEFAULT, "Bad address" },
|
|
{ WSAEHOSTDOWN, "Host is down" },
|
|
{ WSAEHOSTUNREACH, "No route to host" },
|
|
{ WSAEINPROGRESS, "Operation now in progress" },
|
|
{ WSAEINTR, "Interrupted function call" },
|
|
{ WSAEINVAL, "Invalid argument" },
|
|
{ WSAEISCONN, "Socket is already connected" },
|
|
{ WSAEMFILE, "Too many open files" },
|
|
{ WSAEMSGSIZE, "Message too long" },
|
|
{ WSAENETDOWN, "Network is down" },
|
|
{ WSAENETRESET, "Network dropped connection on reset" },
|
|
{ WSAENETUNREACH, "Network is unreachable" },
|
|
{ WSAENOBUFS, "No buffer space available" },
|
|
{ WSAENOPROTOOPT, "Bad protocol option" },
|
|
{ WSAENOTCONN, "Socket is not connected" },
|
|
{ WSAENOTSOCK, "Socket operation on non-socket" },
|
|
{ WSAEOPNOTSUPP, "Operation not supported" },
|
|
{ WSAEPFNOSUPPORT, "Protocol family not supported" },
|
|
{ WSAEPROCLIM, "Too many processes" },
|
|
{ WSAEPROTONOSUPPORT, "Protocol not supported" },
|
|
{ WSAEPROTOTYPE, "Protocol wrong type for socket" },
|
|
{ WSAESHUTDOWN, "Cannot send after socket shutdown" },
|
|
{ WSAESOCKTNOSUPPORT, "Socket type not supported" },
|
|
{ WSAETIMEDOUT, "Connection timed out" },
|
|
{ WSAEWOULDBLOCK, "Resource temporarily unavailable" },
|
|
{ WSAEDISCON, "Graceful shutdown in progress" },
|
|
{ WSANOTINITIALISED, "Socket layer not initialised" },
|
|
/* WinSock 2 errors */
|
|
{ WSAHOST_NOT_FOUND, "Host not found" },
|
|
{ 0, NULL }
|
|
};
|
|
char tmp[100];
|
|
|
|
for(ep=edefs; ep->string; ep++)
|
|
{ if ( ep->index == (int)error )
|
|
return ep->string;
|
|
}
|
|
|
|
sprintf(tmp, "Unknown error %ld", error);
|
|
return strdup(tmp); /* leaks memory on unknown errors */
|
|
}
|
|
|
|
#endif /*BILLY_GETS_BETTER*/
|
|
#endif /*__WINDOWS__*/
|
|
|
|
#ifdef HAVE_H_ERRNO
|
|
typedef struct
|
|
{ int code;
|
|
const char *string;
|
|
} error_codes;
|
|
|
|
static error_codes h_errno_codes[] = {
|
|
#ifdef HOST_NOT_FOUND
|
|
{ HOST_NOT_FOUND, "Host not found" },
|
|
#endif
|
|
#ifdef TRY_AGAIN
|
|
{ TRY_AGAIN, "Try Again" },
|
|
#endif
|
|
#ifdef NO_RECOVERY
|
|
{ NO_RECOVERY, "No Recovery" },
|
|
#endif
|
|
#ifdef NO_DATA
|
|
{ NO_DATA, "No Data" },
|
|
#endif
|
|
#ifdef NO_ADDRESS
|
|
{ NO_ADDRESS, "No Address" },
|
|
#endif
|
|
{0, NULL}
|
|
};
|
|
|
|
#else /*HAVE_H_ERRNO*/
|
|
#define h_errno_codes NULL
|
|
typedef void * error_codes;
|
|
#endif /*HAVE_H_ERRNO*/
|
|
|
|
int
|
|
nbio_error(int code, nbio_error_map mapid)
|
|
{ const char *msg;
|
|
term_t except = PL_new_term_ref();
|
|
error_codes *map;
|
|
|
|
if ( code == EPLEXCEPTION )
|
|
return FALSE;
|
|
|
|
switch( mapid )
|
|
{ case TCP_HERRNO:
|
|
map = h_errno_codes;
|
|
break;
|
|
default:
|
|
map = NULL;
|
|
}
|
|
|
|
{
|
|
#ifdef __WINDOWS__
|
|
msg = WinSockError(code);
|
|
#else
|
|
|
|
#ifdef HAVE_H_ERRNO
|
|
static char msgbuf[100];
|
|
|
|
if ( map )
|
|
{ while( map->code && map->code != code )
|
|
map++;
|
|
if ( map->code )
|
|
msg = map->string;
|
|
else
|
|
{ sprintf(msgbuf, "Unknown error %d", code);
|
|
msg = msgbuf;
|
|
}
|
|
} else
|
|
#endif
|
|
msg = strerror(code);
|
|
#endif /*__WINDOWS__*/
|
|
|
|
if ( !PL_unify_term(except,
|
|
CompoundArg("error", 2),
|
|
CompoundArg("socket_error", 1),
|
|
AtomArg(msg),
|
|
PL_VARIABLE) )
|
|
return FALSE;
|
|
}
|
|
|
|
return PL_raise_exception(except);
|
|
}
|
|
|
|
|
|
const char *
|
|
nbio_last_error(nbio_sock_t socket)
|
|
{
|
|
#ifdef __WINDOWS__
|
|
plsocket *s;
|
|
|
|
if ( !(s=nbio_to_plsocket_raw(socket)) )
|
|
return NULL;
|
|
|
|
if ( s->error )
|
|
return WinSockError(s->error);
|
|
#endif
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* INITIALISATION *
|
|
*******************************/
|
|
|
|
NBIO_EXPORT(int)
|
|
nbio_init(const char *module)
|
|
{ INITLOCK(); /* is this ok? */
|
|
|
|
LOCK();
|
|
if ( initialised )
|
|
{ UNLOCK();
|
|
return TRUE;
|
|
}
|
|
initialised = TRUE;
|
|
|
|
FUNCTOR_module2 = PL_new_functor(PL_new_atom(":"), 2);
|
|
FUNCTOR_ip4 = PL_new_functor(PL_new_atom("ip"), 4);
|
|
FUNCTOR_ip1 = PL_new_functor(PL_new_atom("ip"), 1);
|
|
ATOM_any = PL_new_atom("any");
|
|
ATOM_broadcast = PL_new_atom("broadcast");
|
|
ATOM_loopback = PL_new_atom("loopback");
|
|
|
|
#ifdef __WINDOWS__
|
|
{ WSADATA WSAData;
|
|
|
|
hinstance = GetModuleHandle(module);
|
|
|
|
#if 0
|
|
WM_SOCKET = RegisterWindowMessage("SWI-Prolog:nonblockio:WM_SOCKET");
|
|
WM_REQUEST = RegisterWindowMessage("SWI-Prolog:nonblockio:WM_REQUEST");
|
|
WM_READY = RegisterWindowMessage("SWI-Prolog:nonblockio:WM_READY");
|
|
WM_DONE = RegisterWindowMessage("SWI-Prolog:nonblockio:WM_DONE");
|
|
#endif
|
|
|
|
if ( WSAStartup(MAKEWORD(2,0), &WSAData) )
|
|
{ UNLOCK();
|
|
return PL_warning("nbio_init() - WSAStartup failed.");
|
|
}
|
|
startSocketThread();
|
|
}
|
|
#endif /*__WINDOWS__*/
|
|
|
|
UNLOCK();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
NBIO_EXPORT(int)
|
|
nbio_cleanup(void)
|
|
{ if ( initialised )
|
|
{
|
|
#ifdef __WINDOWS__
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
socket(-Socket)
|
|
Create a stream inet socket. The socket is represented by a term of
|
|
the format $socket(Id).
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
NBIO_EXPORT(nbio_sock_t)
|
|
nbio_socket(int domain, int type, int protocol)
|
|
{ SOCKET sock;
|
|
plsocket *s;
|
|
|
|
assert(initialised);
|
|
|
|
if ( (sock = socket(domain, type , protocol)) < 0)
|
|
{ nbio_error(errno, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
if ( !(s=allocSocket(sock)) ) /* register it */
|
|
{ closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef __WINDOWS__
|
|
WSAAsyncSelect(sock, State()->hwnd, WM_SOCKET,
|
|
FD_READ|FD_WRITE|FD_ACCEPT|FD_CONNECT|FD_CLOSE);
|
|
#endif
|
|
|
|
return s->id;
|
|
}
|
|
|
|
|
|
NBIO_EXPORT(int)
|
|
nbio_closesocket(nbio_sock_t socket)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket_raw(socket)) )
|
|
{ DEBUG(1, Sdprintf("nbio_closesocket(%d): no plsocket\n", socket));
|
|
return -1;
|
|
}
|
|
|
|
if ( true(s, PLSOCK_OUTSTREAM|PLSOCK_INSTREAM) )
|
|
{ int flags = s->flags; /* may drop out! */
|
|
|
|
if ( flags & PLSOCK_INSTREAM )
|
|
{ assert(s->input);
|
|
Sclose(s->input);
|
|
}
|
|
if ( flags & PLSOCK_OUTSTREAM )
|
|
{ assert(s->output);
|
|
Sclose(s->output);
|
|
}
|
|
} else
|
|
{
|
|
#ifdef __WINDOWS__
|
|
if ( true(s, PLSOCK_CONNECT) )
|
|
{ if ( s->socket >= 0 )
|
|
shutdown(s->socket, SD_SEND);
|
|
}
|
|
#endif
|
|
|
|
freeSocket(s);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
NBIO_EXPORT(int)
|
|
nbio_setopt(nbio_sock_t socket, nbio_option opt, ...)
|
|
{ plsocket *s;
|
|
va_list args;
|
|
int rc;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
va_start(args, opt);
|
|
|
|
switch(opt)
|
|
{ case TCP_NONBLOCK:
|
|
rc = nbio_fcntl(socket, F_SETFL, O_NONBLOCK);
|
|
break;
|
|
case TCP_REUSEADDR:
|
|
{ int val = va_arg(args, int);
|
|
|
|
if( setsockopt(s->socket, SOL_SOCKET, SO_REUSEADDR,
|
|
(const char *)&val, sizeof(val)) == -1 )
|
|
{ nbio_error(h_errno, TCP_HERRNO);
|
|
rc = -1;
|
|
} else
|
|
rc = 0;
|
|
|
|
break;
|
|
}
|
|
case TCP_NO_DELAY:
|
|
#ifdef TCP_NODELAY
|
|
{ int val = va_arg(args, int);
|
|
|
|
#ifndef IPPROTO_TCP /* Is this correct? */
|
|
#define IPPROTO_TCP SOL_SOCKET
|
|
#endif
|
|
if ( setsockopt(s->socket, IPPROTO_TCP, TCP_NODELAY,
|
|
(const char *)&val, sizeof(val)) == -1 )
|
|
{ nbio_error(h_errno, TCP_HERRNO);
|
|
rc = -1;
|
|
} else
|
|
{ rc = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
#else
|
|
rc = -2; /* not implemented */
|
|
#endif
|
|
case UDP_BROADCAST:
|
|
{ int val = va_arg(args, int);
|
|
|
|
if ( setsockopt(s->socket, SOL_SOCKET, SO_BROADCAST,
|
|
(const char *)&val, sizeof(val)) == -1 )
|
|
{ nbio_error(h_errno, TCP_HERRNO);
|
|
rc = -1;
|
|
} else
|
|
rc = 0;
|
|
|
|
break;
|
|
}
|
|
case TCP_DISPATCH:
|
|
{ int val = va_arg(args, int);
|
|
|
|
if ( val )
|
|
set(s, PLSOCK_DISPATCH);
|
|
else
|
|
clear(s, PLSOCK_DISPATCH);
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
}
|
|
case TCP_INSTREAM:
|
|
{ IOSTREAM *in = va_arg(args, IOSTREAM*);
|
|
|
|
s->flags |= PLSOCK_INSTREAM;
|
|
s->input = in;
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
}
|
|
case TCP_OUTSTREAM:
|
|
{ IOSTREAM *out = va_arg(args, IOSTREAM*);
|
|
|
|
s->flags |= PLSOCK_OUTSTREAM;
|
|
s->output = out;
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
rc = -1;
|
|
assert(0);
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_get_flags(nbio_sock_t socket)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
return s->flags;
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
Translate a host + port-number into a sockaddr structure.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
static int
|
|
nbio_get_port(term_t Port, int *port)
|
|
{ char *name;
|
|
|
|
if ( PL_get_atom_chars(Port, &name) )
|
|
{ struct servent *service;
|
|
|
|
if ( !(service = getservbyname(name, "tcp")) )
|
|
return pl_error(NULL, 0, NULL, ERR_EXISTENCE, "service", Port);
|
|
|
|
*port = ntohs(service->s_port);
|
|
DEBUG(1, Sdprintf("Service %s at port %d\n", name, *port));
|
|
return TRUE;
|
|
}
|
|
|
|
if ( PL_get_integer(Port, port) )
|
|
return TRUE;
|
|
|
|
return pl_error(NULL, 0, NULL, ERR_ARGTYPE, -1, Port, "port");
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
Convert a term Host:Port to a socket address. Port is either an integer
|
|
or the name of a registered port (e.g. 'smtp').
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
int
|
|
nbio_get_sockaddr(term_t Address, struct sockaddr_in *addr)
|
|
{ int port;
|
|
|
|
addr->sin_family = AF_INET;
|
|
addr->sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if ( PL_is_functor(Address, FUNCTOR_module2) )
|
|
{ char *hostName;
|
|
term_t arg = PL_new_term_ref();
|
|
|
|
_PL_get_arg(1, Address, arg);
|
|
if ( PL_get_atom_chars(arg, &hostName) )
|
|
{ struct hostent *host;
|
|
|
|
if( !(host = gethostbyname(hostName)) )
|
|
return nbio_error(h_errno, TCP_HERRNO);
|
|
if ( (int)sizeof(addr->sin_addr) < host->h_length )
|
|
return PL_warning("Oops, host address too long!");
|
|
memcpy(&addr->sin_addr, host->h_addr, host->h_length);
|
|
} else if ( !nbio_get_ip(arg, &addr->sin_addr) )
|
|
{ return pl_error(NULL, 0, NULL, ERR_ARGTYPE, 1, arg, "atom|ip/4");
|
|
}
|
|
|
|
_PL_get_arg(2, Address, arg);
|
|
if ( !nbio_get_port(arg, &port) )
|
|
return FALSE;
|
|
} else if ( PL_is_variable(Address) )
|
|
{ port = 0;
|
|
} else if ( !nbio_get_port(Address, &port) )
|
|
return FALSE;
|
|
|
|
addr->sin_port = htons((short)port);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_get_ip(term_t ip4, struct in_addr *ip)
|
|
{ unsigned long hip = 0;
|
|
|
|
if ( PL_is_functor(ip4, FUNCTOR_ip4) )
|
|
{ int i, ia;
|
|
term_t a = PL_new_term_ref();
|
|
|
|
for(i=1; i<=4; i++)
|
|
{ _PL_get_arg(i, ip4, a);
|
|
if ( PL_get_integer(a, &ia) )
|
|
hip |= ia << ((4-i)*8);
|
|
else
|
|
return FALSE;
|
|
}
|
|
hip = htonl(hip);
|
|
memcpy(ip, &hip, sizeof(hip));
|
|
|
|
return TRUE;
|
|
} else if ( PL_is_functor(ip4, FUNCTOR_ip1) )
|
|
{ term_t a = PL_new_term_ref();
|
|
atom_t id;
|
|
|
|
_PL_get_arg(1, ip4, a);
|
|
if ( PL_get_atom(a, &id) )
|
|
{ if ( id == ATOM_any )
|
|
ip->s_addr = INADDR_ANY;
|
|
else if ( id == ATOM_broadcast )
|
|
ip->s_addr = INADDR_BROADCAST;
|
|
else if ( id == ATOM_loopback )
|
|
ip->s_addr = INADDR_LOOPBACK;
|
|
else
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_unify_ip4(term_t Ip, unsigned long hip)
|
|
{ return PL_unify_term(Ip,
|
|
PL_FUNCTOR, FUNCTOR_ip4,
|
|
IntArg((hip >> 24) & 0xff),
|
|
IntArg((hip >> 16) & 0xff),
|
|
IntArg((hip >> 8) & 0xff),
|
|
IntArg((hip >> 0) & 0xff));
|
|
}
|
|
|
|
|
|
int
|
|
nbio_bind(nbio_sock_t socket, struct sockaddr *my_addr, size_t addrlen)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
if ( bind(s->socket, my_addr, (int)addrlen) )
|
|
#else
|
|
if ( bind(s->socket, my_addr, addrlen) )
|
|
#endif
|
|
{ nbio_error(errno, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
|
|
s->flags |= PLSOCK_BIND;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_connect(nbio_sock_t socket,
|
|
const struct sockaddr *serv_addr,
|
|
size_t addrlen)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
if ( connect(s->socket, serv_addr, (int)addrlen) )
|
|
{ s->error = WSAGetLastError();
|
|
|
|
if ( s->error == WSAEWOULDBLOCK )
|
|
{ s->rdata.connect.addrlen = addrlen;
|
|
memcpy(&s->rdata.connect.addr, serv_addr, addrlen);
|
|
placeRequest(s, REQ_CONNECT);
|
|
waitRequest(s);
|
|
}
|
|
|
|
if ( s->error )
|
|
{ nbio_error(s->error, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
}
|
|
#else /*!__WINDOWS__*/
|
|
for(;;)
|
|
{ if ( connect(s->socket, serv_addr, addrlen) )
|
|
{ if ( need_retry(errno) )
|
|
{ if ( PL_handle_signals() < 0 )
|
|
return -1;
|
|
continue;
|
|
}
|
|
nbio_error(errno, TCP_ERRNO);
|
|
return -1;
|
|
} else
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
s->flags |= PLSOCK_CONNECT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
nbio_sock_t
|
|
nbio_accept(nbio_sock_t master, struct sockaddr *addr, socklen_t *addrlen)
|
|
{ SOCKET slave;
|
|
plsocket *m, *s;
|
|
|
|
if ( !(m = nbio_to_plsocket(master)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
{ int alen = (int)*addrlen;
|
|
slave = accept(m->socket, addr, &alen);
|
|
*addrlen = alen;
|
|
}
|
|
|
|
if ( slave == SOCKET_ERROR )
|
|
{ m->error = WSAGetLastError();
|
|
|
|
if ( m->error == WSAEWOULDBLOCK )
|
|
{ m->rdata.accept.addrlen = sizeof(m->rdata.accept.addr);
|
|
placeRequest(m, REQ_ACCEPT);
|
|
if ( !waitRequest(m) )
|
|
return -1;
|
|
if ( m->error )
|
|
return nbio_error(m->error, TCP_ERRNO);
|
|
*addrlen = m->rdata.accept.addrlen;
|
|
memcpy(addr, &m->rdata.accept.addr, m->rdata.accept.addrlen);
|
|
|
|
return m->rdata.accept.slave; /* already registered */
|
|
} else
|
|
{ nbio_error(m->error, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
for(;;)
|
|
{ if ( !wait_socket(m) )
|
|
return -1;
|
|
|
|
slave = accept(m->socket, addr, addrlen);
|
|
|
|
if ( slave == SOCKET_ERROR )
|
|
{ if ( need_retry(errno) )
|
|
{ if ( PL_handle_signals() < 0 )
|
|
return -1;
|
|
|
|
continue;
|
|
} else
|
|
{ nbio_error(errno, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
s = allocSocket(slave);
|
|
s->flags |= PLSOCK_ACCEPT;
|
|
#ifndef __WINDOWS__
|
|
if ( true(s, PLSOCK_NONBLOCK) )
|
|
nbio_setopt(slave, TCP_NONBLOCK);
|
|
#endif
|
|
|
|
return s->id;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_listen(nbio_sock_t socket, int backlog)
|
|
{ plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
if( listen(s->socket, backlog) == -1 )
|
|
{ nbio_error(errno, TCP_ERRNO);
|
|
return -1;
|
|
}
|
|
|
|
s->flags |= PLSOCK_LISTEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* IO-STREAM STUFF *
|
|
*******************************/
|
|
|
|
#define fdFromHandle(p) ((int)((intptr_t)(p)))
|
|
|
|
ssize_t
|
|
nbio_read(int socket, char *buf, size_t bufSize)
|
|
{ plsocket *s;
|
|
int n;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
|
|
DEBUG(3, Sdprintf("[%d] reading from socket %d\n",
|
|
PL_thread_self(), socket));
|
|
|
|
n = recv(s->socket, buf, (int)bufSize, 0);
|
|
if ( n < 0 )
|
|
{ int wsaerrno;
|
|
|
|
if ( (wsaerrno=WSAGetLastError()) == WSAEWOULDBLOCK )
|
|
{ s->rdata.read.buffer = buf;
|
|
s->rdata.read.size = bufSize;
|
|
placeRequest(s, REQ_READ);
|
|
if ( !waitRequest(s) )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
n = s->rdata.read.bytes;
|
|
}
|
|
|
|
if ( n < 0 )
|
|
{ s->error = wsaerrno;
|
|
errno = EIO;
|
|
}
|
|
} else
|
|
{ s->error = 0;
|
|
}
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
for(;;)
|
|
{ if ( !wait_socket(s) )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
|
|
n = recv(s->socket, buf, bufSize, 0);
|
|
|
|
if ( n == -1 && need_retry(errno) )
|
|
{ if ( PL_handle_signals() < 0 )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
nbio_write(nbio_sock_t socket, char *buf, size_t bufSize)
|
|
{ plsocket *s;
|
|
size_t len = bufSize;
|
|
char *str = buf;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
while( len != 0 )
|
|
{ ssize_t n;
|
|
|
|
DEBUG(3, Sdprintf("[%d] sending %d bytes using socket %d\n",
|
|
PL_thread_self(), (int)len, socket));
|
|
|
|
n = send(s->socket, str, (int)len, 0);
|
|
if ( n < 0 )
|
|
{ int error = WSAGetLastError();
|
|
|
|
if ( error == WSAEWOULDBLOCK )
|
|
break;
|
|
|
|
DEBUG(1, Sdprintf("[%d]: send(%d, %d bytes): %s\n",
|
|
PL_thread_self(), s->socket, (int)bufSize,
|
|
WinSockError(error)));
|
|
|
|
s->error = error;
|
|
return -1;
|
|
}
|
|
|
|
len -= n;
|
|
str += n;
|
|
}
|
|
|
|
if ( len > 0 ) /* operation would block */
|
|
{ s->rdata.write.buffer = str;
|
|
s->rdata.write.size = len;
|
|
s->rdata.write.written = 0;
|
|
placeRequest(s, REQ_WRITE);
|
|
if ( !waitRequest(s) )
|
|
{ errno = EPLEXCEPTION; /* handled Prolog signal */
|
|
return -1;
|
|
}
|
|
if ( s->rdata.write.bytes < 0 )
|
|
return -1; /* error in s->error */
|
|
}
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
while( len > 0 )
|
|
{ int n;
|
|
|
|
n = send(s->socket, str, len, 0);
|
|
if ( n < 0 )
|
|
{ if ( need_retry(errno) )
|
|
{ if ( PL_handle_signals() < 0 )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
len -= n;
|
|
str += n;
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
return bufSize;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_close_input(nbio_sock_t socket)
|
|
{ int rc = 0;
|
|
plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket_raw(socket)) )
|
|
return -1;
|
|
|
|
DEBUG(2, Sdprintf("[%d]: nbio_close_input(%d, flags=0x%x)\n",
|
|
PL_thread_self(), socket, s->flags));
|
|
s->flags &= ~PLSOCK_INSTREAM;
|
|
#ifdef __WINDOWS__
|
|
if ( false(s, PLSOCK_LISTEN) )
|
|
{ SOCKET sock;
|
|
|
|
if ( (sock=s->socket) < 0 )
|
|
{ s->error = WSAECONNRESET;
|
|
rc = -1;
|
|
} else if ( shutdown(sock, SD_RECEIVE) == SOCKET_ERROR )
|
|
{ DEBUG(1, Sdprintf("shutdown(%d=%d, SD_RECEIVE) failed: %s\n",
|
|
socket, s->socket,
|
|
WinSockError(WSAGetLastError())));
|
|
Sseterr(s->input, SIO_FERR, WinSockError(WSAGetLastError()));
|
|
rc = -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
s->input = NULL;
|
|
if ( !(s->flags & (PLSOCK_INSTREAM|PLSOCK_OUTSTREAM)) )
|
|
return freeSocket(s);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
nbio_close_output(nbio_sock_t socket)
|
|
{ int rc = 0;
|
|
plsocket *s;
|
|
|
|
if ( !(s = nbio_to_plsocket_raw(socket)) )
|
|
return -1;
|
|
|
|
DEBUG(2, Sdprintf("[%d]: nbio_close_output(%d, flags=0x%x)\n",
|
|
PL_thread_self(), socket, s->flags));
|
|
if ( s->output )
|
|
{
|
|
#if __WINDOWS__
|
|
SOCKET sock;
|
|
#endif
|
|
|
|
s->flags &= ~PLSOCK_OUTSTREAM;
|
|
#if __WINDOWS__
|
|
if ( (sock=s->socket) < 0 )
|
|
{ s->error = WSAECONNRESET;
|
|
rc = -1;
|
|
} else if ( shutdown(sock, SD_SEND) == SOCKET_ERROR )
|
|
{ const char *msg;
|
|
|
|
msg = WinSockError(WSAGetLastError());
|
|
Sseterr(s->output, SIO_FERR, msg);
|
|
DEBUG(1, Sdprintf("shutdown(%d=%d, SD_SEND) failed: %s\n",
|
|
socket, s->socket, msg));
|
|
rc = -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
DEBUG(3, Sdprintf("%d->flags = 0x%x\n", socket, s->flags));
|
|
s->output = NULL;
|
|
if ( !(s->flags & (PLSOCK_INSTREAM|PLSOCK_OUTSTREAM)) )
|
|
return freeSocket(s);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* UDP SUPPORT *
|
|
*******************************/
|
|
|
|
ssize_t
|
|
nbio_recvfrom(int socket, void *buf, size_t bufSize, int flags,
|
|
struct sockaddr *from, socklen_t *fromlen)
|
|
{ plsocket *s;
|
|
int n;
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
DEBUG(3, Sdprintf("[%d] recvfrom from socket %d\n",
|
|
PL_thread_self(), socket));
|
|
|
|
{ int iflen = (int)*fromlen; /* Windows recvfrom uses int */
|
|
|
|
n = recvfrom(s->socket, buf, (int)bufSize, flags, from, &iflen);
|
|
if ( n < 0 )
|
|
{ int wsaerrno;
|
|
|
|
if ( (wsaerrno=WSAGetLastError()) == WSAEWOULDBLOCK )
|
|
{ s->rdata.recvfrom.buffer = buf;
|
|
s->rdata.recvfrom.size = bufSize;
|
|
s->rdata.recvfrom.flags = flags;
|
|
s->rdata.recvfrom.from = from;
|
|
s->rdata.recvfrom.fromlen = fromlen;
|
|
placeRequest(s, REQ_RECVFROM);
|
|
if ( !waitRequest(s) )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
n = s->rdata.recvfrom.bytes;
|
|
}
|
|
|
|
if ( n < 0 )
|
|
{ s->error = wsaerrno;
|
|
errno = EIO;
|
|
}
|
|
} else
|
|
{ *fromlen = iflen;
|
|
s->error = 0;
|
|
}
|
|
}
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
for(;;)
|
|
{ if ( (flags & MSG_DONTWAIT) == 0 && !wait_socket(s) )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
|
|
n = recvfrom(s->socket, buf, bufSize, flags, from, fromlen);
|
|
|
|
if ( n == -1 && need_retry(errno) )
|
|
{ if ( PL_handle_signals() < 0 )
|
|
{ errno = EPLEXCEPTION;
|
|
return -1;
|
|
}
|
|
|
|
if((flags & MSG_DONTWAIT) != 0)
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
#endif /*__WINDOWS__*/
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
nbio_sendto(nbio_sock_t socket, void *buf, size_t bufSize, int flags,
|
|
const struct sockaddr *to, socklen_t tolen)
|
|
{ plsocket *s;
|
|
#ifdef __WINDOWS__
|
|
ssize_t n;
|
|
#endif
|
|
|
|
if ( !(s = nbio_to_plsocket(socket)) )
|
|
return -1;
|
|
|
|
#ifdef __WINDOWS__
|
|
DEBUG(3, Sdprintf("[%d] sending %d bytes using socket %d\n",
|
|
PL_thread_self(), (int)bufSize, socket));
|
|
|
|
n = sendto(s->socket, buf, (int)bufSize, flags, to, (int)tolen);
|
|
if ( n < 0 )
|
|
{ int error = WSAGetLastError();
|
|
|
|
if ( error == WSAEWOULDBLOCK )
|
|
goto wouldblock;
|
|
|
|
DEBUG(1, Sdprintf("[%d]: sendto(%d, %d bytes): %s\n",
|
|
PL_thread_self(), s->socket, (int)bufSize,
|
|
WinSockError(error)));
|
|
|
|
s->error = error;
|
|
return -1;
|
|
} else
|
|
return n;
|
|
|
|
wouldblock:
|
|
s->rdata.sendto.buffer = buf;
|
|
s->rdata.sendto.size = (int)bufSize;
|
|
s->rdata.sendto.bytes = 0;
|
|
s->rdata.sendto.flags = flags;
|
|
s->rdata.sendto.to = to;
|
|
s->rdata.sendto.tolen = (int)tolen;
|
|
placeRequest(s, REQ_SENDTO);
|
|
if ( !waitRequest(s) )
|
|
{ errno = EPLEXCEPTION; /* handled Prolog signal */
|
|
return -1;
|
|
}
|
|
|
|
return s->rdata.sendto.bytes;
|
|
|
|
#else /*__WINDOWS__*/
|
|
|
|
return sendto(s->socket, buf, bufSize, flags, to, tolen);
|
|
#endif /*__WINDOWS__*/
|
|
}
|