diff options
Diffstat (limited to 'lib/imsg-buffer.c')
-rw-r--r-- | lib/imsg-buffer.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/lib/imsg-buffer.c b/lib/imsg-buffer.c new file mode 100644 index 0000000..4f041ff --- /dev/null +++ b/lib/imsg-buffer.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + */ + +#include <zebra.h> + +#include "queue.h" +#include "imsg.h" + +static int ibuf_realloc(struct ibuf *, size_t); +static void ibuf_enqueue(struct msgbuf *, struct ibuf *); +static void ibuf_dequeue(struct msgbuf *, struct ibuf *); + +struct ibuf *ibuf_open(size_t len) +{ + struct ibuf *buf; + + if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) + return NULL; + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return NULL; + } + buf->size = buf->max = len; + buf->fd = -1; + + return (buf); +} + +struct ibuf *ibuf_dynamic(size_t len, size_t max) +{ + struct ibuf *buf; + + if (max < len) + return NULL; + + if ((buf = ibuf_open(len)) == NULL) + return NULL; + + if (max > 0) + buf->max = max; + + return (buf); +} + +static int ibuf_realloc(struct ibuf *buf, size_t len) +{ + uint8_t *b; + + /* on static buffers max is eq size and so the following fails */ + if (buf->wpos + len > buf->max) { + errno = ERANGE; + return -1; + } + + b = realloc(buf->buf, buf->wpos + len); + if (b == NULL) + return -1; + buf->buf = b; + buf->size = buf->wpos + len; + + return 0; +} + +int ibuf_add(struct ibuf *buf, const void *data, size_t len) +{ + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return -1; + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return 0; +} + +void *ibuf_reserve(struct ibuf *buf, size_t len) +{ + void *b; + + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return NULL; + + b = buf->buf + buf->wpos; + buf->wpos += len; + return (b); +} + +void *ibuf_seek(struct ibuf *buf, size_t pos, size_t len) +{ + /* only allowed to seek in already written parts */ + if (pos + len > buf->wpos) + return NULL; + + return (buf->buf + pos); +} + +size_t ibuf_size(struct ibuf *buf) +{ + return (buf->wpos); +} + +size_t ibuf_left(struct ibuf *buf) +{ + return (buf->max - buf->wpos); +} + +void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) +{ + ibuf_enqueue(msgbuf, buf); +} + +int ibuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + + memset(&iov, 0, sizeof(iov)); + TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + } + +again: + if ((n = writev(msgbuf->fd, iov, i)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return -1; + } + + if (n == 0) { /* connection closed */ + errno = 0; + return 0; + } + + msgbuf_drain(msgbuf, n); + + return 1; +} + +void ibuf_free(struct ibuf *buf) +{ + if (buf == NULL) + return; + free(buf->buf); + free(buf); +} + +void msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void msgbuf_drain(struct msgbuf *msgbuf, size_t n) +{ + struct ibuf *buf, *next; + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->wpos) { + n -= buf->wpos - buf->rpos; + + TAILQ_REMOVE(&msgbuf->bufs, buf, entry); + ibuf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } +} + +void msgbuf_clear(struct msgbuf *msgbuf) +{ + struct ibuf *buf; + + while ((buf = TAILQ_POP_FIRST(&msgbuf->bufs, entry)) != NULL) + ibuf_dequeue(msgbuf, buf); +} + +int msgbuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + + memset(&iov, 0, sizeof(iov)); + memset(&msg, 0, sizeof(msg)); + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + if (buf->fd != -1) + break; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmsg), &buf->fd, sizeof(int)); + } + +again: + if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return -1; + } + + if (n == 0) { /* connection closed */ + errno = 0; + return 0; + } + + /* + * assumption: fd got sent if sendmsg sent anything + * this works because fds are passed one at a time + */ + if (buf != NULL && buf->fd != -1) { + close(buf->fd); + buf->fd = -1; + } + + msgbuf_drain(msgbuf, n); + + return 1; +} + +static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); + msgbuf->queued++; +} + +static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + /* TAILQ_REMOVE done by caller */ + if (buf->fd != -1) + close(buf->fd); + + msgbuf->queued--; + ibuf_free(buf); +} |