]> git.sur5r.net Git - openldap/blob - contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c
Merge remote branch 'origin/mdb.master' into OPENLDAP_REL_ENG_2_4
[openldap] / contrib / slapd-modules / nssov / nss-pam-ldapd / tio.c
1 /*
2    tio.c - timed io functions
3    This file is part of the nss-pam-ldapd library.
4
5    Copyright (C) 2007, 2008 Arthur de Jong
6
7    This library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Lesser General Public
9    License as published by the Free Software Foundation; either
10    version 2.1 of the License, or (at your option) any later version.
11
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16
17    You should have received a copy of the GNU Lesser General Public
18    License along with this library; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301 USA
21 */
22
23 //#include "config.h"
24 #include "portable.h"
25
26 #ifdef HAVE_STDINT_H
27 #include <stdint.h>
28 #endif /* HAVE_STDINT_H */
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <signal.h>
37 #include <stdio.h>
38
39 #include "tio.h"
40
41 /* for platforms that don't have ETIME use ETIMEDOUT */
42 #ifndef ETIME
43 #define ETIME ETIMEDOUT
44 #endif /* ETIME */
45
46 /* structure that holds a buffer
47    the buffer contains the data that is between the application and the
48    file descriptor that is used for efficient transfer
49    the buffer is built up as follows:
50    |.....********......|
51          ^start        ^size
52          ^--len--^           */
53 struct tio_buffer {
54   uint8_t *buffer;
55   size_t size;      /* the size of the buffer */
56   size_t maxsize;   /* the maximum size of the buffer */
57   size_t start;     /* the start of the data (before start is unused) */
58   size_t len;       /* size of the data (from the start) */
59 };
60
61 /* structure that holds all the state for files */
62 struct tio_fileinfo {
63   int fd;
64   struct tio_buffer readbuffer;
65   struct tio_buffer writebuffer;
66   struct timeval readtimeout;
67   struct timeval writetimeout;
68   int read_resettable; /* whether the tio_reset() function can be called */
69 #ifdef DEBUG_TIO_STATS
70   /* this is used to collect statistics on the use of the streams
71      and can be used to tune the buffer sizes */
72   size_t byteswritten;
73   size_t bytesread;
74 #endif /* DEBUG_TIO_STATS */
75 };
76
77 /* add the second timeval to the first modifing the first */
78 static inline void tio_tv_add(struct timeval *tv1, const struct timeval *tv2)
79 {
80   /* BUG: we hope that this does not overflow */
81   tv1->tv_usec+=tv2->tv_usec;
82   if (tv1->tv_usec>1000000)
83   {
84     tv1->tv_usec-=1000000;
85     tv1->tv_sec+=1;
86   }
87   tv1->tv_sec+=tv2->tv_sec;
88 }
89
90 /* build a timeval for comparison to when the operation should be finished */
91 static inline void tio_tv_prepare(struct timeval *deadline, const struct timeval *timeout)
92 {
93   if (gettimeofday(deadline,NULL))
94   {
95     /* just blank it in case of errors */
96     deadline->tv_sec=0;
97     deadline->tv_usec=0;
98     return;
99   }
100   tio_tv_add(deadline,timeout);
101 }
102
103 /* update the timeval to the value that is remaining before deadline
104    returns non-zero if there is no more time before the deadline */
105 static inline int tio_tv_remaining(struct timeval *tv, const struct timeval *deadline)
106 {
107   /* get the current time */
108   if (gettimeofday(tv,NULL))
109   {
110     /* 1 second default if gettimeofday() is broken */
111     tv->tv_sec=1;
112     tv->tv_usec=0;
113     return 0;
114   }
115   /* check if we're too late */
116   if ( (tv->tv_sec>deadline->tv_sec) ||
117        ( (tv->tv_sec==deadline->tv_sec) && (tv->tv_usec>deadline->tv_usec) ) )
118     return -1;
119   /* update tv */
120   tv->tv_sec=deadline->tv_sec-tv->tv_sec;
121   if (tv->tv_usec<deadline->tv_usec)
122     tv->tv_usec=deadline->tv_usec-tv->tv_usec;
123   else
124   {
125     tv->tv_sec--;
126     tv->tv_usec=1000000+deadline->tv_usec-tv->tv_usec;
127   }
128   return 0;
129 }
130
131 /* open a new TFILE based on the file descriptor */
132 TFILE *tio_fdopen(int fd,struct timeval *readtimeout,struct timeval *writetimeout,
133                   size_t initreadsize,size_t maxreadsize,
134                   size_t initwritesize,size_t maxwritesize)
135 {
136   struct tio_fileinfo *fp;
137   fp=(struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo));
138   if (fp==NULL)
139     return NULL;
140   fp->fd=fd;
141   /* initialize read buffer */
142   fp->readbuffer.buffer=(uint8_t *)malloc(initreadsize);
143   if (fp->readbuffer.buffer==NULL)
144   {
145     free(fp);
146     return NULL;
147   }
148   fp->readbuffer.size=initreadsize;
149   fp->readbuffer.maxsize=maxreadsize;
150   fp->readbuffer.start=0;
151   fp->readbuffer.len=0;
152   /* initialize write buffer */
153   fp->writebuffer.buffer=(uint8_t *)malloc(initwritesize);
154   if (fp->writebuffer.buffer==NULL)
155   {
156     free(fp->readbuffer.buffer);
157     free(fp);
158     return NULL;
159   }
160   fp->writebuffer.size=initwritesize;
161   fp->writebuffer.maxsize=maxwritesize;
162   fp->writebuffer.start=0;
163   fp->writebuffer.len=0;
164   /* initialize other attributes */
165   fp->readtimeout.tv_sec=readtimeout->tv_sec;
166   fp->readtimeout.tv_usec=readtimeout->tv_usec;
167   fp->writetimeout.tv_sec=writetimeout->tv_sec;
168   fp->writetimeout.tv_usec=writetimeout->tv_usec;
169   fp->read_resettable=0;
170 #ifdef DEBUG_TIO_STATS
171   fp->byteswritten=0;
172   fp->bytesread=0;
173 #endif /* DEBUG_TIO_STATS */
174   return fp;
175 }
176
177 /* wait for any activity on the specified file descriptor using
178    the specified deadline */
179 static int tio_select(TFILE *fp, int readfd, const struct timeval *deadline)
180 {
181   struct timeval tv;
182   fd_set fdset;
183   int rv;
184   while (1)
185   {
186     /* prepare our filedescriptorset */
187     FD_ZERO(&fdset);
188     FD_SET(fp->fd,&fdset);
189     /* figure out the time we need to wait */
190     if (tio_tv_remaining(&tv,deadline))
191     {
192       errno=ETIME;
193       return -1;
194     }
195     /* wait for activity */
196     if (readfd)
197     {
198       /* santiy check for moving clock */
199       if (tv.tv_sec>fp->readtimeout.tv_sec)
200         tv.tv_sec=fp->readtimeout.tv_sec;
201       rv=select(FD_SETSIZE,&fdset,NULL,NULL,&tv);
202     }
203     else
204     {
205       /* santiy check for moving clock */
206       if (tv.tv_sec>fp->writetimeout.tv_sec)
207         tv.tv_sec=fp->writetimeout.tv_sec;
208       rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
209     }
210     if (rv>0)
211       return 0; /* we have activity */
212     else if (rv==0)
213     {
214       /* no file descriptors were available within the specified time */
215       errno=ETIME;
216       return -1;
217     }
218     else if (errno!=EINTR)
219       /* some error ocurred */
220       return -1;
221     /* we just try again on EINTR */
222   }
223 }
224
225 /* do a read on the file descriptor, returning the data in the buffer
226    if no data was read in the specified time an error is returned */
227 int tio_read(TFILE *fp, void *buf, size_t count)
228 {
229   struct timeval deadline;
230   int rv;
231   uint8_t *tmp;
232   size_t newsz;
233   /* have a more convenient storage type for the buffer */
234   uint8_t *ptr=(uint8_t *)buf;
235   /* build a time by which we should be finished */
236   /* TODO: probably only set up deadline if we have to do select() */
237   tio_tv_prepare(&deadline,&(fp->readtimeout));
238   /* loop until we have returned all the needed data */
239   while (1)
240   {
241     /* check if we have enough data in the buffer */
242     if (fp->readbuffer.len >= count)
243     {
244       if (count>0)
245       {
246         if (ptr!=NULL)
247           memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,count);
248         /* adjust buffer position */
249         fp->readbuffer.start+=count;
250         fp->readbuffer.len-=count;
251       }
252       return 0;
253     }
254     /* empty what we have and continue from there */
255     if (fp->readbuffer.len>0)
256     {
257       if (ptr!=NULL)
258       {
259         memcpy(ptr,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
260         ptr+=fp->readbuffer.len;
261       }
262       count-=fp->readbuffer.len;
263       fp->readbuffer.start+=fp->readbuffer.len;
264       fp->readbuffer.len=0;
265     }
266     /* after this point until the read fp->readbuffer.len is 0 */
267     if (!fp->read_resettable)
268     {
269       /* the stream is not resettable, re-use the buffer */
270       fp->readbuffer.start=0;
271     }
272     else if (fp->readbuffer.start>=(fp->readbuffer.size-4))
273     {
274       /* buffer is running empty, try to grow buffer */
275       if (fp->readbuffer.size<fp->readbuffer.maxsize)
276       {
277         newsz=fp->readbuffer.size*2;
278         if (newsz>fp->readbuffer.maxsize)
279           newsz=fp->readbuffer.maxsize;
280         tmp=realloc(fp->readbuffer.buffer,newsz);
281         if (tmp!=NULL)
282         {
283           fp->readbuffer.buffer=tmp;
284           fp->readbuffer.size=newsz;
285         }
286       }
287       /* if buffer still does not contain enough room, clear resettable */
288       if (fp->readbuffer.start>=(fp->readbuffer.size-4))
289       {
290         fp->readbuffer.start=0;
291         fp->read_resettable=0;
292       }
293     }
294     /* wait until we have input */
295     if (tio_select(fp,1,&deadline))
296       return -1;
297     /* read the input in the buffer */
298     rv=read(fp->fd,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.size-fp->readbuffer.start);
299     /* check for errors */
300     if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
301       return -1; /* something went wrong with the read */
302     /* skip the read part in the buffer */
303     fp->readbuffer.len=rv;
304 #ifdef DEBUG_TIO_STATS
305     fp->bytesread+=rv;
306 #endif /* DEBUG_TIO_STATS */
307   }
308 }
309
310 /* Read and discard the specified number of bytes from the stream. */
311 int tio_skip(TFILE *fp, size_t count)
312 {
313   return tio_read(fp,NULL,count);
314 }
315
316 /* the caller has assured us that we can write to the file descriptor
317    and we give it a shot */
318 static int tio_writebuf(TFILE *fp)
319 {
320   int rv;
321   /* write the buffer */
322 #ifdef MSG_NOSIGNAL
323   rv=send(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len,MSG_NOSIGNAL);
324 #else /* not MSG_NOSIGNAL */
325   /* on platforms that cannot use send() with masked signals, we change the
326      signal mask and change it back after the write (note that there is a
327      race condition here) */
328   struct sigaction act,oldact;
329   /* set up sigaction */
330   memset(&act,0,sizeof(struct sigaction));
331   act.sa_sigaction=NULL;
332   act.sa_handler=SIG_IGN;
333   sigemptyset(&act.sa_mask);
334   act.sa_flags=SA_RESTART;
335   /* ignore SIGPIPE */
336   if (sigaction(SIGPIPE,&act,&oldact)!=0)
337     return -1; /* error setting signal handler */
338   /* write the buffer */
339   rv=write(fp->fd,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
340   /* restore the old handler for SIGPIPE */
341   if (sigaction(SIGPIPE,&oldact,NULL)!=0)
342     return -1; /* error restoring signal handler */
343 #endif
344   /* check for errors */
345   if ((rv==0)||((rv<0)&&(errno!=EINTR)&&(errno!=EAGAIN)))
346     return -1; /* something went wrong with the write */
347   /* skip the written part in the buffer */
348   if (rv>0)
349   {
350     fp->writebuffer.start+=rv;
351     fp->writebuffer.len-=rv;
352 #ifdef DEBUG_TIO_STATS
353     fp->byteswritten+=rv;
354 #endif /* DEBUG_TIO_STATS */
355     /* reset start if len is 0 */
356     if (fp->writebuffer.len==0)
357       fp->writebuffer.start=0;
358     /* move contents of the buffer to the front if it will save enough room */
359     if (fp->writebuffer.start>=(fp->writebuffer.size/4))
360     {
361       memmove(fp->writebuffer.buffer,fp->writebuffer.buffer+fp->writebuffer.start,fp->writebuffer.len);
362       fp->writebuffer.start=0;
363     }
364   }
365   return 0;
366 }
367
368 /* write all the data in the buffer to the stream */
369 int tio_flush(TFILE *fp)
370 {
371   struct timeval deadline;
372   /* build a time by which we should be finished */
373   tio_tv_prepare(&deadline,&(fp->writetimeout));
374   /* loop until we have written our buffer */
375   while (fp->writebuffer.len > 0)
376   {
377     /* wait until we can write */
378     if (tio_select(fp,0,&deadline))
379       return -1;
380     /* write one block */
381     if (tio_writebuf(fp))
382       return -1;
383   }
384   return 0;
385 }
386
387 /* try a single write of data in the buffer if the file descriptor
388    will accept data */
389 static int tio_flush_nonblock(TFILE *fp)
390 {
391   struct timeval tv;
392   fd_set fdset;
393   int rv;
394   /* prepare our filedescriptorset */
395   FD_ZERO(&fdset);
396   FD_SET(fp->fd,&fdset);
397   /* set the timeout to 0 to poll */
398   tv.tv_sec=0;
399   tv.tv_usec=0;
400   /* wait for activity */
401   rv=select(FD_SETSIZE,NULL,&fdset,NULL,&tv);
402   /* check if any file descriptors were ready (timeout) or we were
403      interrupted */
404   if ((rv==0)||((rv<0)&&(errno==EINTR)))
405     return 0;
406   /* any other errors? */
407   if (rv<0)
408     return -1;
409   /* so file descriptor will accept writes */
410   return tio_writebuf(fp);
411 }
412
413 int tio_write(TFILE *fp, const void *buf, size_t count)
414 {
415   size_t fr;
416   uint8_t *tmp;
417   size_t newsz;
418   const uint8_t *ptr=(const uint8_t *)buf;
419   /* keep filling the buffer until we have bufferred everything */
420   while (count>0)
421   {
422     /* figure out free size in buffer */
423     fr=fp->writebuffer.size-(fp->writebuffer.start+fp->writebuffer.len);
424     if (count <= fr)
425     {
426       /* the data fits in the buffer */
427       memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,count);
428       fp->writebuffer.len+=count;
429       return 0;
430     }
431     else if (fr > 0)
432     {
433       /* fill the buffer with data that will fit */
434       memcpy(fp->writebuffer.buffer+fp->writebuffer.start+fp->writebuffer.len,ptr,fr);
435       fp->writebuffer.len+=fr;
436       ptr+=fr;
437       count-=fr;
438     }
439     /* try to flush some of the data that is in the buffer */
440     if (tio_flush_nonblock(fp))
441       return -1;
442     /* if we have room now, try again */
443     if (fp->writebuffer.size>(fp->writebuffer.start+fp->writebuffer.len))
444       continue;
445     /* try to grow the buffer */
446     if (fp->writebuffer.size<fp->writebuffer.maxsize)
447     {
448       newsz=fp->writebuffer.size*2;
449       if (newsz>fp->writebuffer.maxsize)
450         newsz=fp->writebuffer.maxsize;
451       tmp=realloc(fp->writebuffer.buffer,newsz);
452       if (tmp!=NULL)
453       {
454         fp->writebuffer.buffer=tmp;
455         fp->writebuffer.size=newsz;
456         continue; /* try again */
457       }
458     }
459     /* write the buffer to the stream */
460     if (tio_flush(fp))
461       return -1;
462   }
463   return 0;
464 }
465
466 int tio_close(TFILE *fp)
467 {
468   int retv;
469   /* write any buffered data */
470   retv=tio_flush(fp);
471 #ifdef DEBUG_TIO_STATS
472   /* dump statistics to stderr */
473   fprintf(stderr,"DEBUG_TIO_STATS READ=%d WRITTEN=%d\n",fp->bytesread,fp->byteswritten);
474 #endif /* DEBUG_TIO_STATS */
475   /* close file descriptor */
476   if (close(fp->fd))
477     retv=-1;
478   /* free any allocated buffers */
479   free(fp->readbuffer.buffer);
480   free(fp->writebuffer.buffer);
481   /* free the tio struct itself */
482   free(fp);
483   /* return the result of the earlier operations */
484   return retv;
485 }
486
487 void tio_mark(TFILE *fp)
488 {
489   /* move any data in the buffer to the start of the buffer */
490   if ((fp->readbuffer.start>0)&&(fp->readbuffer.len>0))
491   {
492     memmove(fp->readbuffer.buffer,fp->readbuffer.buffer+fp->readbuffer.start,fp->readbuffer.len);
493     fp->readbuffer.start=0;
494   }
495   /* mark the stream as resettable */
496   fp->read_resettable=1;
497 }
498
499 int tio_reset(TFILE *fp)
500 {
501   /* check if the stream is (still) resettable */
502   if (!fp->read_resettable)
503     return -1;
504   /* reset the buffer */
505   fp->readbuffer.len+=fp->readbuffer.start;
506   fp->readbuffer.start=0;
507   return 0;
508 }