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