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