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