changeset 18481:21a2ce6f8f37

lib: Added istream-unix for reading fd sockets via istream.
author Timo Sirainen <tss@iki.fi>
date Sat, 25 Apr 2015 11:23:00 +0300
parents 1c275f718758
children 25c848f10517
files src/lib/Makefile.am src/lib/istream-file-private.h src/lib/istream-file.c src/lib/istream-unix.c src/lib/istream-unix.h src/lib/test-istream-unix.c src/lib/test-lib.c src/lib/test-lib.h
diffstat 8 files changed, 355 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib/Makefile.am	Sat Apr 25 11:22:39 2015 +0300
+++ b/src/lib/Makefile.am	Sat Apr 25 11:23:00 2015 +0300
@@ -74,6 +74,7 @@
 	istream-sized.c \
 	istream-tee.c \
 	istream-timeout.c \
+	istream-unix.c \
 	ioloop.c \
 	ioloop-iolist.c \
 	ioloop-notify-none.c \
@@ -200,6 +201,7 @@
 	istream-chain.h \
 	istream-concat.h \
 	istream-crlf.h \
+	istream-file-private.h \
 	istream-hash.h \
 	istream-jsonstr.h \
 	istream-private.h \
@@ -208,6 +210,7 @@
 	istream-sized.h \
 	istream-tee.h \
 	istream-timeout.h \
+	istream-unix.h \
 	ioloop.h \
 	ioloop-iolist.h \
 	ioloop-private.h \
@@ -302,6 +305,7 @@
 	test-istream-crlf.c \
 	test-istream-seekable.c \
 	test-istream-tee.c \
+	test-istream-unix.c \
 	test-json-parser.c \
 	test-json-tree.c \
 	test-llist.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/istream-file-private.h	Sat Apr 25 11:23:00 2015 +0300
@@ -0,0 +1,23 @@
+#ifndef ISTREAM_FILE_PRIVATE_H
+#define ISTREAM_FILE_PRIVATE_H
+
+#include "istream-private.h"
+
+struct file_istream {
+	struct istream_private istream;
+
+	uoff_t skip_left;
+
+	unsigned int file:1;
+	unsigned int autoclose_fd:1;
+	unsigned int seen_eof:1;
+};
+
+struct istream *
+i_stream_create_file_common(struct file_istream *fstream,
+			    int fd, const char *path,
+			    size_t max_buffer_size, bool autoclose_fd);
+ssize_t i_stream_file_read(struct istream_private *stream);
+void i_stream_file_close(struct iostream_private *stream, bool close_parent);
+
+#endif
--- a/src/lib/istream-file.c	Sat Apr 25 11:22:39 2015 +0300
+++ b/src/lib/istream-file.c	Sat Apr 25 11:23:00 2015 +0300
@@ -4,7 +4,7 @@
 
 #include "lib.h"
 #include "ioloop.h"
-#include "istream-private.h"
+#include "istream-file-private.h"
 #include "net.h"
 
 #include <time.h>
@@ -12,18 +12,8 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 
-struct file_istream {
-	struct istream_private istream;
-
-	uoff_t skip_left;
-
-	unsigned int file:1;
-	unsigned int autoclose_fd:1;
-	unsigned int seen_eof:1;
-};
-
-static void i_stream_file_close(struct iostream_private *stream,
-				bool close_parent ATTR_UNUSED)
+void i_stream_file_close(struct iostream_private *stream,
+			 bool close_parent ATTR_UNUSED)
 {
 	struct file_istream *fstream = (struct file_istream *)stream;
 	struct istream_private *_stream = (struct istream_private *)stream;
@@ -51,7 +41,7 @@
 	return 0;
 }
 
-static ssize_t i_stream_file_read(struct istream_private *stream)
+ssize_t i_stream_file_read(struct istream_private *stream)
 {
 	struct file_istream *fstream = (struct file_istream *) stream;
 	uoff_t offset;
@@ -183,16 +173,15 @@
 	return 0;
 }
 
-static struct istream *
-i_stream_create_file_common(int fd, const char *path,
+struct istream *
+i_stream_create_file_common(struct file_istream *fstream,
+			    int fd, const char *path,
 			    size_t max_buffer_size, bool autoclose_fd)
 {
-	struct file_istream *fstream;
 	struct istream *input;
 	struct stat st;
 	bool is_file;
 
-	fstream = i_new(struct file_istream, 1);
 	fstream->autoclose_fd = autoclose_fd;
 
 	fstream->istream.iostream.close = i_stream_file_close;
@@ -235,9 +224,13 @@
 struct istream *i_stream_create_fd(int fd, size_t max_buffer_size,
 				   bool autoclose_fd)
 {
+	struct file_istream *fstream;
+
 	i_assert(fd != -1);
 
-	return i_stream_create_file_common(fd, NULL, max_buffer_size, autoclose_fd);
+	fstream = i_new(struct file_istream, 1);
+	return i_stream_create_file_common(fstream, fd, NULL,
+					   max_buffer_size, autoclose_fd);
 }
 
 struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
@@ -251,9 +244,12 @@
 
 struct istream *i_stream_create_file(const char *path, size_t max_buffer_size)
 {
+	struct file_istream *fstream;
 	struct istream *input;
 
-	input = i_stream_create_file_common(-1, path, max_buffer_size, TRUE);
+	fstream = i_new(struct file_istream, 1);
+	input = i_stream_create_file_common(fstream, -1, path,
+					    max_buffer_size, TRUE);
 	i_stream_set_name(input, path);
 	return input;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/istream-unix.c	Sat Apr 25 11:23:00 2015 +0300
@@ -0,0 +1,105 @@
+/* Copyright (c) 2014 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "fdpass.h"
+#include "istream-file-private.h"
+#include "istream-unix.h"
+
+struct unix_istream {
+	struct file_istream fstream;
+	bool next_read_fd;
+	int read_fd;
+};
+
+static void
+i_stream_unix_close(struct iostream_private *stream, bool close_parent)
+{
+	struct unix_istream *ustream = (struct unix_istream *)stream;
+
+	if (ustream->read_fd != -1)
+		i_close_fd(&ustream->read_fd);
+	i_stream_file_close(stream, close_parent);
+}
+
+static ssize_t i_stream_unix_read(struct istream_private *stream)
+{
+	struct unix_istream *ustream = (struct unix_istream *)stream;
+	size_t size;
+	ssize_t ret;
+
+	if (!ustream->next_read_fd)
+		return i_stream_file_read(stream);
+
+	i_assert(ustream->read_fd == -1);
+	i_assert(ustream->fstream.skip_left == 0); /* not supported here.. */
+	if (!i_stream_try_alloc(stream, 1, &size))
+		return -2;
+
+	do {
+		ret = fd_read(stream->fd,
+			      stream->w_buffer + stream->pos, size,
+			      &ustream->read_fd);
+	} while (unlikely(ret < 0 && errno == EINTR &&
+			  stream->istream.blocking));
+	if (ustream->read_fd != -1)
+		ustream->next_read_fd = FALSE;
+
+	if (unlikely(ret < 0)) {
+		if (errno == EINTR || errno == EAGAIN) {
+			i_assert(!stream->istream.blocking);
+			return 0;
+		} else {
+			i_assert(errno != 0);
+			/* if we get EBADF for a valid fd, it means something's
+			   really wrong and we'd better just crash. */
+			i_assert(errno != EBADF);
+			stream->istream.stream_errno = errno;
+			return -1;
+		}
+	}
+	stream->pos += ret;
+	return ret;
+}
+
+struct istream *i_stream_create_unix(int fd, size_t max_buffer_size)
+{
+	struct unix_istream *ustream;
+	struct istream *input;
+
+	i_assert(fd != -1);
+
+	ustream = i_new(struct unix_istream, 1);
+	ustream->read_fd = -1;
+	input = i_stream_create_file_common(&ustream->fstream, fd, NULL,
+					    max_buffer_size, FALSE);
+	input->real_stream->iostream.close = i_stream_unix_close;
+	input->real_stream->read = i_stream_unix_read;
+	return input;
+}
+
+void i_stream_unix_set_read_fd(struct istream *input)
+{
+	struct unix_istream *ustream =
+		(struct unix_istream *)input->real_stream;
+
+	ustream->next_read_fd = TRUE;
+}
+
+void i_stream_unix_unset_read_fd(struct istream *input)
+{
+	struct unix_istream *ustream =
+		(struct unix_istream *)input->real_stream;
+
+	ustream->next_read_fd = FALSE;
+}
+
+int i_stream_unix_get_read_fd(struct istream *input)
+{
+	struct unix_istream *ustream =
+		(struct unix_istream *)input->real_stream;
+	int fd;
+
+	fd = ustream->read_fd;
+	ustream->read_fd = -1;
+	return fd;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/istream-unix.h	Sat Apr 25 11:23:00 2015 +0300
@@ -0,0 +1,15 @@
+#ifndef ISTREAM_UNIX_H
+#define ISTREAM_UNIX_H
+
+struct istream *i_stream_create_unix(int fd, size_t max_buffer_size);
+/* Start trying to read a file descriptor from the UNIX socket. */
+void i_stream_unix_set_read_fd(struct istream *input);
+/* Stop trying to read a file descriptor from the UNIX socket. */
+void i_stream_unix_unset_read_fd(struct istream *input);
+/* Returns the fd that the last i_stream_read() received, or -1 if no fd
+   was received. This function must be called before
+   i_stream_unix_set_read_fd() is called again after successfully receiving
+   a file descriptor. */
+int i_stream_unix_get_read_fd(struct istream *input);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/test-istream-unix.c	Sat Apr 25 11:23:00 2015 +0300
@@ -0,0 +1,190 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "net.h"
+#include "fd-set-nonblock.h"
+#include "fdpass.h"
+#include "istream.h"
+#include "istream-unix.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+static int send_fd, send_fd2;
+
+static void write_one(int fd)
+{
+	if (write(fd, "1", 1) < 0)
+		i_fatal("write() failed: %m");
+}
+
+static void read_one(int fd)
+{
+	char buf;
+
+	if (read(fd, &buf, 1) < 0)
+		i_fatal("read() failed: m");
+}
+
+static void
+test_server_read_nofd(struct istream *input, unsigned int idx)
+{
+	const unsigned char *data;
+	size_t size;
+
+	test_assert_idx(i_stream_read_data(input, &data, &size, 0) == 1, idx);
+	i_stream_skip(input, 1);
+	test_assert_idx(i_stream_unix_get_read_fd(input) == -1, idx);
+}
+
+static void
+test_server_read_fd(struct istream *input, int wanted_fd, unsigned int idx)
+{
+	struct stat st1, st2;
+	const unsigned char *data;
+	size_t size;
+	int recv_fd;
+
+	test_assert_idx(i_stream_read_data(input, &data, &size, 0) == 1, idx);
+	i_stream_skip(input, 1);
+	test_assert_idx((recv_fd = i_stream_unix_get_read_fd(input)) != -1, idx);
+	if (recv_fd != -1) {
+		if (fstat(recv_fd, &st1) < 0 || fstat(wanted_fd, &st2) < 0)
+			i_fatal("fstat() failed: %m");
+		test_assert_idx(st1.st_ino == st2.st_ino, idx);
+		i_close_fd(&recv_fd);
+	}
+}
+
+static void test_istream_unix_server(int fd)
+{
+	struct istream *input;
+	const unsigned char *data;
+	size_t size;
+
+	input = i_stream_create_unix(fd, 1024);
+	/* 1) simple read */
+	test_server_read_nofd(input, 1);
+	write_one(fd);
+
+	/* 2) fd was sent but we won't get it */
+	test_server_read_nofd(input, 2);
+	/* we still shouldn't have the fd */
+	fd_set_nonblock(fd, TRUE);
+	i_stream_unix_set_read_fd(input);
+	test_assert(i_stream_read_data(input, &data, &size, 0) == 0);
+	test_assert(i_stream_unix_get_read_fd(input) == -1);
+	fd_set_nonblock(fd, FALSE);
+	write_one(fd);
+
+	/* 3) the previous fd should be lost now */
+	test_server_read_nofd(input, 3);
+	write_one(fd);
+
+	/* 4) we should get the fd now */
+	test_server_read_fd(input, send_fd2, 4);
+	write_one(fd);
+
+	/* 5) the previous fd shouldn't be returned anymore */
+	i_stream_unix_set_read_fd(input);
+	test_server_read_nofd(input, 5);
+	write_one(fd);
+
+	/* 6) with i_stream_unix_unset_read_fd() we shouldn't get fd anymore */
+	i_stream_unix_unset_read_fd(input);
+	test_server_read_nofd(input, 6);
+	write_one(fd);
+
+	/* 7-8) two fds were sent, but we'll get only the first one */
+	i_stream_unix_set_read_fd(input);
+	test_server_read_fd(input, send_fd, 7);
+	test_server_read_nofd(input, 8);
+	write_one(fd);
+
+	/* 9-10) two fds were sent, and we'll get them both */
+	i_stream_unix_set_read_fd(input);
+	test_server_read_fd(input, send_fd, 9);
+	i_stream_unix_set_read_fd(input);
+	test_server_read_fd(input, send_fd2, 10);
+	write_one(fd);
+
+	i_stream_destroy(&input);
+	i_close_fd(&fd);
+}
+
+static void test_istream_unix_client(int fd)
+{
+	char buf;
+
+	/* 1) */
+	write_one(fd);
+	read_one(fd);
+
+	/* 2) */
+	if (fd_send(fd, send_fd, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	read_one(fd);
+
+	/* 3) */
+	write_one(fd);
+	read_one(fd);
+
+	/* 4) */
+	if (fd_send(fd, send_fd2, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	read_one(fd);
+
+	/* 5) */
+	write_one(fd);
+	read_one(fd);
+
+	/* 6) */
+	if (fd_send(fd, send_fd, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	read_one(fd);
+
+	/* 7-8) */
+	if (fd_send(fd, send_fd, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	if (fd_send(fd, send_fd2, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	read_one(fd);
+
+	/* 9-10) */
+	if (fd_send(fd, send_fd, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	if (fd_send(fd, send_fd2, &buf, 1) < 0)
+		i_fatal("fd_send() failed: %m");
+	read_one(fd);
+
+	i_close_fd(&fd);
+}
+
+void test_istream_unix(void)
+{
+	int fd[2];
+
+	test_begin("istream unix");
+	if ((send_fd = open("/dev/null", O_RDONLY)) == -1)
+		i_fatal("open(/dev/null) failed: %m");
+	if ((send_fd2 = open("/dev/stderr", O_WRONLY)) == -1)
+		i_fatal("open(/dev/stderr) failed: %m");
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
+		i_fatal("socketpair() failed: %m");
+	switch (fork()) {
+	case -1:
+		i_fatal("fork() failed: %m");
+	case 0:
+		i_close_fd(&fd[0]);
+		test_istream_unix_client(fd[1]);
+		_exit(0);
+	default:
+		i_close_fd(&fd[1]);
+		test_istream_unix_server(fd[0]);
+		break;
+	}
+	i_close_fd(&send_fd);
+	i_close_fd(&send_fd2);
+	test_end();
+}
--- a/src/lib/test-lib.c	Sat Apr 25 11:22:39 2015 +0300
+++ b/src/lib/test-lib.c	Sat Apr 25 11:23:00 2015 +0300
@@ -27,6 +27,7 @@
 		test_istream_crlf,
 		test_istream_seekable,
 		test_istream_tee,
+		test_istream_unix,
 		test_json_parser,
 		test_json_tree,
 		test_llist,
--- a/src/lib/test-lib.h	Sat Apr 25 11:22:39 2015 +0300
+++ b/src/lib/test-lib.h	Sat Apr 25 11:23:00 2015 +0300
@@ -28,6 +28,7 @@
 void test_istream_crlf(void);
 void test_istream_seekable(void);
 void test_istream_tee(void);
+void test_istream_unix(void);
 void test_json_parser(void);
 void test_json_tree(void);
 void test_llist(void);