/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program 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 "sysdeps.h"
#include <assert.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <memory.h>
#include <stdio.h>
#ifndef HAVE_WINSOCK
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif
/** QUEUE
**/
#define SYS_MAX_QUEUE 16
typedef struct {
int start;
int end;
void* pending[ SYS_MAX_QUEUE ];
}
SysQueueRec, *SysQueue;
static void
sys_queue_reset( SysQueue queue )
{
queue->start = queue->end = 0;
}
static void
sys_queue_add( SysQueue queue, void* item )
{
assert( queue->end - queue->start < SYS_MAX_QUEUE );
assert( queue->start == 0 );
assert( item != NULL );
queue->pending[ queue->end++ ] = item;
}
#if 0
static void
sys_queue_remove( SysQueue queue, void* item )
{
int nn, count;
assert( queue->end > queue->start );
assert( item != NULL );
count = queue->end - queue->start;
for ( nn = queue->start; count > 0; ++nn, --count ) {
if ( queue->pending[nn] == item ) {
queue->pending[nn] = queue->pending[nn+count-1];
queue->end -= 1;
break;
}
}
assert( 0 && "sys_queue_remove: item not found" );
}
#endif
static void*
sys_queue_get( SysQueue queue )
{
if (queue->end > queue->start) {
return queue->pending[ queue->start++ ];
}
return NULL;
}
/** CHANNELS
**/
typedef struct SysChannelRec_ {
SysChannel next;
int fd;
char active;
char pending;
char closed;
int wanted;
int ready;
SysChannelCallback callback;
void* opaque;
} SysChannelRec;
/*** channel allocation ***/
#define SYS_EVENT_MAX 3
#define SYS_MAX_CHANNELS 16
static SysChannelRec _s_channels0[ SYS_MAX_CHANNELS ];
static SysChannel _s_free_channels;
static SysChannel
sys_channel_alloc( void )
{
SysChannel channel = _s_free_channels;
assert( channel != NULL && "out of free channels" );
_s_free_channels = channel->next;
channel->next = NULL;
channel->active = 0;
channel->closed = 0;
channel->pending = 0;
channel->wanted = 0;
return channel;
}
static void
sys_channel_free( SysChannel channel )
{
if (channel->fd >= 0) {
#ifdef _WIN32
shutdown( channel->fd, SD_BOTH );
#else
shutdown( channel->fd, SHUT_RDWR );
#endif
close(channel->fd);
channel->fd = -1;
}
channel->wanted = 0;
channel->ready = 0;
channel->callback = NULL;
channel->next = _s_free_channels;
_s_free_channels = channel;
}
/* list of active channels */
static SysChannel _s_channels;
/* used by select to wait on channel events */
static fd_set _s_fdsets[SYS_EVENT_MAX];
static int _s_maxfd;
static void
sys_channel_deactivate( SysChannel channel )
{
assert( channel->active != 0 );
SysChannel *pnode = &_s_channels;
for (;;) {
SysChannel node = *pnode;
assert( node != NULL );
if (node == channel)
break;
pnode = &node->next;
}
*pnode = channel->next;
channel->next = NULL;
channel->active = 0;
}
static void
sys_channel_activate( SysChannel channel )
{
assert( channel->active == 0 );
channel->next = _s_channels;
_s_channels = channel;
channel->active = 1;
if (channel->fd > _s_maxfd)
_s_maxfd = channel->fd;
}
/* queue of pending channels */
static SysQueueRec _s_pending_channels[1];
static void
sys_init_channels( void )
{
int nn;
for (nn = 0; nn < SYS_MAX_CHANNELS-1; nn++)
_s_channels0[nn].next = &_s_channels0[nn+1];
_s_free_channels = &_s_channels0[0];
for (nn = 0; nn < SYS_EVENT_MAX; nn++)
FD_ZERO( &_s_fdsets[nn] );
_s_maxfd = -1;
sys_queue_reset( _s_pending_channels );
}
void
sys_channel_on( SysChannel channel,
int events,
SysChannelCallback callback,
void* opaque )
{
int adds = events & ~channel->wanted;
int removes = channel->wanted & ~events;
channel->wanted = events;
channel->callback = callback;
channel->opaque = opaque;
/* update global fdsets */
if (adds) {
int ee;
for (ee = 0; ee < SYS_EVENT_MAX; ee++)
if (adds & (1 << ee))
FD_SET( channel->fd, &_s_fdsets[ee] );
}
if (removes) {
int ee;
for (ee = 0; ee < SYS_EVENT_MAX; ee++)
if (removes & (1 << ee))
FD_CLR( channel->fd, &_s_fdsets[ee] );
}
if (events && !channel->active) {
sys_channel_activate( channel );
}
else if (!events && channel->active) {
sys_channel_deactivate( channel );
}
}
int
sys_channel_read( SysChannel channel, void* buffer, int size )
{
char* buff = buffer;
int count = 0;
assert( !channel->closed );
while (size > 0) {
int len = read(channel->fd, buff, size);
if (len < 0) {
if (errno == EINTR)
continue;
if (count == 0)
count = -1;
break;
}
buff += len;
size -= len;
count += len;
}
return count;
}
int
sys_channel_write( SysChannel channel, const void* buffer, int size )
{
const char* buff = buffer;
int count = 0;
assert( !channel->closed );
while (size > 0) {
int len = write(channel->fd, buff, size);
if (len < 0) {
if (errno == EINTR)
continue;
if (count == 0)
count = -1;
break;
}
buff += len;
size -= len;
count += len;
}
return count;
}
void
sys_channel_close( SysChannel channel )
{
if (channel->active) {
sys_channel_on( channel, 0, NULL, NULL );
}
if (channel->pending) {
/* we can't free the channel right now because it */
/* is in the pending list, set a flag */
channel->closed = 1;
return;
}
if (!channel->closed) {
channel->closed = 1;
}
sys_channel_free( channel );
}
/** time measurement
**/
SysTime sys_time_ms( void )
{
struct timeval tv;
gettimeofday( &tv, NULL );
return (SysTime)(tv.tv_usec / 1000) + (SysTime)tv.tv_sec * 1000;
}
/** timers
**/
typedef struct SysTimerRec_
{
SysTimer next;
SysTime when;
SysCallback callback;
void* opaque;
} SysTimerRec;
#define SYS_MAX_TIMERS 16
static SysTimerRec _s_timers0[ SYS_MAX_TIMERS ];
static SysTimer _s_free_timers;
static SysTimer _s_timers;
static SysQueueRec _s_pending_timers[1];
static void
sys_init_timers( void )
{
int nn;
for (nn = 0; nn < SYS_MAX_TIMERS-1; nn++) {
_s_timers0[nn].next = & _s_timers0[nn+1];
}
_s_free_timers = &_s_timers0[0];
sys_queue_reset( _s_pending_timers );
}
SysTimer sys_timer_create( void )
{
SysTimer timer = _s_free_timers;
assert( timer != NULL && "too many timers allocated" );
_s_free_timers = timer->next;
timer->next = NULL;
return timer;
}
void sys_timer_unset( SysTimer timer )
{
if (timer->callback != NULL) {
SysTimer *pnode, node;
pnode = &_s_timers;
for (;;) {
node = *pnode;
if (node == NULL)
break;
if (node == timer) {
*pnode = node->next;
break;
}
pnode = &node->next;
}
timer->next = NULL;
timer->callback = NULL;
timer->opaque = NULL;
}
}
void sys_timer_set( SysTimer timer,
SysTime when,
SysCallback callback,
void* opaque )
{
if (timer->callback != NULL)
sys_timer_unset(timer);
if (callback != NULL) {
SysTime now = sys_time_ms();
if (now >= when) {
callback( opaque );
} else {
SysTimer *pnode, node;
pnode = &_s_timers;
for (;;) {
node = *pnode;
if (node == NULL || node->when >= when) {
break;
}
pnode = &node->next;
}
timer->next = *pnode;
*pnode = timer;
timer->when = when;
timer->callback = callback;
timer->opaque = opaque;
}
}
}
void sys_timer_destroy( SysTimer timer )
{
assert( timer != NULL && "sys_timer_destroy: bad argument" );
if (timer->callback != NULL)
sys_timer_unset(timer);
timer->next = _s_free_timers;
_s_free_timers = timer;
}
static void
sys_single_loop( void )
{
fd_set rfd, wfd, efd;
struct timeval timeout_tv, *timeout = NULL;
int n;
memcpy(&rfd, &_s_fdsets[0], sizeof(fd_set));
memcpy(&wfd, &_s_fdsets[1], sizeof(fd_set));
memcpy(&efd, &_s_fdsets[2], sizeof(fd_set));
if ( _s_timers != NULL ) {
SysTime now = sys_time_ms();
SysTimer first = _s_timers;
timeout = &timeout_tv;
if (first->when <= now) {
timeout->tv_sec = 0;
timeout->tv_usec = 0;
} else {
SysTime diff = first->when - now;
timeout->tv_sec = diff / 1000;
timeout->tv_usec = (diff - timeout->tv_sec*1000) * 1000;
}
}
n = select( _s_maxfd+1, &rfd, &wfd, &efd, timeout);
if(n < 0) {
if(errno == EINTR) return;
perror("select");
return;
}
/* enqueue pending channels */
{
int i;
sys_queue_reset( _s_pending_channels );
for(i = 0; (i <= _s_maxfd) && (n > 0); i++)
{
int events = 0;
if(FD_ISSET(i, &rfd)) events |= SYS_EVENT_READ;
if(FD_ISSET(i, &wfd)) events |= SYS_EVENT_WRITE;
if(FD_ISSET(i, &efd)) events |= SYS_EVENT_ERROR;
if (events) {
SysChannel channel;
n--;
for (channel = _s_channels; channel; channel = channel->next)
{
if (channel->fd != i)
continue;
channel->ready = events;
channel->pending = 1;
sys_queue_add( _s_pending_channels, channel );
break;
}
}
}
}
/* enqueue pending timers */
{
SysTimer timer = _s_timers;
SysTime now = sys_time_ms();
sys_queue_reset( _s_pending_timers );
while (timer != NULL)
{
if (timer->when > now)
break;
sys_queue_add( _s_pending_timers, timer );
_s_timers = timer = timer->next;
}
}
}
void sys_main_init( void )
{
sys_init_channels();
sys_init_timers();
}
int sys_main_loop( void )
{
for (;;) {
SysTimer timer;
SysChannel channel;
/* exit if we have nothing to do */
if (_s_channels == NULL && _s_timers == NULL)
break;
sys_single_loop();
while ((timer = sys_queue_get( _s_pending_timers )) != NULL) {
timer->callback( timer->opaque );
}
while ((channel = sys_queue_get( _s_pending_channels )) != NULL) {
int events;
channel->pending = 0;
if (channel->closed) {
/* the channel was closed by a previous callback */
sys_channel_close(channel);
}
events = channel->ready;
channel->ready = 0;
channel->callback( channel->opaque, events );
}
}
return 0;
}
SysChannel
sys_channel_create_tcp_server( int port )
{
SysChannel channel;
int on = 1;
const int BACKLOG = 4;
channel = sys_channel_alloc();
if (-1==(channel->fd=socket(AF_INET, SOCK_STREAM, 0))) {
perror("socket");
sys_channel_free( channel );
return NULL;
}
/* Enable address re-use for server mode */
if ( -1==setsockopt( channel->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) )) {
perror("setsockopt(SO_REUSEADDR)");
}
{
struct sockaddr_in servname;
long in_addr = INADDR_ANY;
servname.sin_family = AF_INET;
servname.sin_port = htons(port);
servname.sin_addr.s_addr=in_addr;
if (-1==bind(channel->fd, (struct sockaddr*)&servname, sizeof(servname))) {
perror("bind");
sys_channel_close(channel);
return NULL;
}
/* Listen but don't accept */
if ( listen(channel->fd, BACKLOG) < 0 ) {
perror("listen");
sys_channel_close(channel);
return NULL;
}
}
return channel;
}
SysChannel
sys_channel_create_tcp_handler( SysChannel server_channel )
{
int on = 1;
SysChannel channel = sys_channel_alloc();
channel->fd = accept( server_channel->fd, NULL, 0 );
if (channel->fd < 0) {
perror( "accept" );
sys_channel_free( channel );
return NULL;
}
/* set to non-blocking and disable TCP Nagle algorithm */
fcntl(channel->fd, F_SETFL, O_NONBLOCK);
setsockopt(channel->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
return channel;
}
SysChannel
sys_channel_create_tcp_client( const char* hostname, int port )
{
struct hostent* hp;
struct sockaddr_in addr;
SysChannel channel = sys_channel_alloc();
int on = 1;
hp = gethostbyname(hostname);
if(hp == 0) {
fprintf(stderr, "unknown host: %s\n", hostname);
sys_channel_free(channel);
return NULL;
};
memset(&addr, 0, sizeof(addr));
addr.sin_family = hp->h_addrtype;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
channel->fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
if(channel->fd < 0) {
sys_channel_free(channel);
return NULL;
}
if(connect( channel->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror( "connect" );
sys_channel_free(channel);
return NULL;
}
/* set to non-blocking and disable Nagle algorithm */
fcntl(channel->fd, F_SETFL, O_NONBLOCK);
setsockopt( channel->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) );
return channel;
}