diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2007-06-08 22:57:27 +0000 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2007-06-08 22:57:27 +0000 |
commit | 51f3f0903b3f32231f7e8780a5d7c4f9ae1f9203 (patch) | |
tree | 547d454f6a94f6344033b51a8c4e5411515ec1d6 /common/tio.c | |
parent | 6dc21ddc6876690943c093adc7289a922212fe87 (diff) |
implement our own stdio-like library that handles IO with a simple configurable timeout mechanism with buffering
git-svn-id: http://arthurdejong.org/svn/nss-pam-ldapd/nss-ldapd@272 ef36b2f9-881f-0410-afb5-c4e39611909c
Diffstat (limited to 'common/tio.c')
-rw-r--r-- | common/tio.c | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/common/tio.c b/common/tio.c new file mode 100644 index 0000000..9ddc6bc --- /dev/null +++ b/common/tio.c @@ -0,0 +1,365 @@ +/* + tio.c - timed io functions + This file is part of the nss-ldapd library. + + Copyright (C) 2007 Arthur de Jong + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "config.h" + +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <signal.h> + +#include "tio.h" + +/* buffer size for both read and write buffers */ +/* TODO: pass this along with the open function */ +#define TIO_BUFFERSIZE (4*1024) + +/* structure that holds a buffer */ +struct tio_buffer { + uint8_t *buffer; + /* the size is TIO_BUFFERSIZE */ + size_t len; /* the number of bytes used in the buffer */ + int start; /* the start of the buffer */ +}; + +/* structure that holds all the state for files */ +struct tio_fileinfo { + int fd; + struct tio_buffer *readbuffer; + struct tio_buffer *writebuffer; + struct timeval readtimeout; + struct timeval writetimeout; +#ifdef DEBUG_TIO_STATS + /* this is used to collect statistics on the use of the streams + and can be used to tune the buffer sizes */ + size_t byteswritten; + size_t bytesread; +#endif /* DEBUG_TIO_STATS */ +}; + +/* add the second timeval to the first modifing the first */ +static inline void tio_tv_add(struct timeval *tv1, const struct timeval *tv2) +{ + /* BUG: we hope that this does not overflow */ + 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; +} + +/* build a timeval for comparison to when the operation should be finished */ +static inline void tio_tv_prepare(struct timeval *deadline, const struct timeval *timeout) +{ + if (gettimeofday(deadline,NULL)) + { + /* just blank it in case of errors */ + deadline->tv_sec=0; + deadline->tv_usec=0; + return; + } + tio_tv_add(deadline,timeout); +} + +static inline struct tio_buffer *tio_buffer_new(void) +{ + struct tio_buffer *buf; + /* allocate memory */ + buf = (struct tio_buffer *)malloc(sizeof(struct tio_buffer)+TIO_BUFFERSIZE); + if (buf==NULL) + return NULL; + /* initialize struct */ + buf->buffer=((uint8_t *)buf)+sizeof(struct tio_buffer); + buf->len=0; + buf->start=0; + return buf; +} + +static inline void tio_buffer_free(struct tio_buffer *buf) +{ + /* since we allocated only one block we can just free it */ + free(buf); +} + +/* update the timeval to the value that is remaining before deadline + returns non-zero if there is no more time before the deadline */ +static inline int tio_tv_remaining(struct timeval *tv, const struct timeval *deadline) +{ + /* get the current time */ + if (gettimeofday(tv,NULL)) + { + /* 1 second default if gettimeofday() is broken */ + tv->tv_sec=1; + tv->tv_usec=0; + return 0; + } + /* check if we're too late */ + if ( (tv->tv_sec>deadline->tv_sec) || + ( (tv->tv_sec==deadline->tv_sec) && (tv->tv_usec>deadline->tv_usec) ) ) + return -1; + /* update tv */ + 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; +} + +/* open a new TFILE based on the file descriptor */ +TFILE *tio_fdopen(int fd,struct timeval *readtimeout,struct timeval *writetimeout) +{ + struct tio_fileinfo *fp; + fp=(struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo)); + if (fp==NULL) + return NULL; + fp->fd=fd; + fp->readbuffer=NULL; + fp->writebuffer=NULL; + 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; +#ifdef TIO_STATS + fp->byteswritten=0; + fp->bytesread=0; +#endif + return fp; +} + +/* wait for any activity on the specified file descriptor using + the specified deadline */ +static int tio_select(int fd, int readfd, const struct timeval *deadline) +{ + struct timeval tv; + fd_set fdset; + int rv; + while (1) + { + /* prepare our filedescriptorset */ + FD_ZERO(&fdset); + FD_SET(fd,&fdset); + /* figure out the time we need to wait */ + if (tio_tv_remaining(&tv,deadline)) + { + errno=ETIME; + return -1; + } + /* wait for activity */ + if (readfd) + rv=select(FD_SETSIZE,&fdset,NULL,NULL,&tv); + else + rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv); + if (rv>0) + return 0; /* we have activity */ + else if (rv==0) + { + /* no file descriptors were available within the specified time */ + errno=ETIME; + return -1; + } + else if (errno!=EINTR) + /* some error ocurred */ + return -1; + /* we just try again on EINTR */ + } +} + +/* do a read on the file descriptor, returning the data in the buffer + if no data was read in the specified time an error is returned */ +int tio_read(TFILE *fp, void *buf, size_t count) +{ + struct timeval deadline; + int rv; + /* have a more convenient storage type for the buffer */ + uint8_t *ptr=(uint8_t *)buf; + /* ensure that we have a read buffer */ + if (fp->readbuffer==NULL) + { + fp->readbuffer=tio_buffer_new(); + if (fp->readbuffer==NULL) + return -1; /* error allocating buffer */ + } + /* build a time by which we should be finished */ + tio_tv_prepare(&deadline,&(fp->readtimeout)); + /* loop until we have returned all the needed data */ + while (1) + { + /* return any data already in the buffer */ + if (fp->readbuffer->len >= count) + { + /* we have enough data in our buffer */ + if (count>0) + { + if (ptr!=NULL) + memcpy(ptr,fp->readbuffer->buffer+fp->readbuffer->start,count); + /* adjust buffer position */ + fp->readbuffer->start+=count; + fp->readbuffer->len-=count; + } + return 0; + } + else + { + /* empty what we have and continue from there */ + if (fp->readbuffer->len > 0) + { + if (ptr!=NULL) + memcpy(ptr,fp->readbuffer->buffer+fp->readbuffer->start,fp->readbuffer->len); + count-=fp->readbuffer->len; + if (ptr!=NULL) + ptr+=fp->readbuffer->len; + } + /* flag the buffer as empty */ + fp->readbuffer->start=0; + fp->readbuffer->len=0; + } + /* wait until we have input */ + if (tio_select(fp->fd,1,&deadline)) + return -1; + /* read the input in the buffer */ + rv=read(fp->fd,fp->readbuffer->buffer,TIO_BUFFERSIZE); + /* check for errors */ + if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN))) + return -1; /* something went wrong with the read */ + /* skip the read part in the buffer */ + fp->readbuffer->len=rv; +#ifdef DEBUG_TIO_STATS + fp->bytesread+=rv; +#endif /* DEBUG_TIO_STATS */ + } +} + +/* Read and discard the specified number of bytes from the stream. */ +int tio_skip(TFILE *fp, size_t count) +{ + return tio_read(fp,NULL,count); +} + +/* write all the data in the buffer to the stream */ +int tio_flush(TFILE *fp) +{ + struct timeval deadline; + struct sigaction act,oldact; + int rv; + /* check write buffer presence */ + if (fp->writebuffer==NULL) + return 0; + /* set up sigaction */ + memset(&act,0,sizeof(struct sigaction)); + act.sa_handler=SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags=SA_RESTART; + /* build a time by which we should be finished */ + tio_tv_prepare(&deadline,&(fp->writetimeout)); + /* loop until we have written our buffer */ + while (fp->writebuffer->len > 0) + { + /* wait until we can write */ + if (tio_select(fp->fd,0,&deadline)) + return -1; + /* ignore SIGPIPE */ + if (sigaction(SIGPIPE,&act,&oldact)!=0) + return -1; /* error setting signal handler */ + /* write the buffer */ + rv=write(fp->fd,fp->writebuffer->buffer+fp->writebuffer->start,fp->writebuffer->len); + /* restore the old handler for SIGPIPE */ + if (sigaction(SIGPIPE,&oldact,NULL)!=0) + return -1; /* error restoring signal handler */ + /* check for errors */ + if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN))) + return -1; /* something went wrong with the write */ + /* skip the written part in the buffer */ + fp->writebuffer->len-=rv; +#ifdef DEBUG_TIO_STATS + fp->byteswritten+=rv; +#endif /* DEBUG_TIO_STATS */ + } + /* clear buffer and we're done */ + fp->writebuffer->start=0; + fp->writebuffer->len=0; + return 0; +} + +int tio_write(TFILE *fp, const void *buf, size_t count) +{ + size_t fr; + uint8_t *ptr=(const uint8_t *)buf; + /* ensure that we have a write buffer */ + if (fp->writebuffer==NULL) + { + fp->writebuffer=tio_buffer_new(); + if (fp->writebuffer==NULL) + return -1; /* error allocating buffer */ + } + /* keep filling the buffer until we have bufferred everything */ + while (count>0) + { + /* figure out free size in buffer */ + fr=TIO_BUFFERSIZE-(fp->writebuffer->start+fp->writebuffer->len); + if (count <= fr) + { + /* the data fits in the buffer */ + memcpy(fp->writebuffer->buffer+fp->writebuffer->start+fp->writebuffer->len,buf,count); + fp->writebuffer->len+=count; + return 0; + } + /* fill the buffer */ + memcpy(fp->writebuffer->buffer+fp->writebuffer->start+fp->writebuffer->len,buf,fr); + fp->writebuffer->len+=fr; + ptr+=fr; + count-=fr; + /* write the buffer to the stream */ + if (tio_flush(fp)<0) + return -1; + } + return 0; +} + +int tio_close(TFILE *fp) +{ + int retv; + /* write any buffered data */ + retv=tio_flush(fp); +#ifdef DEBUG_TIO_STATS + /* dump statistics to stderr */ + fprintf(stderr,"DEBUG_TIO_STATS READ=%d WRITTEN=%d\n",fp->bytesread,fp->byteswritten); +#endif /* DEBUG_TIO_STATS */ + /* free any allocated buffers */ + if (fp->readbuffer!=NULL) + tio_buffer_free(fp->readbuffer); + if (fp->writebuffer!=NULL) + tio_buffer_free(fp->writebuffer); + /* close file descriptor */ + if (close(fp->fd)) + return -1; + return retv; +} |