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--^           */
53struct 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 */
62struct 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 */
78static 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 */
91static 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 */
105static 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 */
132TFILE *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 */
179static 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 */
227int 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. */
311int 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 */
318static 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 */
369int 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 */
389static 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
413int 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
466int 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
487void 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
499int 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}
509