fis-gtm/sr_port/iosocket_snr.c

375 lines
15 KiB
C

/****************************************************************
* *
* Copyright 2001, 2012 Fidelity Information Services, Inc *
* *
* This source code contains the intellectual property *
* of its copyright holder(s), and is made available *
* under a license. If you do not know the terms of *
* the license, please stop and do not read further. *
* *
****************************************************************/
/* iosocket_snr.c
* description:
* -- takes care of the buffering of the recv for the socket device (and possible tcp device)
* parameters:
* -- socketptr pointer to a socket device, where we get socket descriptor, buffer and offset in buffer
* -- buffer pointer to the buffer where to return stuff
* -- maxlength maximum number of bytes to get
* -- flags flags to be passed to recv()
* -- time_for_read pointer to the timeout structure used by select()
* -- extra_status reports either a timeout or a lost-of-connection
* return:
* -- got some stuff to return return number of bytes received
* -- got nothing and timed out return 0
* -- loss-of-connection return -2
* -- error condition return -1, with errno set
* side note:
* -- use our own buffer if the requested size is smaller than our own buffer size
* -- use the caller's buffer if the requested size is bigger than our own buffer
* control flow:
* -- if we already have some leftover, use it and figure out how much is still needed
* -- if there is still need to read, figure out whether to use our buffer or the passed-in buffer
* -- select so that this operation is timed
* -- if select returns positive, recv, otherwise, return timeout
* -- if recv gets nothing, return lost-of-connection
* -- if the device buffer is used, move it over to the return buffer and update the device buffer pointer
*/
#include "mdef.h"
#include <sys/types.h>
#include <errno.h>
#include "gtm_stdio.h"
#include "gtm_time.h"
#include "gtm_inet.h"
#include "gtm_string.h"
#ifdef UNIX
#include "gtm_fcntl.h"
#include "eintr_wrappers.h"
static int fcntl_res;
#ifdef DEBUG
#include <sys/time.h> /* for gettimeofday */
#endif
#ifdef GTM_USE_POLL_FOR_SUBSECOND_SELECT
#include <sys/poll.h>
#endif
#endif
#include "gt_timer.h"
#include "io.h"
#include "iotimer.h"
#include "iotcpdef.h"
#include "iotcproutine.h"
#include "stringpool.h"
#include "iosocketdef.h"
#include "min_max.h"
#include "gtm_utf8.h"
#include "outofband.h"
/* MAX_SNR_IO is for read loop in iosocket_snr_utf_prebuffer(). It is possible for a series of interrupts (one
* from each active region) to interfere with this read so be generous here.
*/
#define MAX_SNR_IO 50
#ifdef DEBUG
/* Hold gettimeofday before and after select to debug AIX spin */
static struct timeval tvbefore, tvafter;
#endif
GBLREF io_pair io_curr_device;
GBLREF bool out_of_time;
GBLREF spdesc stringpool;
GBLREF tcp_library_struct tcp_routines;
GBLREF int4 outofband;
/* Local routine we aren't making static due to increased debugging difficult static routines make */
ssize_t iosocket_snr_io(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read);
/* Select aNd Receive */
ssize_t iosocket_snr(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read)
{
int status;
ssize_t bytesread, recvsize;
void *recvbuff;
DBGSOCK((stdout, "socsnr: socketptr: 0x"lvaddr" buffer: 0x"lvaddr" maxlength: %d, flags: %d\n",
socketptr, buffer, maxlength, flags));
/* Use leftover from the previous read, if there is any */
assert(0 < maxlength);
if (0 < socketptr->buffered_length)
{
DBGSOCK2((stdout, "socsnr: read from buffer - buffered_length: %d\n", socketptr->buffered_length));
bytesread = MIN(socketptr->buffered_length, maxlength);
memcpy(buffer, (void *)(socketptr->buffer + socketptr->buffered_offset), bytesread);
socketptr->buffered_offset += bytesread;
socketptr->buffered_length -= bytesread;
DBGSOCK2((stdout, "socsnr: after buffer read - buffered_offset: %d buffered_length: %d\n",
socketptr->buffered_offset, socketptr->buffered_length));
return bytesread;
}
/* Decide on which buffer to use and the size of the recv */
if (socketptr->buffer_size > maxlength)
{
recvbuff = socketptr->buffer;
recvsize = socketptr->buffer_size;
} else
{
recvbuff = buffer;
recvsize = maxlength;
}
VMS_ONLY(recvsize = MIN(recvsize, VMS_MAX_TCP_IO_SIZE));
DBGSOCK2((stdout, "socsnr: recvsize set to %d\n", recvsize));
/* Select and recv */
assert(0 == socketptr->buffered_length);
socketptr->buffered_length = 0;
bytesread = (int)iosocket_snr_io(socketptr, recvbuff, recvsize, flags, time_for_read);
DBGSOCK2((stdout, "socsnr: bytes read from recv: %d timeout: %d\n", bytesread, out_of_time));
if (0 < bytesread)
{ /* Got something this time */
if (recvbuff == socketptr->buffer)
{
if (bytesread <= maxlength)
memcpy(buffer, socketptr->buffer, bytesread);
else
{ /* Got some stuff for future recv */
memcpy(buffer, socketptr->buffer, maxlength);
socketptr->buffered_length = bytesread - maxlength;
bytesread = socketptr->buffered_offset = maxlength;
DBGSOCK2((stdout, "socsnr: Buffer updated post read - buffered_offset: %d "
"buffered_length: %d\n", socketptr->buffered_offset, socketptr->buffered_length));
}
}
}
return bytesread;
}
/* Do the IO dirty work. Note the return value can be from either select() or recv().
* This would be a static routine but that just makes it harder to debug.
*/
ssize_t iosocket_snr_io(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read)
{
int status, bytesread, real_errno;
fd_set tcp_fd;
ABS_TIME lcl_time_for_read;
# ifdef GTM_USE_POLL_FOR_SUBSECOND_SELECT
long poll_timeout;
unsigned long poll_nfds;
struct pollfd poll_fdlist[1];
# endif
DBGSOCK2((stdout, "socsnrio: Socket read request - socketptr: 0x"lvaddr" buffer: 0x"lvaddr" maxlength: %d flags: %d ",
socketptr, buffer, maxlength, flags));
DBGSOCK2((stdout, "time_for_read->at_sec: %d usec: %d\n", time_for_read->at_sec, time_for_read->at_usec));
DEBUG_ONLY(gettimeofday(&tvbefore, NULL);)
#ifndef GTM_USE_POLL_FOR_SUBSECOND_SELECT
FD_ZERO(&tcp_fd);
FD_SET(socketptr->sd, &tcp_fd);
assert(0 != FD_ISSET(socketptr->sd, &tcp_fd));
lcl_time_for_read = *time_for_read;
status = tcp_routines.aa_select(socketptr->sd + 1, (void *)(&tcp_fd), (void *)0, (void *)0, &lcl_time_for_read);
#else
poll_fdlist[0].fd = socketptr->sd;
poll_fdlist[0].events = POLLIN;
poll_nfds = 1;
poll_timeout = time_for_read->at_usec / 1000; /* convert to millisecs */
status = poll(&poll_fdlist[0], poll_nfds, poll_timeout);
#endif
real_errno = errno;
DEBUG_ONLY(gettimeofday(&tvafter, NULL);)
DBGSOCK2((stdout, "socsnrio: Select return code: %d :: errno: %d\n", status, real_errno));
if (0 < status)
{
bytesread = tcp_routines.aa_recv(socketptr->sd, buffer, maxlength, flags);
real_errno = errno;
socketptr->last_recv_errno = (-1 != status) ? 0 : real_errno; /* Save status of last recv for dbging purposes */
DBGSOCK2((stdout, "socsnrio: aa_recv return code: %d :: errno: %d\n", bytesread, errno));
if ((0 == bytesread) ||
((-1 == bytesread) && ((ECONNRESET == real_errno) || (EPIPE == real_errno) || (EINVAL == real_errno))))
{ /* Lost connection */
if (0 == bytesread)
errno = ECONNRESET;
return (ssize_t)(-2);
}
DBGSOCK_ONLY2(errno = real_errno);
return bytesread;
}
DBGSOCK_ONLY2(errno = real_errno);
return (ssize_t)status;
}
/* When scanning for delimiters, we have to make sure that the next read can pull in at least one full utf char.
* Failure to do this means that if a partial utf8 char is read, it will be rebuffered, reread, rebuffered, forever.
* A return code of zero indicates a timeout error occured. A negative return code indicates an IO error of some sort.
* A positive return code is the length in bytes of the next unicode char in the buffer.
*/
ssize_t iosocket_snr_utf_prebuffer(io_desc *iod, socket_struct *socketptr, int flags, ABS_TIME *time_for_read,
boolean_t wait_for_input)
{
int mblen, bytesread, real_errno;
ssize_t readlen;
char *readptr;
assert(CHSET_M != iod->ichset);
DBGSOCK((stdout, "socsnrupb: Enter prebuffer: buffered_length: %d wait_for_input: %d\n",
socketptr->buffered_length, wait_for_input));
/* See if there is *anything* in the buffer */
if (0 == socketptr->buffered_length)
{ /* Buffer is empty, read at least one char into it so we can check how many we need */
do
{
bytesread = (int)iosocket_snr_io(socketptr, socketptr->buffer, socketptr->buffer_size, flags,
time_for_read);
DBGSOCK_ONLY2(real_errno = errno);
DBGSOCK2((stdout, "socsnrupb: Buffer empty - bytes read: %d errno: %d\n", bytesread, real_errno));
DBGSOCK_ONLY2(errno = real_errno);
} while ((((-1 == bytesread) && (EINTR == errno)) || (0 == bytesread && wait_for_input))
&& !out_of_time && (0 == outofband));
if (out_of_time || (0 != outofband))
{
DBGSOCK_ONLY(if (out_of_time)
{
DBGSOCK((stdout, "socsnrupb: Returning due to timeout\n"));
} else
{
DBGSOCK((stdout, "socsnrupb: Returning due to outofband\n"));
}
);
if (0 < bytesread)
{ /* If we read anything, be sure to consider it buffered */
socketptr->buffered_length = bytesread;
socketptr->buffered_offset = 0;
}
return 0;
}
if (0 >= bytesread)
{
DBGSOCK_ONLY2(real_errno = errno);
DBGSOCK2((stdout, "socsnrupb: Returning due to error code %d errno: %d\n", bytesread, real_errno));
DBGSOCK_ONLY2(errno = real_errno);
return bytesread;
}
socketptr->buffered_length = bytesread;
socketptr->buffered_offset = 0;
}
/* Compute number of bytes we need for the first char in the buffer */
readptr = socketptr->buffer + socketptr->buffered_offset;
switch(iod->ichset)
{
case CHSET_UTF8:
mblen = UTF8_MBFOLLOW(readptr);
if (0 > mblen)
mblen = 0; /* Invalid char, just assume one char needed */
break;
case CHSET_UTF16BE:
mblen = UTF16BE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
if (0 > mblen)
mblen = 1; /* If buffer is too small we will get -1 here. Assume need 2 chars */
break;
case CHSET_UTF16LE:
mblen = UTF16LE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
if (0 > mblen)
mblen = 1; /* If buffer is too small we will get -1 here. Assume need 2 chars */
break;
case CHSET_UTF16:
/* Special case as we don't know which mode we are in. This should only be used when
* checking for BOMs. Check if first char is 0xFF or 0xFE. If it is, return 1 as our
* (follow) length. If neither, assume UTF16BE (default UTF16 codeset) and return the
* length it gives.
*/
if ((0xFF == (unsigned char)*readptr) || (0xFE == (unsigned char)*readptr))
mblen = 1;
else
{
mblen = UTF16BE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
if (0 > mblen)
mblen = 1; /* If buffer is too small we will get -1 here. Assume need 2 chars */
}
break;
default:
GTMASSERT;
}
mblen++; /* Include first char we were looking at in the required byte length */
DBGSOCK2((stdout, "socsnrupb: Length of char: %d\n", mblen));
if (socketptr->buffered_length < mblen)
{ /* Still insufficient chars in the buffer for our utf character. Read some more in. */
if ((socketptr->buffered_offset + mblen) > socketptr->buffer_size)
{ /* Our char won't fit in the buffer. This can only occur if the read point is
* right at the end of the buffer since the minimum buffer size is 32K. Solution
* is to slide the part of the char that we have down to the beginning of the
* buffer so we have plenty of room. Since this is at most 3 bytes, this is not
* a major performance concern.
*/
DBGSOCK2((stdout, "socsnrupb: Char won't fit in buffer, slide it down\n"));
assert(SIZEOF(int) > socketptr->buffered_length);
assert(socketptr->buffered_offset > socketptr->buffered_length); /* Assert no overlap */
memcpy(socketptr->buffer, (socketptr->buffer + socketptr->buffered_offset), socketptr->buffered_length);
socketptr->buffered_offset = 0;
}
while (socketptr->buffered_length < mblen)
{
DBGSOCK2((stdout, "socsnrupb: Top of read loop for char - buffered_length: %d\n",
socketptr->buffered_length));
readptr = socketptr->buffer + socketptr->buffered_offset + socketptr->buffered_length;
readlen = socketptr->buffer_size - socketptr->buffered_offset - socketptr->buffered_length;
assert(0 < readlen);
bytesread = (int)iosocket_snr_io(socketptr, readptr, readlen, flags, time_for_read);
DBGSOCK2((stdout, "socsnrupb: Read %d chars\n", bytesread));
if (0 > bytesread)
{ /* Some error occurred. Check for restartable condition. */
if (EINTR == errno)
if (!out_of_time)
continue;
else
return 0; /* timeout indicator */
return bytesread;
}
if (out_of_time)
return 0;
socketptr->buffered_length += bytesread;
}
}
DBGSOCK((stdout, "socsnrupb: Returning char length %d -- buffered_length: %d\n", mblen, socketptr->buffered_length));
return mblen;
}
/* Place len bytes pointed by buffer back into socketptr's internal buffer
*
* Side effect: suppose the last snr was with a length > internal buffer size, we would not have used the internal buffer. For
* that case, unsnr might move data not in the internal buffer into the internal buffer and also might result in buffer
* expansion
*/
void iosocket_unsnr(socket_struct *socketptr, unsigned char *buffer, size_t len)
{
char *new_buff;
DBGSOCK((stdout, "iosunsnr: ** Requeueing %d bytes\n", len));
if ((socketptr->buffered_length + len) <= socketptr->buffer_size)
{
if (0 < socketptr->buffered_length)
{
if (socketptr->buffered_offset < len)
{
memmove(socketptr->buffer + len, socketptr->buffer + socketptr->buffered_offset,
socketptr->buffered_length);
memmove(socketptr->buffer, buffer, len);
} else
{
memmove(socketptr->buffer, buffer, len);
memmove(socketptr->buffer + len, socketptr->buffer + socketptr->buffered_offset,
socketptr->buffered_length);
}
} else
memmove(socketptr->buffer, buffer, len);
} else
{
new_buff = malloc(socketptr->buffered_length + len);
memcpy(new_buff, buffer, len);
if (0 < socketptr->buffered_length)
memcpy(new_buff + len, socketptr->buffer + socketptr->buffered_offset, socketptr->buffered_length);
free(socketptr->buffer);
socketptr->buffer = new_buff;
}
socketptr->buffered_offset = 0;
socketptr->buffered_length += len;
return;
}