1 /*
2 tio.c - timed io functions
3 This file is part of the nss-pam-ldapd library.
4
5 Copyright (C) 2007-2014 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 "portable.h"
24
25 #ifdef HAVE_STDINT_H
26 #include <stdint.h>
27 #endif /* HAVE_STDINT_H */
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <sys/time.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <limits.h>
38 #include <poll.h>
39 #include <time.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 int readtimeout;
69 int 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 /* some older versions of Solaris don't provide CLOCK_MONOTONIC but do have
80 a CLOCK_HIGHRES that has the same properties we need */
81 #ifndef CLOCK_MONOTONIC
82 #ifdef CLOCK_HIGHRES
83 #define CLOCK_MONOTONIC CLOCK_HIGHRES
84 #endif /* CLOCK_HIGHRES */
85 #endif /* not CLOCK_MONOTONIC */
86
87 /* update the timeout to the value that is remaining before the deadline
88 returns the number of milliseconds before the deadline (or a negative
89 value of the deadline has expired) */
tio_time_remaining(struct timespec * deadline,int timeout)90 static inline int tio_time_remaining(struct timespec *deadline, int timeout)
91 {
92 struct timespec tv;
93 /* if this is the first call, set the deadline and return the full time */
94 if ((deadline->tv_sec == 0) && (deadline->tv_nsec == 0))
95 {
96 if (clock_gettime(CLOCK_MONOTONIC, deadline) == 0)
97 {
98 deadline->tv_sec += timeout / 1000;
99 deadline->tv_nsec += (timeout % 1000) * 1000000;
100 }
101 return timeout;
102 }
103 /* get the current time (fall back to full time on error) */
104 if (clock_gettime(CLOCK_MONOTONIC, &tv))
105 return timeout;
106 /* calculate time remaining in milliseconds */
107 return (deadline->tv_sec - tv.tv_sec) * 1000 +
108 (deadline->tv_nsec - tv.tv_nsec) / 1000000;
109 }
110
111 /* open a new TFILE based on the file descriptor */
tio_fdopen(int fd,int readtimeout,int writetimeout,size_t initreadsize,size_t maxreadsize,size_t initwritesize,size_t maxwritesize)112 TFILE *tio_fdopen(int fd, int readtimeout, int writetimeout,
113 size_t initreadsize, size_t maxreadsize,
114 size_t initwritesize, size_t maxwritesize)
115 {
116 struct tio_fileinfo *fp;
117 fp = (struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo));
118 if (fp == NULL)
119 return NULL;
120 fp->fd = fd;
121 /* initialize read buffer */
122 fp->readbuffer.buffer = (uint8_t *)malloc(initreadsize);
123 if (fp->readbuffer.buffer == NULL)
124 {
125 free(fp);
126 return NULL;
127 }
128 fp->readbuffer.size = initreadsize;
129 fp->readbuffer.maxsize = maxreadsize;
130 fp->readbuffer.start = 0;
131 fp->readbuffer.len = 0;
132 /* initialize write buffer */
133 fp->writebuffer.buffer = (uint8_t *)malloc(initwritesize);
134 if (fp->writebuffer.buffer == NULL)
135 {
136 free(fp->readbuffer.buffer);
137 free(fp);
138 return NULL;
139 }
140 fp->writebuffer.size = initwritesize;
141 fp->writebuffer.maxsize = maxwritesize;
142 fp->writebuffer.start = 0;
143 fp->writebuffer.len = 0;
144 /* initialize other attributes */
145 fp->readtimeout = readtimeout;
146 fp->writetimeout = writetimeout;
147 fp->read_resettable = 0;
148 #ifdef DEBUG_TIO_STATS
149 fp->byteswritten = 0;
150 fp->bytesread = 0;
151 #endif /* DEBUG_TIO_STATS */
152 return fp;
153 }
154
155 /* wait for any activity on the specified file descriptor using
156 the specified deadline */
tio_wait(int fd,short events,int timeout,struct timespec * deadline)157 static int tio_wait(int fd, short events, int timeout,
158 struct timespec *deadline)
159 {
160 int t;
161 struct pollfd fds[1];
162 int rv;
163 while (1)
164 {
165 fds[0].fd = fd;
166 fds[0].events = events;
167 /* figure out the time we need to wait */
168 if ((t = tio_time_remaining(deadline, timeout)) < 0)
169 {
170 errno = ETIME;
171 return -1;
172 }
173 /* sanity check for moving clock */
174 if (t > timeout)
175 t = timeout;
176 /* wait for activity */
177 rv = poll(fds, 1, t);
178 if (rv > 0)
179 return 0; /* we have activity */
180 else if (rv == 0)
181 {
182 /* no file descriptors were available within the specified time */
183 errno = ETIME;
184 return -1;
185 }
186 else if ((errno != EINTR) && (errno != EAGAIN))
187 /* some error occurred */
188 return -1;
189 /* we just try again on EINTR or EAGAIN */
190 }
191 }
192
193 /* do a read on the file descriptor, returning the data in the buffer
194 if no data was read in the specified time an error is returned */
tio_read(TFILE * fp,void * buf,size_t count)195 int tio_read(TFILE *fp, void *buf, size_t count)
196 {
197 struct timespec deadline = {0, 0};
198 int rv;
199 uint8_t *tmp;
200 size_t newsz;
201 size_t len;
202 /* have a more convenient storage type for the buffer */
203 uint8_t *ptr = (uint8_t *)buf;
204 /* loop until we have returned all the needed data */
205 while (1)
206 {
207 /* check if we have enough data in the buffer */
208 if (fp->readbuffer.len >= count)
209 {
210 if (count > 0)
211 {
212 if (ptr != NULL)
213 memcpy(ptr, fp->readbuffer.buffer + fp->readbuffer.start, count);
214 /* adjust buffer position */
215 fp->readbuffer.start += count;
216 fp->readbuffer.len -= count;
217 }
218 return 0;
219 }
220 /* empty what we have and continue from there */
221 if (fp->readbuffer.len > 0)
222 {
223 if (ptr != NULL)
224 {
225 memcpy(ptr, fp->readbuffer.buffer + fp->readbuffer.start,
226 fp->readbuffer.len);
227 ptr += fp->readbuffer.len;
228 }
229 count -= fp->readbuffer.len;
230 fp->readbuffer.start += fp->readbuffer.len;
231 fp->readbuffer.len = 0;
232 }
233 /* after this point until the read fp->readbuffer.len is 0 */
234 if (!fp->read_resettable)
235 {
236 /* the stream is not resettable, re-use the buffer */
237 fp->readbuffer.start = 0;
238 }
239 else if (fp->readbuffer.start >= (fp->readbuffer.size - 4))
240 {
241 /* buffer is running empty, try to grow buffer */
242 if (fp->readbuffer.size < fp->readbuffer.maxsize)
243 {
244 newsz = fp->readbuffer.size * 2;
245 if (newsz > fp->readbuffer.maxsize)
246 newsz = fp->readbuffer.maxsize;
247 tmp = realloc(fp->readbuffer.buffer, newsz);
248 if (tmp != NULL)
249 {
250 fp->readbuffer.buffer = tmp;
251 fp->readbuffer.size = newsz;
252 }
253 }
254 /* if buffer still does not contain enough room, clear resettable */
255 if (fp->readbuffer.start >= (fp->readbuffer.size - 4))
256 {
257 fp->readbuffer.start = 0;
258 fp->read_resettable = 0;
259 }
260 }
261 /* wait until we have input */
262 if (tio_wait(fp->fd, POLLIN, fp->readtimeout, &deadline))
263 return -1;
264 /* read the input in the buffer */
265 len = fp->readbuffer.size - fp->readbuffer.start;
266 #ifdef SSIZE_MAX
267 if (len > SSIZE_MAX)
268 len = SSIZE_MAX;
269 #endif /* SSIZE_MAX */
270 rv = read(fp->fd, fp->readbuffer.buffer + fp->readbuffer.start, len);
271 /* check for errors */
272 if (rv == 0)
273 {
274 errno = ECONNRESET;
275 return -1;
276 }
277 else if ((rv < 0) && (errno != EINTR) && (errno != EAGAIN))
278 return -1; /* something went wrong with the read */
279 else if (rv > 0)
280 fp->readbuffer.len = rv; /* skip the read part in the buffer */
281 #ifdef DEBUG_TIO_STATS
282 fp->bytesread += rv;
283 #endif /* DEBUG_TIO_STATS */
284 }
285 }
286
287 /* Read and discard the specified number of bytes from the stream. */
tio_skip(TFILE * fp,size_t count)288 int tio_skip(TFILE *fp, size_t count)
289 {
290 return tio_read(fp, NULL, count);
291 }
292
293 /* Read all available data from the stream and empty the read buffer. */
tio_skipall(TFILE * fp,int timeout)294 int tio_skipall(TFILE *fp, int timeout)
295 {
296 struct timespec deadline = {0, 0};
297 int rv;
298 size_t len;
299 /* clear the read buffer */
300 fp->readbuffer.start = 0;
301 fp->readbuffer.len = 0;
302 fp->read_resettable = 0;
303 /* read until we can't read no more */
304 len = fp->readbuffer.size;
305 #ifdef SSIZE_MAX
306 if (len > SSIZE_MAX)
307 len = SSIZE_MAX;
308 #endif /* SSIZE_MAX */
309 while (1)
310 {
311 /* wait until we have input */
312 if (tio_wait(fp->fd, POLLIN, timeout, &deadline))
313 return -1;
314 /* read data from the stream */
315 rv = read(fp->fd, fp->readbuffer.buffer, len);
316 if (rv == 0)
317 return 0; /* end-of-file */
318 if ((rv < 0) && (errno == EWOULDBLOCK))
319 return 0; /* we've ready everything we can without blocking */
320 if ((rv < 0) && (errno != EINTR) && (errno != EAGAIN))
321 return -1; /* something went wrong with the read */
322 }
323 }
324
325 /* the caller has assured us that we can write to the file descriptor
326 and we give it a shot */
tio_writebuf(TFILE * fp)327 static int tio_writebuf(TFILE *fp)
328 {
329 int rv;
330 /* write the buffer */
331 #ifdef MSG_NOSIGNAL
332 rv = send(fp->fd, fp->writebuffer.buffer + fp->writebuffer.start,
333 fp->writebuffer.len, MSG_NOSIGNAL);
334 #else /* not MSG_NOSIGNAL */
335 /* on platforms that cannot use send() with masked signals, we change the
336 signal mask and change it back after the write (note that there is a
337 race condition here) */
338 struct sigaction act, oldact;
339 /* set up sigaction */
340 memset(&act, 0, sizeof(struct sigaction));
341 act.sa_sigaction = NULL;
342 act.sa_handler = SIG_IGN;
343 sigemptyset(&act.sa_mask);
344 act.sa_flags = SA_RESTART;
345 /* ignore SIGPIPE */
346 if (sigaction(SIGPIPE, &act, &oldact) != 0)
347 return -1; /* error setting signal handler */
348 /* write the buffer */
349 rv = write(fp->fd, fp->writebuffer.buffer + fp->writebuffer.start,
350 fp->writebuffer.len);
351 /* restore the old handler for SIGPIPE */
352 if (sigaction(SIGPIPE, &oldact, NULL) != 0)
353 return -1; /* error restoring signal handler */
354 #endif
355 /* check for errors */
356 if ((rv == 0) || ((rv < 0) && (errno != EINTR) && (errno != EAGAIN)))
357 return -1; /* something went wrong with the write */
358 /* skip the written part in the buffer */
359 if (rv > 0)
360 {
361 fp->writebuffer.start += rv;
362 fp->writebuffer.len -= rv;
363 #ifdef DEBUG_TIO_STATS
364 fp->byteswritten += rv;
365 #endif /* DEBUG_TIO_STATS */
366 /* reset start if len is 0 */
367 if (fp->writebuffer.len == 0)
368 fp->writebuffer.start = 0;
369 /* move contents of the buffer to the front if it will save enough room */
370 if (fp->writebuffer.start >= (fp->writebuffer.size / 4))
371 {
372 memmove(fp->writebuffer.buffer,
373 fp->writebuffer.buffer + fp->writebuffer.start,
374 fp->writebuffer.len);
375 fp->writebuffer.start = 0;
376 }
377 }
378 return 0;
379 }
380
381 /* write all the data in the buffer to the stream */
tio_flush(TFILE * fp)382 int tio_flush(TFILE *fp)
383 {
384 struct timespec deadline = {0, 0};
385 /* loop until we have written our buffer */
386 while (fp->writebuffer.len > 0)
387 {
388 /* wait until we can write */
389 if (tio_wait(fp->fd, POLLOUT, fp->writetimeout, &deadline))
390 return -1;
391 /* write one block */
392 if (tio_writebuf(fp))
393 return -1;
394 }
395 return 0;
396 }
397
398 /* try a single write of data in the buffer if the file descriptor
399 will accept data */
tio_flush_nonblock(TFILE * fp)400 static int tio_flush_nonblock(TFILE *fp)
401 {
402 struct pollfd fds[1];
403 int rv;
404 /* see if we can write without blocking */
405 fds[0].fd = fp->fd;
406 fds[0].events = POLLOUT;
407 rv = poll(fds, 1, 0);
408 /* check if any file descriptors were ready (timeout) or we were
409 interrupted */
410 if ((rv == 0) || ((rv < 0) && ((errno == EINTR) || (errno == EAGAIN))))
411 return 0;
412 /* any other errors? */
413 if (rv < 0)
414 return -1;
415 /* so file descriptor will accept writes */
416 return tio_writebuf(fp);
417 }
418
tio_write(TFILE * fp,const void * buf,size_t count)419 int tio_write(TFILE *fp, const void *buf, size_t count)
420 {
421 size_t fr;
422 uint8_t *tmp;
423 size_t newsz;
424 const uint8_t *ptr = (const uint8_t *)buf;
425 /* keep filling the buffer until we have buffered everything */
426 while (count > 0)
427 {
428 /* figure out free size in buffer */
429 fr = fp->writebuffer.size - (fp->writebuffer.start + fp->writebuffer.len);
430 if (count <= fr)
431 {
432 /* the data fits in the buffer */
433 memcpy(fp->writebuffer.buffer + fp->writebuffer.start +
434 fp->writebuffer.len, ptr, count);
435 fp->writebuffer.len += count;
436 return 0;
437 }
438 else if (fr > 0)
439 {
440 /* fill the buffer with data that will fit */
441 memcpy(fp->writebuffer.buffer + fp->writebuffer.start +
442 fp->writebuffer.len, ptr, fr);
443 fp->writebuffer.len += fr;
444 ptr += fr;
445 count -= fr;
446 }
447 /* try to flush some of the data that is in the buffer */
448 if (tio_flush_nonblock(fp))
449 return -1;
450 /* if we have room now, try again */
451 if (fp->writebuffer.size > (fp->writebuffer.start + fp->writebuffer.len))
452 continue;
453 /* try to grow the buffer */
454 if (fp->writebuffer.size < fp->writebuffer.maxsize)
455 {
456 newsz = fp->writebuffer.size * 2;
457 if (newsz > fp->writebuffer.maxsize)
458 newsz = fp->writebuffer.maxsize;
459 tmp = realloc(fp->writebuffer.buffer, newsz);
460 if (tmp != NULL)
461 {
462 fp->writebuffer.buffer = tmp;
463 fp->writebuffer.size = newsz;
464 continue; /* try again */
465 }
466 }
467 /* write the buffer to the stream */
468 if (tio_flush(fp))
469 return -1;
470 }
471 return 0;
472 }
473
tio_close(TFILE * fp)474 int tio_close(TFILE *fp)
475 {
476 int retv;
477 /* write any buffered data */
478 retv = tio_flush(fp);
479 #ifdef DEBUG_TIO_STATS
480 /* dump statistics to stderr */
481 fprintf(stderr, "DEBUG_TIO_STATS READ=%d WRITTEN=%d\n", fp->bytesread,
482 fp->byteswritten);
483 #endif /* DEBUG_TIO_STATS */
484 /* close file descriptor */
485 if (close(fp->fd))
486 retv = -1;
487 /* free any allocated buffers */
488 memset(fp->readbuffer.buffer, 0, fp->readbuffer.size);
489 memset(fp->writebuffer.buffer, 0, fp->writebuffer.size);
490 free(fp->readbuffer.buffer);
491 free(fp->writebuffer.buffer);
492 /* free the tio struct itself */
493 free(fp);
494 /* return the result of the earlier operations */
495 return retv;
496 }
497
tio_mark(TFILE * fp)498 void tio_mark(TFILE *fp)
499 {
500 /* move any data in the buffer to the start of the buffer */
501 if ((fp->readbuffer.start > 0) && (fp->readbuffer.len > 0))
502 {
503 memmove(fp->readbuffer.buffer,
504 fp->readbuffer.buffer + fp->readbuffer.start, fp->readbuffer.len);
505 fp->readbuffer.start = 0;
506 }
507 /* mark the stream as resettable */
508 fp->read_resettable = 1;
509 }
510
tio_reset(TFILE * fp)511 int tio_reset(TFILE *fp)
512 {
513 /* check if the stream is (still) resettable */
514 if (!fp->read_resettable)
515 return -1;
516 /* reset the buffer */
517 fp->readbuffer.len += fp->readbuffer.start;
518 fp->readbuffer.start = 0;
519 return 0;
520 }
521