# define INCLUDE_FILE_IO
# include "dgd.h"
# include <sys/time.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# include <errno.h>
# include "str.h"
# include "array.h"
# include "object.h"
# include "comm.net.h"
# define EXT_SIZE 16384 /* size requested when extending a buffer */
struct _connection_ {
int fd; /* file descriptor */
struct sockaddr_in sin; /* peer's address */
struct _connection_ *next; /* next in list */
int extended; /* output buffer has been extended */
char host[16]; /* ascii ip number of peer */
char hostlen; /* length of host string */
};
static connection *connections; /* connections array */
static connection *flist; /* list of free connections */
static fd_set rfds; /* read file descriptor bitmap */
static fd_set wfds; /* write file descriptor bitmap */
static fd_set readfds; /* fds selected for reading */
static fd_set writefds; /* fds selected for writing */
static fd_set xxxfds;
static fd_set ufds; /* identical to rfds, minus those that
are blocked */
static int maxfd; /* largest fd opened yet */
/*
* NAME: conn->init()
* DESCRIPTION: initialize connections
*/
void conn_init(maxusers)
int maxusers;
{
register int n;
register connection *conn;
connections = ALLOC(connection, maxusers);
for (n = maxusers, conn = connections; n > 0; --n, conn++) {
conn->fd = -1;
conn->next = flist;
flist = conn;
}
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&ufds);
}
/*
* NAME: conn->finish()
* DESCRIPTION: terminate connections
*/
void conn_finish()
{
}
/*
* NAME: conn->new()
* DESCRIPTION: initialize a new connection struct
*/
static connection *conn_new(fd, sin)
int fd;
struct sockaddr_in *sin;
{
register connection *conn;
conn = flist;
flist = conn->next;
conn->fd = fd;
memcpy(&conn->sin, sin, sizeof(conn->sin));
strcpy(conn->host, inet_ntoa(sin->sin_addr));
conn->hostlen = strlen(conn->host);
if (fd >= 0) {
FD_SET(fd, &xxxfds);
if (fd > maxfd) {
maxfd = fd;
}
}
return conn;
}
/*
* NAME: conn->extend()
* DESCRIPTION: enlarge a socket's output buffer
*/
static void conn_extend(conn)
connection *conn;
{
int bufsize;
int sz = sizeof(bufsize);
conn->extended = 1;
if (getsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, &sz) >= 0
&& sz == sizeof(bufsize) && bufsize < EXT_SIZE) {
bufsize = EXT_SIZE;
setsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF,
(void *)&bufsize, sizeof(bufsize));
}
}
/*
* NAME: conn->listen()
* DESCRIPTION: bind a connection to a local port
*/
connection *conn_listen(port, protocol)
int port, protocol;
{
int fd;
static struct sockaddr_in sin;
int size;
int on = 1;
if (port < 0) {
/* Request automatic port assignment */
port = 0;
}
/* No free connections: see the comment about nusers in comm_receive. */
if (flist == (connection *) NULL) {
return (connection *) NULL;
}
if (protocol == PRC_UDP) {
fd = socket(PF_INET, SOCK_DGRAM, 0);
} else {
fd = socket(PF_INET, SOCK_STREAM, 0);
}
if (fd < 0) {
return (connection *)NULL;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
sizeof(on)) < 0) {
close(fd);
return (connection *)NULL;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = INADDR_ANY; /* htonl? */
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
close(fd);
return (connection *)NULL;
}
size = sizeof(sin);
if (getsockname(fd, (struct sockaddr *)&sin, &size) < 0) {
close(fd);
return (connection *)NULL;
}
if (protocol != PRC_UDP && listen(fd, 64) < 0) {
close(fd);
return (connection *)NULL;
}
if (fcntl(fd, F_SETFL, FNDELAY) < 0) {
close(fd);
return (connection *)NULL;
}
FD_SET(fd, &rfds);
FD_SET(fd, &ufds);
return conn_new(fd, &sin);
}
/*
* NAME: conn->accept()
* DESCRIPTION: accept a connection
*/
connection *conn_accept(conn)
connection *conn;
{
int fd;
struct sockaddr_in sin;
int len, on;
if (!FD_ISSET(conn->fd, &readfds)) {
return (connection *) NULL;
}
len = sizeof(sin);
fd = accept(conn->fd, (struct sockaddr *) &sin, &len);
if (fd < 0) {
return (connection *) NULL;
}
on = 1;
setsockopt(conn->fd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on));
FD_SET(fd, &rfds);
FD_SET(fd, &ufds);
return conn_new(fd, &sin);
}
/*
* NAME: conn->connect()
* DESCRIPTION: initiate a tcp connection
*/
connection *conn_connect(host, port, protocol)
char *host;
int port, protocol;
{
int fd;
static struct sockaddr_in sin;
if (protocol != PRC_TCP) {
return (connection *) NULL;
}
/* No free connections: see the comment about nusers in comm_receive(). */
if (flist == (connection *) NULL) {
return (connection *) NULL;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(host);
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return conn_new(-1, &sin);
}
if (fcntl(fd, F_SETFL, FNDELAY) < 0) {
close(fd);
return conn_new(-1, &sin);
}
if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0 &&
!(errno == EINPROGRESS || errno == EAGAIN)) {
close(fd);
return conn_new(-1, &sin);
}
FD_SET(fd, &wfds);
return conn_new(fd, &sin);
}
/*
* NAME: conn->connected()
* DESCRIPTION: mark a connecting socket as active
*/
int conn_connected(conn)
register connection *conn;
{
struct sockaddr sa;
int salen = sizeof(sa);
if (conn->fd == -1)
return -1;
if (!FD_ISSET(conn->fd, &writefds))
return 0;
/* Use getpeername() solely to check for errors. */
if (getpeername(conn->fd, &sa, &salen) < 0) {
FD_CLR(conn->fd, &wfds);
return -1;
}
FD_CLR(conn->fd, &wfds);
FD_SET(conn->fd, &rfds);
FD_SET(conn->fd, &ufds);
return 1;
}
/*
* NAME: conn->writable()
* DESCRIPTION: check if a connection has been selected for writing
*/
int conn_writable(conn)
register connection *conn;
{
return conn->fd >= 0 && FD_ISSET(conn->fd, &writefds);
}
/*
* NAME: conn->del()
* DESCRIPTION: delete a connection
*/
void conn_del(conn)
register connection *conn;
{
if (conn->fd >= 0) {
close(conn->fd);
FD_CLR(conn->fd, &rfds);
FD_CLR(conn->fd, &ufds);
FD_CLR(conn->fd, &wfds);
FD_CLR(conn->fd, &readfds);
FD_CLR(conn->fd, &writefds);
conn->fd = -1;
}
conn->next = flist;
flist = conn;
}
/*
* NAME: conn->wait()
* DESCRIPTION: mark a connection as waiting for writability
*/
void conn_wait(conn, wait)
register connection *conn;
register int wait;
{
if (conn->fd < 0) {
return;
}
if (wait) {
FD_SET(conn->fd, &wfds);
} else {
FD_CLR(conn->fd, &wfds);
}
}
/*
* NAME: conn->block()
* DESCRIPTION: temporarily unselect (block) a connection with respect to
* incoming data
*/
void conn_block(conn, flag)
register connection *conn;
register int flag;
{
if (conn->fd < 0) {
return;
}
if (flag) {
FD_CLR(conn->fd, &ufds);
FD_CLR(conn->fd, &readfds);
} else {
FD_SET(conn->fd, &ufds);
}
}
/*
* NAME: conn->select()
* DESCRIPTION: wait for input from connections
*/
int conn_select(wait)
int wait;
{
struct timeval timeout;
int retval;
/*
* First, check readability and writability for binary sockets with pending
* data only.
*/
memcpy(&readfds, &ufds, sizeof(fd_set));
memcpy(&writefds, &wfds, sizeof(fd_set));
timeout.tv_sec = (int) wait;
timeout.tv_usec = 0;
retval = select(maxfd + 1, &readfds, &writefds, (fd_set *) NULL, &timeout);
if (retval < 0) {
FD_ZERO(&readfds);
}
/*
* Now check writability for all sockets in a polling call.
*/
memcpy(&xxxfds, &rfds, sizeof(fd_set));
timeout.tv_sec = 0;
timeout.tv_usec = 0;
select(maxfd + 1, (fd_set *) NULL, &xxxfds, (fd_set *) NULL, &timeout);
return retval;
}
/*
* NAME: conn->recvfrom()
* DESCRIPTION: read a datagram
*/
int conn_recvfrom(conn, buf, size)
connection *conn;
char *buf;
int size;
{
struct sockaddr_in sin;
int len = sizeof(sin);
if (conn->fd < 0) {
return -1;
}
if (!FD_ISSET(conn->fd, &readfds)) {
return 0;
}
size = recvfrom(conn->fd, buf, size, 0, (struct sockaddr *)&sin, &len);
memcpy(&(conn->sin), &sin, len);
strcpy(conn->host, inet_ntoa(sin.sin_addr));
conn->hostlen = strlen(conn->host);
return size; /* Size may legitimately be 0 */
}
/*
* NAME: conn->read()
* DESCRIPTION: read from a connection
*/
int conn_read(conn, buf, size)
connection *conn;
char *buf;
int size;
{
if (conn->fd < 0) {
return -1;
}
if (!FD_ISSET(conn->fd, &readfds) || size == 0) {
return 0;
}
size = read(conn->fd, buf, size);
return (size == 0) ? -1 : size;
}
/*
* NAME: conn->sendto()
* DESCRIPTION: send a datagram
*/
void conn_sendto(conn, data, size, host, port)
connection *conn;
char *data, *host;
int size, port;
{
static struct sockaddr_in sin;
if (conn->fd >= 0) {
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(host);
sin.sin_port = htons(port);
if (sendto(conn->fd, data, size, 0, (struct sockaddr *)&sin,
sizeof(sin)) < 0
&& errno == EMSGSIZE && !conn->extended) {
conn_extend(conn);
sendto(conn->fd, data, size, 0, (struct sockaddr *)&sin,
sizeof(sin));
}
}
}
/*
* NAME: conn->write()
* DESCRIPTION: write to a connection; return the number of bytes written
*/
int conn_write(conn, buf, size)
connection *conn;
char *buf;
register int size;
{
int len;
if (conn->fd < 0) {
return -1;
} else if (!FD_ISSET(conn->fd, &xxxfds)) {
return 0;
} else {
len = write(conn->fd, buf, size);
if (len < 0) {
FD_CLR(conn->fd, &xxxfds);
if (errno == EWOULDBLOCK) {
return 0;
} else {
close(conn->fd);
FD_CLR(conn->fd, &rfds);
FD_CLR(conn->fd, &ufds);
FD_CLR(conn->fd, &wfds);
FD_CLR(conn->fd, &readfds);
FD_CLR(conn->fd, &writefds);
conn->fd = -1;
return -1;
}
}
if (len < size && !conn->extended) {
conn_extend(conn);
}
return len;
}
}
/*
* NAME: conn->ipnum()
* DESCRIPTION: return the ip number of a connection
*/
string *conn_ipnum(conn)
connection *conn;
{
return str_new(conn->host, conn->hostlen);
}
/*
* NAME: conn->port()
* DESCRIPTION: return the port number of a connection
*/
Int conn_port(conn)
connection *conn;
{
return ntohs(conn->sin.sin_port);
}