Back to index

openldap  2.4.31
tio.c
Go to the documentation of this file.
00001 /*
00002    tio.c - timed io functions
00003    This file is part of the nss-pam-ldapd library.
00004 
00005    Copyright (C) 2007, 2008 Arthur de Jong
00006 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Lesser General Public
00009    License as published by the Free Software Foundation; either
00010    version 2.1 of the License, or (at your option) any later version.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Lesser General Public License for more details.
00016 
00017    You should have received a copy of the GNU Lesser General Public
00018    License along with this library; if not, write to the Free Software
00019    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00020    02110-1301 USA
00021 */
00022 
00023 //#include "config.h"
00024 #include "portable.h"
00025 
00026 #ifdef HAVE_STDINT_H
00027 #include <stdint.h>
00028 #endif /* HAVE_STDINT_H */
00029 #include <stdlib.h>
00030 #include <unistd.h>
00031 #include <sys/time.h>
00032 #include <sys/types.h>
00033 #include <sys/socket.h>
00034 #include <errno.h>
00035 #include <string.h>
00036 #include <signal.h>
00037 #include <stdio.h>
00038 
00039 #include "tio.h"
00040 
00041 /* for platforms that don't have ETIME use ETIMEDOUT */
00042 #ifndef ETIME
00043 #define ETIME ETIMEDOUT
00044 #endif /* ETIME */
00045 
00046 /* structure that holds a buffer
00047    the buffer contains the data that is between the application and the
00048    file descriptor that is used for efficient transfer
00049    the buffer is built up as follows:
00050    |.....********......|
00051          ^start        ^size
00052          ^--len--^           */
00053 struct tio_buffer {
00054   uint8_t *buffer;
00055   size_t size;      /* the size of the buffer */
00056   size_t maxsize;   /* the maximum size of the buffer */
00057   size_t start;     /* the start of the data (before start is unused) */
00058   size_t len;       /* size of the data (from the start) */
00059 };
00060 
00061 /* structure that holds all the state for files */
00062 struct tio_fileinfo {
00063   int fd;
00064   struct tio_buffer readbuffer;
00065   struct tio_buffer writebuffer;
00066   struct timeval readtimeout;
00067   struct timeval writetimeout;
00068   int read_resettable; /* whether the tio_reset() function can be called */
00069 #ifdef DEBUG_TIO_STATS
00070   /* this is used to collect statistics on the use of the streams
00071      and can be used to tune the buffer sizes */
00072   size_t byteswritten;
00073   size_t bytesread;
00074 #endif /* DEBUG_TIO_STATS */
00075 };
00076 
00077 /* add the second timeval to the first modifing the first */
00078 static inline void tio_tv_add(struct timeval *tv1, const struct timeval *tv2)
00079 {
00080   /* BUG: we hope that this does not overflow */
00081   tv1->tv_usec+=tv2->tv_usec;
00082   if (tv1->tv_usec>1000000)
00083   {
00084     tv1->tv_usec-=1000000;
00085     tv1->tv_sec+=1;
00086   }
00087   tv1->tv_sec+=tv2->tv_sec;
00088 }
00089 
00090 /* build a timeval for comparison to when the operation should be finished */
00091 static inline void tio_tv_prepare(struct timeval *deadline, const struct timeval *timeout)
00092 {
00093   if (gettimeofday(deadline,NULL))
00094   {
00095     /* just blank it in case of errors */
00096     deadline->tv_sec=0;
00097     deadline->tv_usec=0;
00098     return;
00099   }
00100   tio_tv_add(deadline,timeout);
00101 }
00102 
00103 /* update the timeval to the value that is remaining before deadline
00104    returns non-zero if there is no more time before the deadline */
00105 static inline int tio_tv_remaining(struct timeval *tv, const struct timeval *deadline)
00106 {
00107   /* get the current time */
00108   if (gettimeofday(tv,NULL))
00109   {
00110     /* 1 second default if gettimeofday() is broken */
00111     tv->tv_sec=1;
00112     tv->tv_usec=0;
00113     return 0;
00114   }
00115   /* check if we're too late */
00116   if ( (tv->tv_sec>deadline->tv_sec) ||
00117        ( (tv->tv_sec==deadline->tv_sec) && (tv->tv_usec>deadline->tv_usec) ) )
00118     return -1;
00119   /* update tv */
00120   tv->tv_sec=deadline->tv_sec-tv->tv_sec;
00121   if (tv->tv_usec<deadline->tv_usec)
00122     tv->tv_usec=deadline->tv_usec-tv->tv_usec;
00123   else
00124   {
00125     tv->tv_sec--;
00126     tv->tv_usec=1000000+deadline->tv_usec-tv->tv_usec;
00127   }
00128   return 0;
00129 }
00130 
00131 /* open a new TFILE based on the file descriptor */
00132 TFILE *tio_fdopen(int fd,struct timeval *readtimeout,struct timeval *writetimeout,
00133                   size_t initreadsize,size_t maxreadsize,
00134                   size_t initwritesize,size_t maxwritesize)
00135 {
00136   struct tio_fileinfo *fp;
00137   fp=(struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo));
00138   if (fp==NULL)
00139     return NULL;
00140   fp->fd=fd;
00141   /* initialize read buffer */
00142   fp->readbuffer.buffer=(uint8_t *)malloc(initreadsize);
00143   if (fp->readbuffer.buffer==NULL)
00144   {
00145     free(fp);
00146     return NULL;
00147   }
00148   fp->readbuffer.size=initreadsize;
00149   fp->readbuffer.maxsize=maxreadsize;
00150   fp->readbuffer.start=0;
00151   fp->readbuffer.len=0;
00152   /* initialize write buffer */
00153   fp->writebuffer.buffer=(uint8_t *)malloc(initwritesize);
00154   if (fp->writebuffer.buffer==NULL)
00155   {
00156     free(fp->readbuffer.buffer);
00157     free(fp);
00158     return NULL;
00159   }
00160   fp->writebuffer.size=initwritesize;
00161   fp->writebuffer.maxsize=maxwritesize;
00162   fp->writebuffer.start=0;
00163   fp->writebuffer.len=0;
00164   /* initialize other attributes */
00165   fp->readtimeout.tv_sec=readtimeout->tv_sec;
00166   fp->readtimeout.tv_usec=readtimeout->tv_usec;
00167   fp->writetimeout.tv_sec=writetimeout->tv_sec;
00168   fp->writetimeout.tv_usec=writetimeout->tv_usec;
00169   fp->read_resettable=0;
00170 #ifdef DEBUG_TIO_STATS
00171   fp->byteswritten=0;
00172   fp->bytesread=0;
00173 #endif /* DEBUG_TIO_STATS */
00174   return fp;
00175 }
00176 
00177 /* wait for any activity on the specified file descriptor using
00178    the specified deadline */
00179 static int tio_select(TFILE *fp, int readfd, const struct timeval *deadline)
00180 {
00181   struct timeval tv;
00182   fd_set fdset;
00183   int rv;
00184   while (1)
00185   {
00186     /* prepare our filedescriptorset */
00187     FD_ZERO(&fdset);
00188     FD_SET(fp->fd,&fdset);
00189     /* figure out the time we need to wait */
00190     if (tio_tv_remaining(&tv,deadline))
00191     {
00192       errno=ETIME;
00193       return -1;
00194     }
00195     /* wait for activity */
00196     if (readfd)
00197     {
00198       /* santiy check for moving clock */
00199       if (tv.tv_sec>fp->readtimeout.tv_sec)
00200         tv.tv_sec=fp->readtimeout.tv_sec;
00201       rv=select(FD_SETSIZE,&fdset,NULL,NULL,&tv);
00202     }
00203     else
00204     {
00205       /* santiy check for moving clock */
00206       if (tv.tv_sec>fp->writetimeout.tv_sec)
00207         tv.tv_sec=fp->writetimeout.tv_sec;
00208       rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
00209     }
00210     if (rv>0)
00211       return 0; /* we have activity */
00212     else if (rv==0)
00213     {
00214       /* no file descriptors were available within the specified time */
00215       errno=ETIME;
00216       return -1;
00217     }
00218     else if (errno!=EINTR)
00219       /* some error ocurred */
00220       return -1;
00221     /* we just try again on EINTR */
00222   }
00223 }
00224 
00225 /* do a read on the file descriptor, returning the data in the buffer
00226    if no data was read in the specified time an error is returned */
00227 int tio_read(TFILE *fp, void *buf, size_t count)
00228 {
00229   struct timeval deadline;
00230   int rv;
00231   uint8_t *tmp;
00232   size_t newsz;
00233   /* have a more convenient storage type for the buffer */
00234   uint8_t *ptr=(uint8_t *)buf;
00235   /* build a time by which we should be finished */
00236   /* TODO: probably only set up deadline if we have to do select() */
00237   tio_tv_prepare(&deadline,&(fp->readtimeout));
00238   /* loop until we have returned all the needed data */
00239   while (1)
00240   {
00241     /* check if we have enough data in the buffer */
00242     if (fp->readbuffer.len >= count)
00243     {
00244       if (count>0)
00245       {
00246         if (ptr!=NULL)
00247           memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,count);
00248         /* adjust buffer position */
00249         fp->readbuffer.start+=count;
00250         fp->readbuffer.len-=count;
00251       }
00252       return 0;
00253     }
00254     /* empty what we have and continue from there */
00255     if (fp->readbuffer.len>0)
00256     {
00257       if (ptr!=NULL)
00258       {
00259         memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
00260         ptr+=fp->readbuffer.len;
00261       }
00262       count-=fp->readbuffer.len;
00263       fp->readbuffer.start+=fp->readbuffer.len;
00264       fp->readbuffer.len=0;
00265     }
00266     /* after this point until the read fp->readbuffer.len is 0 */
00267     if (!fp->read_resettable)
00268     {
00269       /* the stream is not resettable, re-use the buffer */
00270       fp->readbuffer.start=0;
00271     }
00272     else if (fp->readbuffer.start>=(fp->readbuffer.size-4))
00273     {
00274       /* buffer is running empty, try to grow buffer */
00275       if (fp->readbuffer.size<fp->readbuffer.maxsize)
00276       {
00277         newsz=fp->readbuffer.size*2;
00278         if (newsz>fp->readbuffer.maxsize)
00279           newsz=fp->readbuffer.maxsize;
00280         tmp=realloc(fp->readbuffer.buffer,newsz);
00281         if (tmp!=NULL)
00282         {
00283           fp->readbuffer.buffer=tmp;
00284           fp->readbuffer.size=newsz;
00285         }
00286       }
00287       /* if buffer still does not contain enough room, clear resettable */
00288       if (fp->readbuffer.start>=(fp->readbuffer.size-4))
00289       {
00290         fp->readbuffer.start=0;
00291         fp->read_resettable=0;
00292       }
00293     }
00294     /* wait until we have input */
00295     if (tio_select(fp,1,&deadline))
00296       return -1;
00297     /* read the input in the buffer */
00298     rv=read(fp->fd,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.size-fp->readbuffer.start);
00299     /* check for errors */
00300     if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
00301       return -1; /* something went wrong with the read */
00302     /* skip the read part in the buffer */
00303     fp->readbuffer.len=rv;
00304 #ifdef DEBUG_TIO_STATS
00305     fp->bytesread+=rv;
00306 #endif /* DEBUG_TIO_STATS */
00307   }
00308 }
00309 
00310 /* Read and discard the specified number of bytes from the stream. */
00311 int tio_skip(TFILE *fp, size_t count)
00312 {
00313   return tio_read(fp,NULL,count);
00314 }
00315 
00316 /* the caller has assured us that we can write to the file descriptor
00317    and we give it a shot */
00318 static int tio_writebuf(TFILE *fp)
00319 {
00320   int rv;
00321   /* write the buffer */
00322 #ifdef MSG_NOSIGNAL
00323   rv=send(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len,MSG_NOSIGNAL);
00324 #else /* not MSG_NOSIGNAL */
00325   /* on platforms that cannot use send() with masked signals, we change the
00326      signal mask and change it back after the write (note that there is a
00327      race condition here) */
00328   struct sigaction act,oldact;
00329   /* set up sigaction */
00330   memset(&act,0,sizeof(struct sigaction));
00331   act.sa_sigaction=NULL;
00332   act.sa_handler=SIG_IGN;
00333   sigemptyset(&act.sa_mask);
00334   act.sa_flags=SA_RESTART;
00335   /* ignore SIGPIPE */
00336   if (sigaction(SIGPIPE,&act,&oldact)!=0)
00337     return -1; /* error setting signal handler */
00338   /* write the buffer */
00339   rv=write(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
00340   /* restore the old handler for SIGPIPE */
00341   if (sigaction(SIGPIPE,&oldact,NULL)!=0)
00342     return -1; /* error restoring signal handler */
00343 #endif
00344   /* check for errors */
00345   if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
00346     return -1; /* something went wrong with the write */
00347   /* skip the written part in the buffer */
00348   if (rv>0)
00349   {
00350     fp->writebuffer.start+=rv;
00351     fp->writebuffer.len-=rv;
00352 #ifdef DEBUG_TIO_STATS
00353     fp->byteswritten+=rv;
00354 #endif /* DEBUG_TIO_STATS */
00355     /* reset start if len is 0 */
00356     if (fp->writebuffer.len==0)
00357       fp->writebuffer.start=0;
00358     /* move contents of the buffer to the front if it will save enough room */
00359     if (fp->writebuffer.start>=(fp->writebuffer.size/4))
00360     {
00361       memmove(fp->writebuffer.buffer,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
00362       fp->writebuffer.start=0;
00363     }
00364   }
00365   return 0;
00366 }
00367 
00368 /* write all the data in the buffer to the stream */
00369 int tio_flush(TFILE *fp)
00370 {
00371   struct timeval deadline;
00372   /* build a time by which we should be finished */
00373   tio_tv_prepare(&deadline,&(fp->writetimeout));
00374   /* loop until we have written our buffer */
00375   while (fp->writebuffer.len > 0)
00376   {
00377     /* wait until we can write */
00378     if (tio_select(fp,0,&deadline))
00379       return -1;
00380     /* write one block */
00381     if (tio_writebuf(fp))
00382       return -1;
00383   }
00384   return 0;
00385 }
00386 
00387 /* try a single write of data in the buffer if the file descriptor
00388    will accept data */
00389 static int tio_flush_nonblock(TFILE *fp)
00390 {
00391   struct timeval tv;
00392   fd_set fdset;
00393   int rv;
00394   /* prepare our filedescriptorset */
00395   FD_ZERO(&fdset);
00396   FD_SET(fp->fd,&fdset);
00397   /* set the timeout to 0 to poll */
00398   tv.tv_sec=0;
00399   tv.tv_usec=0;
00400   /* wait for activity */
00401   rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
00402   /* check if any file descriptors were ready (timeout) or we were
00403      interrupted */
00404   if ((rv==0)||((rv<0)&&(errno==EINTR)))
00405     return 0;
00406   /* any other errors? */
00407   if (rv<0)
00408     return -1;
00409   /* so file descriptor will accept writes */
00410   return tio_writebuf(fp);
00411 }
00412 
00413 int tio_write(TFILE *fp, const void *buf, size_t count)
00414 {
00415   size_t fr;
00416   uint8_t *tmp;
00417   size_t newsz;
00418   const uint8_t *ptr=(const uint8_t *)buf;
00419   /* keep filling the buffer until we have bufferred everything */
00420   while (count>0)
00421   {
00422     /* figure out free size in buffer */
00423     fr=fp->writebuffer.size-(fp->writebuffer.start+fp->writebuffer.len);
00424     if (count <= fr)
00425     {
00426       /* the data fits in the buffer */
00427       memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,count);
00428       fp->writebuffer.len+=count;
00429       return 0;
00430     }
00431     else if (fr > 0)
00432     {
00433       /* fill the buffer with data that will fit */
00434       memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,fr);
00435       fp->writebuffer.len+=fr;
00436       ptr+=fr;
00437       count-=fr;
00438     }
00439     /* try to flush some of the data that is in the buffer */
00440     if (tio_flush_nonblock(fp))
00441       return -1;
00442     /* if we have room now, try again */
00443     if (fp->writebuffer.size>(fp->writebuffer.start+fp->writebuffer.len))
00444       continue;
00445     /* try to grow the buffer */
00446     if (fp->writebuffer.size<fp->writebuffer.maxsize)
00447     {
00448       newsz=fp->writebuffer.size*2;
00449       if (newsz>fp->writebuffer.maxsize)
00450         newsz=fp->writebuffer.maxsize;
00451       tmp=realloc(fp->writebuffer.buffer,newsz);
00452       if (tmp!=NULL)
00453       {
00454         fp->writebuffer.buffer=tmp;
00455         fp->writebuffer.size=newsz;
00456         continue; /* try again */
00457       }
00458     }
00459     /* write the buffer to the stream */
00460     if (tio_flush(fp))
00461       return -1;
00462   }
00463   return 0;
00464 }
00465 
00466 int tio_close(TFILE *fp)
00467 {
00468   int retv;
00469   /* write any buffered data */
00470   retv=tio_flush(fp);
00471 #ifdef DEBUG_TIO_STATS
00472   /* dump statistics to stderr */
00473   fprintf(stderr,"DEBUG_TIO_STATS READ=%d WRITTEN=%d\n",fp->bytesread,fp->byteswritten);
00474 #endif /* DEBUG_TIO_STATS */
00475   /* close file descriptor */
00476   if (close(fp->fd))
00477     retv=-1;
00478   /* free any allocated buffers */
00479   free(fp->readbuffer.buffer);
00480   free(fp->writebuffer.buffer);
00481   /* free the tio struct itself */
00482   free(fp);
00483   /* return the result of the earlier operations */
00484   return retv;
00485 }
00486 
00487 void tio_mark(TFILE *fp)
00488 {
00489   /* move any data in the buffer to the start of the buffer */
00490   if ((fp->readbuffer.start>0)&&(fp->readbuffer.len>0))
00491   {
00492     memmove(fp->readbuffer.buffer,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
00493     fp->readbuffer.start=0;
00494   }
00495   /* mark the stream as resettable */
00496   fp->read_resettable=1;
00497 }
00498 
00499 int tio_reset(TFILE *fp)
00500 {
00501   /* check if the stream is (still) resettable */
00502   if (!fp->read_resettable)
00503     return -1;
00504   /* reset the buffer */
00505   fp->readbuffer.len+=fp->readbuffer.start;
00506   fp->readbuffer.start=0;
00507   return 0;
00508 }