summaryrefslogtreecommitdiffstats
path: root/contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c')
-rw-r--r--contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c b/contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c
new file mode 100644
index 0000000..012e680
--- /dev/null
+++ b/contrib/slapd-modules/nssov/nss-pam-ldapd/tio.c
@@ -0,0 +1,520 @@
+/*
+ tio.c - timed io functions
+ This file is part of the nss-pam-ldapd library.
+
+ Copyright (C) 2007-2014 Arthur de Jong
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA
+*/
+
+#include "portable.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif /* HAVE_STDINT_H */
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <limits.h>
+#include <poll.h>
+#include <time.h>
+
+#include "tio.h"
+
+/* for platforms that don't have ETIME use ETIMEDOUT */
+#ifndef ETIME
+#define ETIME ETIMEDOUT
+#endif /* ETIME */
+
+/* structure that holds a buffer
+ the buffer contains the data that is between the application and the
+ file descriptor that is used for efficient transfer
+ the buffer is built up as follows:
+ |.....********......|
+ ^start ^size
+ ^--len--^ */
+struct tio_buffer {
+ uint8_t *buffer;
+ size_t size; /* the size of the buffer */
+ size_t maxsize; /* the maximum size of the buffer */
+ size_t start; /* the start of the data (before start is unused) */
+ size_t len; /* size of the data (from the start) */
+};
+
+/* structure that holds all the state for files */
+struct tio_fileinfo {
+ int fd;
+ struct tio_buffer readbuffer;
+ struct tio_buffer writebuffer;
+ int readtimeout;
+ int writetimeout;
+ int read_resettable; /* whether the tio_reset() function can be called */
+#ifdef DEBUG_TIO_STATS
+ /* this is used to collect statistics on the use of the streams
+ and can be used to tune the buffer sizes */
+ size_t byteswritten;
+ size_t bytesread;
+#endif /* DEBUG_TIO_STATS */
+};
+
+/* some older versions of Solaris don't provide CLOCK_MONOTONIC but do have
+ a CLOCK_HIGHRES that has the same properties we need */
+#ifndef CLOCK_MONOTONIC
+#ifdef CLOCK_HIGHRES
+#define CLOCK_MONOTONIC CLOCK_HIGHRES
+#endif /* CLOCK_HIGHRES */
+#endif /* not CLOCK_MONOTONIC */
+
+/* update the timeout to the value that is remaining before the deadline
+ returns the number of milliseconds before the deadline (or a negative
+ value of the deadline has expired) */
+static inline int tio_time_remaining(struct timespec *deadline, int timeout)
+{
+ struct timespec tv;
+ /* if this is the first call, set the deadline and return the full time */
+ if ((deadline->tv_sec == 0) && (deadline->tv_nsec == 0))
+ {
+ if (clock_gettime(CLOCK_MONOTONIC, deadline) == 0)
+ {
+ deadline->tv_sec += timeout / 1000;
+ deadline->tv_nsec += (timeout % 1000) * 1000000;
+ }
+ return timeout;
+ }
+ /* get the current time (fall back to full time on error) */
+ if (clock_gettime(CLOCK_MONOTONIC, &tv))
+ return timeout;
+ /* calculate time remaining in milliseconds */
+ return (deadline->tv_sec - tv.tv_sec) * 1000 +
+ (deadline->tv_nsec - tv.tv_nsec) / 1000000;
+}
+
+/* open a new TFILE based on the file descriptor */
+TFILE *tio_fdopen(int fd, int readtimeout, int writetimeout,
+ size_t initreadsize, size_t maxreadsize,
+ size_t initwritesize, size_t maxwritesize)
+{
+ struct tio_fileinfo *fp;
+ fp = (struct tio_fileinfo *)malloc(sizeof(struct tio_fileinfo));
+ if (fp == NULL)
+ return NULL;
+ fp->fd = fd;
+ /* initialize read buffer */
+ fp->readbuffer.buffer = (uint8_t *)malloc(initreadsize);
+ if (fp->readbuffer.buffer == NULL)
+ {
+ free(fp);
+ return NULL;
+ }
+ fp->readbuffer.size = initreadsize;
+ fp->readbuffer.maxsize = maxreadsize;
+ fp->readbuffer.start = 0;
+ fp->readbuffer.len = 0;
+ /* initialize write buffer */
+ fp->writebuffer.buffer = (uint8_t *)malloc(initwritesize);
+ if (fp->writebuffer.buffer == NULL)
+ {
+ free(fp->readbuffer.buffer);
+ free(fp);
+ return NULL;
+ }
+ fp->writebuffer.size = initwritesize;
+ fp->writebuffer.maxsize = maxwritesize;
+ fp->writebuffer.start = 0;
+ fp->writebuffer.len = 0;
+ /* initialize other attributes */
+ fp->readtimeout = readtimeout;
+ fp->writetimeout = writetimeout;
+ fp->read_resettable = 0;
+#ifdef DEBUG_TIO_STATS
+ fp->byteswritten = 0;
+ fp->bytesread = 0;
+#endif /* DEBUG_TIO_STATS */
+ return fp;
+}
+
+/* wait for any activity on the specified file descriptor using
+ the specified deadline */
+static int tio_wait(int fd, short events, int timeout,
+ struct timespec *deadline)
+{
+ int t;
+ struct pollfd fds[1];
+ int rv;
+ while (1)
+ {
+ fds[0].fd = fd;
+ fds[0].events = events;
+ /* figure out the time we need to wait */
+ if ((t = tio_time_remaining(deadline, timeout)) < 0)
+ {
+ errno = ETIME;
+ return -1;
+ }
+ /* sanity check for moving clock */
+ if (t > timeout)
+ t = timeout;
+ /* wait for activity */
+ rv = poll(fds, 1, t);
+ if (rv > 0)
+ return 0; /* we have activity */
+ else if (rv == 0)
+ {
+ /* no file descriptors were available within the specified time */
+ errno = ETIME;
+ return -1;
+ }
+ else if ((errno != EINTR) && (errno != EAGAIN))
+ /* some error occurred */
+ return -1;
+ /* we just try again on EINTR or EAGAIN */
+ }
+}
+
+/* do a read on the file descriptor, returning the data in the buffer
+ if no data was read in the specified time an error is returned */
+int tio_read(TFILE *fp, void *buf, size_t count)
+{
+ struct timespec deadline = {0, 0};
+ int rv;
+ uint8_t *tmp;
+ size_t newsz;
+ size_t len;
+ /* have a more convenient storage type for the buffer */
+ uint8_t *ptr = (uint8_t *)buf;
+ /* loop until we have returned all the needed data */
+ while (1)
+ {
+ /* check if we have enough data in the buffer */
+ if (fp->readbuffer.len >= count)
+ {
+ if (count > 0)
+ {
+ if (ptr != NULL)
+ memcpy(ptr, fp->readbuffer.buffer + fp->readbuffer.start, count);
+ /* adjust buffer position */
+ fp->readbuffer.start += count;
+ fp->readbuffer.len -= count;
+ }
+ return 0;
+ }
+ /* empty what we have and continue from there */
+ if (fp->readbuffer.len > 0)
+ {
+ if (ptr != NULL)
+ {
+ memcpy(ptr, fp->readbuffer.buffer + fp->readbuffer.start,
+ fp->readbuffer.len);
+ ptr += fp->readbuffer.len;
+ }
+ count -= fp->readbuffer.len;
+ fp->readbuffer.start += fp->readbuffer.len;
+ fp->readbuffer.len = 0;
+ }
+ /* after this point until the read fp->readbuffer.len is 0 */
+ if (!fp->read_resettable)
+ {
+ /* the stream is not resettable, re-use the buffer */
+ fp->readbuffer.start = 0;
+ }
+ else if (fp->readbuffer.start >= (fp->readbuffer.size - 4))
+ {
+ /* buffer is running empty, try to grow buffer */
+ if (fp->readbuffer.size < fp->readbuffer.maxsize)
+ {
+ newsz = fp->readbuffer.size * 2;
+ if (newsz > fp->readbuffer.maxsize)
+ newsz = fp->readbuffer.maxsize;
+ tmp = realloc(fp->readbuffer.buffer, newsz);
+ if (tmp != NULL)
+ {
+ fp->readbuffer.buffer = tmp;
+ fp->readbuffer.size = newsz;
+ }
+ }
+ /* if buffer still does not contain enough room, clear resettable */
+ if (fp->readbuffer.start >= (fp->readbuffer.size - 4))
+ {
+ fp->readbuffer.start = 0;
+ fp->read_resettable = 0;
+ }
+ }
+ /* wait until we have input */
+ if (tio_wait(fp->fd, POLLIN, fp->readtimeout, &deadline))
+ return -1;
+ /* read the input in the buffer */
+ len = fp->readbuffer.size - fp->readbuffer.start;
+#ifdef SSIZE_MAX
+ if (len > SSIZE_MAX)
+ len = SSIZE_MAX;
+#endif /* SSIZE_MAX */
+ rv = read(fp->fd, fp->readbuffer.buffer + fp->readbuffer.start, len);
+ /* check for errors */
+ if (rv == 0)
+ {
+ errno = ECONNRESET;
+ return -1;
+ }
+ else if ((rv < 0) && (errno != EINTR) && (errno != EAGAIN))
+ return -1; /* something went wrong with the read */
+ else if (rv > 0)
+ fp->readbuffer.len = rv; /* skip the read part in the buffer */
+#ifdef DEBUG_TIO_STATS
+ fp->bytesread += rv;
+#endif /* DEBUG_TIO_STATS */
+ }
+}
+
+/* Read and discard the specified number of bytes from the stream. */
+int tio_skip(TFILE *fp, size_t count)
+{
+ return tio_read(fp, NULL, count);
+}
+
+/* Read all available data from the stream and empty the read buffer. */
+int tio_skipall(TFILE *fp, int timeout)
+{
+ struct timespec deadline = {0, 0};
+ int rv;
+ size_t len;
+ /* clear the read buffer */
+ fp->readbuffer.start = 0;
+ fp->readbuffer.len = 0;
+ fp->read_resettable = 0;
+ /* read until we can't read no more */
+ len = fp->readbuffer.size;
+#ifdef SSIZE_MAX
+ if (len > SSIZE_MAX)
+ len = SSIZE_MAX;
+#endif /* SSIZE_MAX */
+ while (1)
+ {
+ /* wait until we have input */
+ if (tio_wait(fp->fd, POLLIN, timeout, &deadline))
+ return -1;
+ /* read data from the stream */
+ rv = read(fp->fd, fp->readbuffer.buffer, len);
+ if (rv == 0)
+ return 0; /* end-of-file */
+ if ((rv < 0) && (errno == EWOULDBLOCK))
+ return 0; /* we've ready everything we can without blocking */
+ if ((rv < 0) && (errno != EINTR) && (errno != EAGAIN))
+ return -1; /* something went wrong with the read */
+ }
+}
+
+/* the caller has assured us that we can write to the file descriptor
+ and we give it a shot */
+static int tio_writebuf(TFILE *fp)
+{
+ int rv;
+ /* write the buffer */
+#ifdef MSG_NOSIGNAL
+ rv = send(fp->fd, fp->writebuffer.buffer + fp->writebuffer.start,
+ fp->writebuffer.len, MSG_NOSIGNAL);
+#else /* not MSG_NOSIGNAL */
+ /* on platforms that cannot use send() with masked signals, we change the
+ signal mask and change it back after the write (note that there is a
+ race condition here) */
+ struct sigaction act, oldact;
+ /* set up sigaction */
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_sigaction = NULL;
+ act.sa_handler = SIG_IGN;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ /* ignore SIGPIPE */
+ if (sigaction(SIGPIPE, &act, &oldact) != 0)
+ return -1; /* error setting signal handler */
+ /* write the buffer */
+ rv = write(fp->fd, fp->writebuffer.buffer + fp->writebuffer.start,
+ fp->writebuffer.len);
+ /* restore the old handler for SIGPIPE */
+ if (sigaction(SIGPIPE, &oldact, NULL) != 0)
+ return -1; /* error restoring signal handler */
+#endif
+ /* check for errors */
+ if ((rv == 0) || ((rv < 0) && (errno != EINTR) && (errno != EAGAIN)))
+ return -1; /* something went wrong with the write */
+ /* skip the written part in the buffer */
+ if (rv > 0)
+ {
+ fp->writebuffer.start += rv;
+ fp->writebuffer.len -= rv;
+#ifdef DEBUG_TIO_STATS
+ fp->byteswritten += rv;
+#endif /* DEBUG_TIO_STATS */
+ /* reset start if len is 0 */
+ if (fp->writebuffer.len == 0)
+ fp->writebuffer.start = 0;
+ /* move contents of the buffer to the front if it will save enough room */
+ if (fp->writebuffer.start >= (fp->writebuffer.size / 4))
+ {
+ memmove(fp->writebuffer.buffer,
+ fp->writebuffer.buffer + fp->writebuffer.start,
+ fp->writebuffer.len);
+ fp->writebuffer.start = 0;
+ }
+ }
+ return 0;
+}
+
+/* write all the data in the buffer to the stream */
+int tio_flush(TFILE *fp)
+{
+ struct timespec deadline = {0, 0};
+ /* loop until we have written our buffer */
+ while (fp->writebuffer.len > 0)
+ {
+ /* wait until we can write */
+ if (tio_wait(fp->fd, POLLOUT, fp->writetimeout, &deadline))
+ return -1;
+ /* write one block */
+ if (tio_writebuf(fp))
+ return -1;
+ }
+ return 0;
+}
+
+/* try a single write of data in the buffer if the file descriptor
+ will accept data */
+static int tio_flush_nonblock(TFILE *fp)
+{
+ struct pollfd fds[1];
+ int rv;
+ /* see if we can write without blocking */
+ fds[0].fd = fp->fd;
+ fds[0].events = POLLOUT;
+ rv = poll(fds, 1, 0);
+ /* check if any file descriptors were ready (timeout) or we were
+ interrupted */
+ if ((rv == 0) || ((rv < 0) && ((errno == EINTR) || (errno == EAGAIN))))
+ return 0;
+ /* any other errors? */
+ if (rv < 0)
+ return -1;
+ /* so file descriptor will accept writes */
+ return tio_writebuf(fp);
+}
+
+int tio_write(TFILE *fp, const void *buf, size_t count)
+{
+ size_t fr;
+ uint8_t *tmp;
+ size_t newsz;
+ const uint8_t *ptr = (const uint8_t *)buf;
+ /* keep filling the buffer until we have buffered everything */
+ while (count > 0)
+ {
+ /* figure out free size in buffer */
+ fr = fp->writebuffer.size - (fp->writebuffer.start + fp->writebuffer.len);
+ if (count <= fr)
+ {
+ /* the data fits in the buffer */
+ memcpy(fp->writebuffer.buffer + fp->writebuffer.start +
+ fp->writebuffer.len, ptr, count);
+ fp->writebuffer.len += count;
+ return 0;
+ }
+ else if (fr > 0)
+ {
+ /* fill the buffer with data that will fit */
+ memcpy(fp->writebuffer.buffer + fp->writebuffer.start +
+ fp->writebuffer.len, ptr, fr);
+ fp->writebuffer.len += fr;
+ ptr += fr;
+ count -= fr;
+ }
+ /* try to flush some of the data that is in the buffer */
+ if (tio_flush_nonblock(fp))
+ return -1;
+ /* if we have room now, try again */
+ if (fp->writebuffer.size > (fp->writebuffer.start + fp->writebuffer.len))
+ continue;
+ /* try to grow the buffer */
+ if (fp->writebuffer.size < fp->writebuffer.maxsize)
+ {
+ newsz = fp->writebuffer.size * 2;
+ if (newsz > fp->writebuffer.maxsize)
+ newsz = fp->writebuffer.maxsize;
+ tmp = realloc(fp->writebuffer.buffer, newsz);
+ if (tmp != NULL)
+ {
+ fp->writebuffer.buffer = tmp;
+ fp->writebuffer.size = newsz;
+ continue; /* try again */
+ }
+ }
+ /* write the buffer to the stream */
+ if (tio_flush(fp))
+ return -1;
+ }
+ return 0;
+}
+
+int tio_close(TFILE *fp)
+{
+ int retv;
+ /* write any buffered data */
+ retv = tio_flush(fp);
+#ifdef DEBUG_TIO_STATS
+ /* dump statistics to stderr */
+ fprintf(stderr, "DEBUG_TIO_STATS READ=%d WRITTEN=%d\n", fp->bytesread,
+ fp->byteswritten);
+#endif /* DEBUG_TIO_STATS */
+ /* close file descriptor */
+ if (close(fp->fd))
+ retv = -1;
+ /* free any allocated buffers */
+ memset(fp->readbuffer.buffer, 0, fp->readbuffer.size);
+ memset(fp->writebuffer.buffer, 0, fp->writebuffer.size);
+ free(fp->readbuffer.buffer);
+ free(fp->writebuffer.buffer);
+ /* free the tio struct itself */
+ free(fp);
+ /* return the result of the earlier operations */
+ return retv;
+}
+
+void tio_mark(TFILE *fp)
+{
+ /* move any data in the buffer to the start of the buffer */
+ if ((fp->readbuffer.start > 0) && (fp->readbuffer.len > 0))
+ {
+ memmove(fp->readbuffer.buffer,
+ fp->readbuffer.buffer + fp->readbuffer.start, fp->readbuffer.len);
+ fp->readbuffer.start = 0;
+ }
+ /* mark the stream as resettable */
+ fp->read_resettable = 1;
+}
+
+int tio_reset(TFILE *fp)
+{
+ /* check if the stream is (still) resettable */
+ if (!fp->read_resettable)
+ return -1;
+ /* reset the buffer */
+ fp->readbuffer.len += fp->readbuffer.start;
+ fp->readbuffer.start = 0;
+ return 0;
+}