bitchx/source/newio.c

543 lines
12 KiB
C

/*
* newio.c: This is some handy stuff to deal with file descriptors in a way
* much like stdio's FILE pointers
*
* IMPORTANT NOTE: If you use the routines here-in, you shouldn't switch to
* using normal reads() on the descriptors cause that will cause bad things
* to happen. If using any of these routines, use them all
*
* Copyright 1990 Michael Sandrof
* Copyright 1995 Matthew Green
* Copyright 1997 EPIC Software Labs
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
*/
#include "irc.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(newio_c)
#include "ircaux.h"
#include "output.h"
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef WIN32
#include "winbitchx.h"
#endif
#define MAIN_SOURCE
#include "modval.h"
#if defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX) && !defined(__EMX__)
# define IO_ARRAYLEN sysconf(_SC_OPEN_MAX)
#else
# ifdef FD_SETSIZE
# define IO_ARRAYLEN FD_SETSIZE
# else
# define IO_ARRAYLEN NFDBITS
# endif
#endif
#ifdef __GNU__
#undef IO_ARRAYLEN
#define IO_ARRAYLEN 65535
#endif
#define MAX_SEGMENTS 16
typedef struct myio_struct
{
char *buffer;
size_t buffer_size;
unsigned read_pos,
write_pos;
int segments;
int error;
} MyIO;
static MyIO **io_rec = NULL;
int dgets_errno = 0;
/*
* Get_pending_bytes: What do you think it does?
*/
size_t get_pending_bytes (int fd)
{
if (fd >= 0 && io_rec[fd] && io_rec[fd]->buffer)
return strlen(io_rec[fd]->buffer);
return 0;
}
static void init_io (void)
{
static int first = 1;
if (first)
{
int c, max_fd = IO_ARRAYLEN;
io_rec = (MyIO **)new_malloc(sizeof(MyIO *) * max_fd);
for (c = 0; c < max_fd; c++)
io_rec[c] = (MyIO *) 0;
first = 0;
}
}
#ifdef HAVE_SSL
void SSL_show_errors(void)
{
char buf[1000];
int sslerr;
while((sslerr = ERR_get_error()))
{
ERR_error_string(sslerr, buf);
say("%s", buf);
}
}
#endif
/*
* All new dgets -- no more trap doors!
*
* There are at least four ways to look at this function.
* The most important variable is 'buffer', which determines if
* we force line buffering. If it is on, then we will sit on any
* incomplete lines until they get a newline. This is the default
* behavior for server connections, because they *must* be line
* delineated. However, when are getting input from an untrusted
* source (eg, dcc chat, /exec'd process), we cannot assume that every
* line will be newline delinated. So in those cases, 'buffer' is 0,
* and we force a flush on whatever we can slurp, without waiting for
* a newline.
*
* Return values:
*
* -1 -- something really died. Either a read error occured, the
* fildesc wasnt really ready for reading, or the input buffer
* for the filedesc filled up (8192 bytes)
* 0 -- If the data read in from the file descriptor did not form a
* complete line, then zero is always returned. This should be
* considered a stopping condition. Do not call dgets() again
* after it returns 0, because unless more data is avaiable on
* the fd, it will return -1, which you would misinterpret as an
* error condition.
* If "buffer" is 0, then whatever we have available will be
* returned in "str".
* If "buffer" is not 0, then we will retain whatever we have
* available, waiting for the newline to occur perhaps next time.
* >0 -- If a full, newline terminated line was available, the length
* of the line is returned.
*/
int BX_dgets (char *str, int des, int buffer, int buffersize, void *ssl_fd)
{
int cnt = 0, c;
MyIO *ioe;
int nbytes;
if (!io_rec)
init_io();
ioe = io_rec[des];
if (ioe == NULL)
{
ioe = io_rec[des] = (MyIO *)new_malloc(sizeof(MyIO));
ioe->buffer_size = IO_BUFFER_SIZE;
ioe->buffer = (char *)new_malloc(ioe->buffer_size + 2);
ioe->read_pos = ioe->write_pos = 0;
}
if (ioe->read_pos == ioe->write_pos)
{
ioe->read_pos = ioe->write_pos = 0;
ioe->buffer[0] = 0;
ioe->segments = 0;
}
if (!strchr(ioe->buffer + ioe->read_pos, '\n'))
{
if (ioe->read_pos)
{
ov_strcpy(ioe->buffer, ioe->buffer + ioe->read_pos);
ioe->read_pos = 0;
ioe->write_pos = strlen(ioe->buffer);
ioe->segments = 1;
}
/*
* Dont try to read into a full buffer.
*/
if (ioe->write_pos >= ioe->buffer_size)
{
yell("***XXX*** Buffer for des [%d] is filled!", des);
dgets_errno = ENOMEM; /* Cough */
return -1;
}
/*
* Check to see if any bytes are ready. If this fails,
* then its almost always due to the filedesc being
* bogus. Thats a fatal error.
*/
if (ioctl(des, FIONREAD, &nbytes) == -1)
{
*str = 0;
dgets_errno = errno;
return -1;
}
/*
* Check for a quasi-EOF condition. If we get to this
* point, then new_select() indicated that this fd is ready.
* The fd is ready if either:
* 1) A newline is in the buffer
* 2) select(2) returned ready for the fd.
*
* If 1) is true, then write_pos will not be zero. So we can
* use that as a cheap way to check for #1. If #1 is false,
* then #2 must have been true, and if nbytes is 0, then
* that indicates an EOF condition.
*/
else if (!nbytes && ioe->write_pos == 0)
{
*str = 0;
dgets_errno = errno;
return -1;
}
else if (nbytes)
{
#ifdef HAVE_SSL
int rc = 0;
#endif
if (nbytes >= IO_BUFFER_SIZE)
nbytes = IO_BUFFER_SIZE-1;
#ifdef HAVE_SSL
if(ssl_fd)
{
c = SSL_read((SSL *)ssl_fd, ioe->buffer + ioe->write_pos,
ioe->buffer_size - ioe->write_pos);
if(c == -1 && (rc = SSL_get_error((SSL *)ssl_fd, c)) == SSL_ERROR_WANT_READ)
{
/* If SSL needs more data, then we need to call SSL_read again,
* so we'll return with 0 bytes read, and hope we get called
* again. :)
*/
return 0;
}
}
else
#endif
c = read(des, ioe->buffer + ioe->write_pos,
ioe->buffer_size - ioe->write_pos);
if (x_debug & DEBUG_INBOUND)
yell("FD [%d], should [%d] did [%d]",
des, nbytes, c);
if (c <= 0)
{
#ifdef HAVE_SSL
if(ssl_fd)
{
say("SSL_read() failed, SSL error %d", rc);
SSL_show_errors();
}
#endif
*str = 0;
dgets_errno = (c == 0) ? -1 : errno;
return -1;
}
ioe->buffer[ioe->write_pos + c] = 0;
ioe->write_pos += c;
ioe->segments++;
}
else
{
/*
* At this point nbytes is 0, and it doesnt
* appear the socket is at EOF or ready to read.
* Very little to do at this point but force the
* issue and figure out what the heck went wrong.
*/
struct timeval t = { 0, 0 };
fd_set testing;
FD_ZERO(&testing);
FD_SET(des, &testing);
switch (select(des + 1, &testing, NULL, NULL, &t))
{
case -1:
{
yell("Aberrant condition for des [%d], closing down the connection out of desperation.", des);
*str = 0;
dgets_errno = errno;
return -1;
}
case 0:
{
yell("des [%d] passed to dgets(), but it isnt ready.", des);
if (ioe->write_pos == 0)
{
yell("X*X*X*X*X*X*X*X*X ABANDON SHIP! X*X*X*X*X*X*X*X*X*X");
ircpanic("write_pos is zero when it cant be.");
}
else
{
yell("But something is buffered. Flushing it to see if that helps.");
ioe->buffer[ioe->write_pos++] = '\n';
break;
}
}
case 1:
{
errno = ECONNABORTED;
*str = 0;
dgets_errno = errno;
return -1;
}
}
}
}
dgets_errno = 0;
/*
* If the caller wants us to force line buffering, and if there
* is no complete line, just stop right here.
*/
if (buffer && !strchr(ioe->buffer + ioe->read_pos, '\n'))
{
if (ioe->segments > MAX_SEGMENTS)
{
yell("*** Too many read()s on des [%d] without a newline!", des);
*str = 0;
dgets_errno = ECONNABORTED;
return -1;
}
return 0;
}
/*
* Slurp up the data that is available into 'str'.
*/
while (ioe->read_pos < ioe->write_pos)
{
if (((str[cnt] = ioe->buffer[ioe->read_pos++])) == '\n')
break;
cnt++;
if (cnt >= buffersize-1)
break;
}
/*
* Terminate it
*/
str[cnt + 1] = 0;
/*
* If we end in a newline, then all is well.
* Otherwise, we're unbuffered, tell the caller.
* The caller then would need to do a strlen() to get
* the amount of data.
*/
if (str[cnt] == '\n')
return cnt;
else
return 0;
}
static int global_max_fd = -1;
/*
* new_select: works just like select(), execpt I trimmed out the excess
* parameters I didn't need.
*/
int new_select (fd_set *rd, fd_set *wd, struct timeval *timeout)
{
int i,
set = 0;
fd_set new;
struct timeval thetimeout;
struct timeval *newtimeout = &thetimeout;
if (timeout)
thetimeout = *timeout;
else
newtimeout = NULL;
if (!io_rec)
ircpanic("new select called before io_rec init");
if (newtimeout && newtimeout->tv_usec < 0)
ircpanic("new select with < -1");
FD_ZERO(&new);
for (i = 0; i <= global_max_fd; i++)
{
if (io_rec[i])
{
if ((io_rec[i]->read_pos < io_rec[i]->write_pos) &&
strchr(io_rec[i]->buffer + io_rec[i]->read_pos, '\n'))
{
FD_SET(i, &new);
set++;
}
}
}
if (set)
{
*rd = new;
return set;
}
#ifdef WIN32
memset(&thetimeout, 0, sizeof(struct timeval));
thetimeout.tv_usec = 500;
i = select(global_max_fd + 1, rd, wd, NULL, &thetimeout);
{
MSG msg;
while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return i;
#else
return (select(global_max_fd + 1, rd, wd, NULL, newtimeout));
#endif
}
/*
* Register a filedesc for readable events
* Set up its input buffer
*/
int BX_new_open (int des)
{
if (des < 0)
return des; /* Invalid */
if (!io_rec)
init_io();
if (!FD_ISSET(des, &readables))
FD_SET(des, &readables);
if (des > global_max_fd)
global_max_fd = des;
return des;
}
int new_open_write (int des)
{
if (des < 0)
return des; /* Invalid */
if (!io_rec)
init_io();
if (!FD_ISSET(des, &writables))
FD_SET(des, &writables);
#if 0
if (des > global_max_fd)
global_max_fd = des;
#endif
return des;
}
/*
* Unregister a filedesc for readable events
* and close it down and free its input buffer
*/
int BX_new_close (int des)
{
if (des < 0)
return -1;
if (FD_ISSET(des, &readables))
FD_CLR(des, &readables);
if (io_rec && io_rec[des])
{
new_free(&(io_rec[des]->buffer));
new_free((char **)&(io_rec[des]));
}
close(des);
/*
* If we're closing the highest fd in use, then we
* want to adjust global_max_fd downward to the next highest fd.
*/
if (des == global_max_fd)
{
do
des--;
while (des >= 0 && !FD_ISSET(des, &readables));
global_max_fd = des;
}
return -1;
}
int new_close_write (int des)
{
if (des < 0)
return -1;
if (FD_ISSET(des, &writables))
FD_CLR(des, &writables);
if (io_rec && io_rec[des])
{
new_free(&(io_rec[des]->buffer));
new_free((char **)&(io_rec[des]));
}
close(des);
#if 0
/*
* If we're closing the highest fd in use, then we
* want to adjust global_max_fd downward to the next highest fd.
*/
if (des == global_max_fd)
{
do
des--;
while (!FD_ISSET(des, &writables));
global_max_fd = des;
}
#endif
return -1;
}
/* set's socket options */
void set_socket_options (int s)
{
int opt = 1;
#ifndef NO_STRUCT_LINGER
struct linger lin;
lin.l_onoff = lin.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&lin, sizeof(struct linger));
#endif
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
opt = 1;
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, sizeof(opt));
#if notyet
/* This is waiting for nonblock-aware code */
info = fcntl(fd, F_GETFL, 0);
info |= O_NONBLOCK;
fcntl(fd, F_SETFL, info);
#endif
}