diff --git a/src/util-io.h b/src/util-io.h index 1c5e44c..0efd545 100644 --- a/src/util-io.h +++ b/src/util-io.h @@ -33,6 +33,7 @@ #include #include +#include "util-macros.h" #include "util-mem.h" /** @@ -194,6 +195,7 @@ struct iobuf { size_t sz; size_t len; char *data; + int fds[32]; }; /** @@ -214,6 +216,11 @@ iobuf_new(size_t size) .data = data, }; + int *fd; + ARRAY_FOR_EACH(buf->fds, fd) { + *fd = -1; + } + return buf; } @@ -254,6 +261,19 @@ iobuf_data(struct iobuf *buf) return buf->data; } +/** + * Return the next available file descriptor in this buffer or -1. + * The fd is removed from this buffer and belongs to the caller. + */ +static inline int +iobuf_take_fd(struct iobuf *buf) +{ + int fd = buf->fds[0]; + if (fd != -1) + memmove(buf->fds, buf->fds + 1, ARRAY_LENGTH(buf->fds) - 1); + return fd; +} + /** * Remove the data bytes from the buffer. The caller must free() the data. * The buffer state is the same as iobuf_new() after this call. @@ -315,6 +335,48 @@ iobuf_append_from_fd(struct iobuf *buf, int fd) return nread == 0 ? rc : (int)nread; } +/** + * Append all available data from the file descriptor to the pointer. The + * file descriptor shold be in O_NONBLOCK or this call will block. If the + * data exceeds the current buffer size it is resized automatically. + * + * Any file descriptors passed through the fd are placed + * + * @return The number of bytes read or a negative errno on failure. Zero + * indicates EOF. + */ +static inline int +iobuf_recv_from_fd(struct iobuf *buf, int fd) +{ + char data[1024]; + size_t nread = 0; + ssize_t rc; + do { + _cleanup_free_ int *fds = NULL; + rc = xread_with_fds(fd, data, sizeof(data), &fds); + if (rc == 0 || rc == -EAGAIN) { + break; + } else if (rc < 0) { + return rc; + } + iobuf_append(buf, data, rc); + + if (fds) { + int *fd = fds; + for (size_t idx = 0; *fd != -1 && idx < ARRAY_LENGTH(buf->fds) - 1; idx++) { + if (buf->fds[idx] == -1) { + buf->fds[idx] = *fd; + fd++; + } + } + } + + nread += rc; + } while (rc == sizeof(data)); + + return nread == 0 ? rc : (int)nread; +} + /** * Release the memory associated with this iobuf. Use iobuf_take_data() * prevent the data from being free()d. @@ -327,6 +389,10 @@ iobuf_free(struct iobuf *buf) buf->sz = 0; buf->len = 0; buf->data = NULL; + + int fd; + while ((fd = iobuf_take_fd(buf)) != -1) + xclose(fd); free(buf); } return NULL; diff --git a/test/iotest.c b/test/iotest.c index 62e0864..54630f5 100644 --- a/test/iotest.c +++ b/test/iotest.c @@ -166,6 +166,34 @@ MUNIT_TEST(test_iobuf_append_fd) return MUNIT_OK; } +MUNIT_TEST(test_iobuf_recv_fd) +{ + int fds[2]; + int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); + munit_assert_int(rc, ==, 0); + + _cleanup_close_ int left = fds[0]; + _cleanup_close_ int right = fds[1]; + _cleanup_fclose_ FILE *fp = tmpfile(); + + /* actual message data to be sent */ + char data[] = "some data\n"; + + /* Send the fd from left to right */ + int sendfds[2] = { fileno(fp), -1 }; + int sendrc = xsend_with_fd(left, data, sizeof(data), sendfds); + munit_assert_int(sendrc, ==, sizeof(data)); + + _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); + rc = iobuf_recv_from_fd(buf, right); + munit_assert_int(rc, ==, sizeof(data)); + + _cleanup_close_ int fd = iobuf_take_fd(buf); + munit_assert_int(fd, !=, -1); + + return MUNIT_OK; +} + MUNIT_TEST(test_pass_fd) { int fds[2];