#include "portable.h"
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include "tio.h"
#ifndef ETIME
#define ETIME ETIMEDOUT
#endif
struct tio_buffer {
uint8_t *buffer;
size_t size;
size_t maxsize;
size_t start;
size_t len;
};
struct tio_fileinfo {
int fd;
struct tio_buffer readbuffer;
struct tio_buffer writebuffer;
struct timeval readtimeout;
struct timeval writetimeout;
int read_resettable;
#ifdef DEBUG_TIO_STATS
size_t byteswritten;
size_t bytesread;
#endif
};
static inline void tio_tv_add(struct timeval *tv1, const struct timeval *tv2)
{
tv1->tv_usec+=tv2->tv_usec;
if (tv1->tv_usec>1000000)
{
tv1->tv_usec-=1000000;
tv1->tv_sec+=1;
}
tv1->tv_sec+=tv2->tv_sec;
}
static inline void tio_tv_prepare(struct timeval *deadline, const struct timeval *timeout)
{
if (gettimeofday(deadline,NULL))
{
deadline->tv_sec=0;
deadline->tv_usec=0;
return;
}
tio_tv_add(deadline,timeout);
}
static inline int tio_tv_remaining(struct timeval *tv, const struct timeval *deadline)
{
if (gettimeofday(tv,NULL))
{
tv->tv_sec=1;
tv->tv_usec=0;
return 0;
}
if ( (tv->tv_sec>deadline->tv_sec) ||
( (tv->tv_sec==deadline->tv_sec) && (tv->tv_usec>deadline->tv_usec) ) )
return -1;
tv->tv_sec=deadline->tv_sec-tv->tv_sec;
if (tv->tv_usec<deadline->tv_usec)
tv->tv_usec=deadline->tv_usec-tv->tv_usec;
else
{
tv->tv_sec--;
tv->tv_usec=1000000+deadline->tv_usec-tv->tv_usec;
}
return 0;
}
TFILE *tio_fdopen(int fd,struct timeval *readtimeout,struct timeval *writetimeout,
size_t initreadsize,size_t maxreadsize,
size_t initwritesize,size_t maxwritesize)
{
struct tio_fileinfo *fp;
fp=(struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo));
if (fp==NULL)
return NULL;
fp->fd=fd;
fp->readbuffer.buffer=(uint8_t *)malloc(initreadsize);
if (fp->readbuffer.buffer==NULL)
{
free(fp);
return NULL;
}
fp->readbuffer.size=initreadsize;
fp->readbuffer.maxsize=maxreadsize;
fp->readbuffer.start=0;
fp->readbuffer.len=0;
fp->writebuffer.buffer=(uint8_t *)malloc(initwritesize);
if (fp->writebuffer.buffer==NULL)
{
free(fp->readbuffer.buffer);
free(fp);
return NULL;
}
fp->writebuffer.size=initwritesize;
fp->writebuffer.maxsize=maxwritesize;
fp->writebuffer.start=0;
fp->writebuffer.len=0;
fp->readtimeout.tv_sec=readtimeout->tv_sec;
fp->readtimeout.tv_usec=readtimeout->tv_usec;
fp->writetimeout.tv_sec=writetimeout->tv_sec;
fp->writetimeout.tv_usec=writetimeout->tv_usec;
fp->read_resettable=0;
#ifdef DEBUG_TIO_STATS
fp->byteswritten=0;
fp->bytesread=0;
#endif
return fp;
}
static int tio_select(TFILE *fp, int readfd, const struct timeval *deadline)
{
struct timeval tv;
fd_set fdset;
int rv;
while (1)
{
FD_ZERO(&fdset);
FD_SET(fp->fd,&fdset);
if (tio_tv_remaining(&tv,deadline))
{
errno=ETIME;
return -1;
}
if (readfd)
{
if (tv.tv_sec>fp->readtimeout.tv_sec)
tv.tv_sec=fp->readtimeout.tv_sec;
rv=select(FD_SETSIZE,&fdset,NULL,NULL,&tv);
}
else
{
if (tv.tv_sec>fp->writetimeout.tv_sec)
tv.tv_sec=fp->writetimeout.tv_sec;
rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
}
if (rv>0)
return 0;
else if (rv==0)
{
errno=ETIME;
return -1;
}
else if (errno!=EINTR)
return -1;
}
}
int tio_read(TFILE *fp, void *buf, size_t count)
{
struct timeval deadline;
int rv;
uint8_t *tmp;
size_t newsz;
uint8_t *ptr=(uint8_t *)buf;
tio_tv_prepare(&deadline,&(fp->readtimeout));
while (1)
{
if (fp->readbuffer.len >= count)
{
if (count>0)
{
if (ptr!=NULL)
memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,count);
fp->readbuffer.start+=count;
fp->readbuffer.len-=count;
}
return 0;
}
if (fp->readbuffer.len>0)
{
if (ptr!=NULL)
{
memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
ptr+=fp->readbuffer.len;
}
count-=fp->readbuffer.len;
fp->readbuffer.start+=fp->readbuffer.len;
fp->readbuffer.len=0;
}
if (!fp->read_resettable)
{
fp->readbuffer.start=0;
}
else if (fp->readbuffer.start>=(fp->readbuffer.size-4))
{
if (fp->readbuffer.size<fp->readbuffer.maxsize)
{
newsz=fp->readbuffer.size*2;
if (newsz>fp->readbuffer.maxsize)
newsz=fp->readbuffer.maxsize;
tmp=realloc(fp->readbuffer.buffer,newsz);
if (tmp!=NULL)
{
fp->readbuffer.buffer=tmp;
fp->readbuffer.size=newsz;
}
}
if (fp->readbuffer.start>=(fp->readbuffer.size-4))
{
fp->readbuffer.start=0;
fp->read_resettable=0;
}
}
if (tio_select(fp,1,&deadline))
return -1;
rv=read(fp->fd,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.size-fp->readbuffer.start);
if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
return -1;
fp->readbuffer.len=rv;
#ifdef DEBUG_TIO_STATS
fp->bytesread+=rv;
#endif
}
}
int tio_skip(TFILE *fp, size_t count)
{
return tio_read(fp,NULL,count);
}
static int tio_writebuf(TFILE *fp)
{
int rv;
#ifdef MSG_NOSIGNAL
rv=send(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len,MSG_NOSIGNAL);
#else
struct sigaction act,oldact;
memset(&act,0,sizeof(struct sigaction));
act.sa_sigaction=NULL;
act.sa_handler=SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_RESTART;
if (sigaction(SIGPIPE,&act,&oldact)!=0)
return -1;
rv=write(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
if (sigaction(SIGPIPE,&oldact,NULL)!=0)
return -1;
#endif
if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
return -1;
if (rv>0)
{
fp->writebuffer.start+=rv;
fp->writebuffer.len-=rv;
#ifdef DEBUG_TIO_STATS
fp->byteswritten+=rv;
#endif
if (fp->writebuffer.len==0)
fp->writebuffer.start=0;
if (fp->writebuffer.start>=(fp->writebuffer.size/4))
{
memmove(fp->writebuffer.buffer,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
fp->writebuffer.start=0;
}
}
return 0;
}
int tio_flush(TFILE *fp)
{
struct timeval deadline;
tio_tv_prepare(&deadline,&(fp->writetimeout));
while (fp->writebuffer.len > 0)
{
if (tio_select(fp,0,&deadline))
return -1;
if (tio_writebuf(fp))
return -1;
}
return 0;
}
static int tio_flush_nonblock(TFILE *fp)
{
struct timeval tv;
fd_set fdset;
int rv;
FD_ZERO(&fdset);
FD_SET(fp->fd,&fdset);
tv.tv_sec=0;
tv.tv_usec=0;
rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
if ((rv==0)||((rv<0)&&(errno==EINTR)))
return 0;
if (rv<0)
return -1;
return tio_writebuf(fp);
}
int tio_write(TFILE *fp, const void *buf, size_t count)
{
size_t fr;
uint8_t *tmp;
size_t newsz;
const uint8_t *ptr=(const uint8_t *)buf;
while (count>0)
{
fr=fp->writebuffer.size-(fp->writebuffer.start+fp->writebuffer.len);
if (count <= fr)
{
memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,count);
fp->writebuffer.len+=count;
return 0;
}
else if (fr > 0)
{
memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,fr);
fp->writebuffer.len+=fr;
ptr+=fr;
count-=fr;
}
if (tio_flush_nonblock(fp))
return -1;
if (fp->writebuffer.size>(fp->writebuffer.start+fp->writebuffer.len))
continue;
if (fp->writebuffer.size<fp->writebuffer.maxsize)
{
newsz=fp->writebuffer.size*2;
if (newsz>fp->writebuffer.maxsize)
newsz=fp->writebuffer.maxsize;
tmp=realloc(fp->writebuffer.buffer,newsz);
if (tmp!=NULL)
{
fp->writebuffer.buffer=tmp;
fp->writebuffer.size=newsz;
continue;
}
}
if (tio_flush(fp))
return -1;
}
return 0;
}
int tio_close(TFILE *fp)
{
int retv;
retv=tio_flush(fp);
#ifdef DEBUG_TIO_STATS
fprintf(stderr,"DEBUG_TIO_STATS READ=%d WRITTEN=%d\n",fp->bytesread,fp->byteswritten);
#endif
if (close(fp->fd))
retv=-1;
free(fp->readbuffer.buffer);
free(fp->writebuffer.buffer);
free(fp);
return retv;
}
void tio_mark(TFILE *fp)
{
if ((fp->readbuffer.start>0)&&(fp->readbuffer.len>0))
{
memmove(fp->readbuffer.buffer,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
fp->readbuffer.start=0;
}
fp->read_resettable=1;
}
int tio_reset(TFILE *fp)
{
if (!fp->read_resettable)
return -1;
fp->readbuffer.len+=fp->readbuffer.start;
fp->readbuffer.start=0;
return 0;
}