| /* Stream/packet buffer API implementation |
| * Copyright (c) 2014-2015 Timo Teräs |
| * |
| * This file is free software: you may copy, redistribute and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include "zassert.h" |
| #include "zbuf.h" |
| #include "memory.h" |
| #include "memtypes.h" |
| #include "nhrpd.h" |
| |
| #define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) |
| |
| struct zbuf *zbuf_alloc(size_t size) |
| { |
| struct zbuf *zb; |
| |
| zb = XMALLOC(MTYPE_STREAM_DATA, sizeof(*zb) + size); |
| if (!zb) |
| return NULL; |
| |
| zbuf_init(zb, zb+1, size, 0); |
| zb->allocated = 1; |
| |
| return zb; |
| } |
| |
| void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen) |
| { |
| *zb = (struct zbuf) { |
| .buf = buf, |
| .end = (uint8_t *)buf + len, |
| .head = buf, |
| .tail = (uint8_t *)buf + datalen, |
| }; |
| } |
| |
| void zbuf_free(struct zbuf *zb) |
| { |
| if (zb->allocated) |
| XFREE(MTYPE_STREAM_DATA, zb); |
| } |
| |
| void zbuf_reset(struct zbuf *zb) |
| { |
| zb->head = zb->tail = zb->buf; |
| zb->error = 0; |
| } |
| |
| void zbuf_reset_head(struct zbuf *zb, void *ptr) |
| { |
| zassert((void*)zb->buf <= ptr && ptr <= (void*)zb->tail); |
| zb->head = ptr; |
| } |
| |
| static void zbuf_remove_headroom(struct zbuf *zb) |
| { |
| ssize_t headroom = zbuf_headroom(zb); |
| if (!headroom) |
| return; |
| memmove(zb->buf, zb->head, zbuf_used(zb)); |
| zb->head -= headroom; |
| zb->tail -= headroom; |
| } |
| |
| ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen) |
| { |
| ssize_t r; |
| |
| if (zb->error) |
| return -3; |
| |
| zbuf_remove_headroom(zb); |
| if (maxlen > zbuf_tailroom(zb)) |
| maxlen = zbuf_tailroom(zb); |
| |
| r = read(fd, zb->tail, maxlen); |
| if (r > 0) zb->tail += r; |
| else if (r == 0) r = -2; |
| else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; |
| |
| return r; |
| } |
| |
| ssize_t zbuf_write(struct zbuf *zb, int fd) |
| { |
| ssize_t r; |
| |
| if (zb->error) |
| return -3; |
| |
| r = write(fd, zb->head, zbuf_used(zb)); |
| if (r > 0) { |
| zb->head += r; |
| if (zb->head == zb->tail) |
| zbuf_reset(zb); |
| } |
| else if (r == 0) r = -2; |
| else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; |
| |
| return r; |
| } |
| |
| ssize_t zbuf_recv(struct zbuf *zb, int fd) |
| { |
| ssize_t r; |
| |
| if (zb->error) |
| return -3; |
| |
| zbuf_remove_headroom(zb); |
| r = recv(fd, zb->tail, zbuf_tailroom(zb), 0); |
| if (r > 0) zb->tail += r; |
| else if (r == 0) r = -2; |
| else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; |
| return r; |
| } |
| |
| ssize_t zbuf_send(struct zbuf *zb, int fd) |
| { |
| ssize_t r; |
| |
| if (zb->error) |
| return -3; |
| |
| r = send(fd, zb->head, zbuf_used(zb), 0); |
| if (r >= 0) |
| zbuf_reset(zb); |
| |
| return r; |
| } |
| |
| void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg) |
| { |
| size_t seplen = strlen(sep), len; |
| uint8_t *ptr; |
| |
| ptr = memmem(zb->head, zbuf_used(zb), sep, seplen); |
| if (!ptr) return NULL; |
| |
| len = ptr - zb->head + seplen; |
| zbuf_init(msg, zbuf_pulln(zb, len), len, len); |
| return msg->head; |
| } |
| |
| void zbufq_init(struct zbuf_queue *zbq) |
| { |
| *zbq = (struct zbuf_queue) { |
| .queue_head = LIST_INITIALIZER(zbq->queue_head), |
| }; |
| } |
| |
| void zbufq_reset(struct zbuf_queue *zbq) |
| { |
| struct zbuf *buf, *bufn; |
| |
| list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) { |
| list_del(&buf->queue_list); |
| zbuf_free(buf); |
| } |
| } |
| |
| void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb) |
| { |
| list_add_tail(&zb->queue_list, &zbq->queue_head); |
| } |
| |
| int zbufq_write(struct zbuf_queue *zbq, int fd) |
| { |
| struct iovec iov[16]; |
| struct zbuf *zb, *zbn; |
| ssize_t r; |
| size_t iovcnt = 0; |
| |
| list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) { |
| iov[iovcnt++] = (struct iovec) { |
| .iov_base = zb->head, |
| .iov_len = zbuf_used(zb), |
| }; |
| if (iovcnt >= ZEBRA_NUM_OF(iov)) |
| break; |
| } |
| |
| r = writev(fd, iov, iovcnt); |
| if (r < 0) |
| return r; |
| |
| list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) { |
| if (r < (ssize_t)zbuf_used(zb)) { |
| zb->head += r; |
| return 1; |
| } |
| |
| r -= zbuf_used(zb); |
| list_del(&zb->queue_list); |
| zbuf_free(zb); |
| } |
| |
| return 0; |
| } |
| |
| void zbuf_copy(struct zbuf *zdst, struct zbuf *zsrc, size_t len) |
| { |
| const void *src; |
| void *dst; |
| |
| dst = zbuf_pushn(zdst, len); |
| src = zbuf_pulln(zsrc, len); |
| if (!dst || !src) return; |
| memcpy(dst, src, len); |
| } |