Browse Source

fdpass: new module.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 9 years ago
parent
commit
8967bc9e1b
5 changed files with 260 additions and 0 deletions
  1. 1 0
      ccan/fdpass/LICENSE
  2. 65 0
      ccan/fdpass/_info
  3. 83 0
      ccan/fdpass/fdpass.c
  4. 23 0
      ccan/fdpass/fdpass.h
  5. 88 0
      ccan/fdpass/test/run.c

+ 1 - 0
ccan/fdpass/LICENSE

@@ -0,0 +1 @@
+../../licenses/CC0

+ 65 - 0
ccan/fdpass/_info

@@ -0,0 +1,65 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * fdpass - routines to pass a file descriptor over a socket.
+ *
+ * This code handles all the hairy details of fd passing.
+ *
+ * License: CC0 (Public domain)
+ * Maintainer: Rusty Russell <rusty@rustcorp.com.au>
+ *
+ * Example:
+ *	// Outputs hello!
+ *	#include <ccan/fdpass/fdpass.h>
+ *	#include <sys/socket.h>
+ *	#include <sys/un.h>
+ *	#include <stdio.h>
+ *	#include <stdlib.h>
+ *	#include <unistd.h>
+ *	
+ *	static void child(int sockfd)
+ *	{
+ *		char buffer[6];
+ *		int newfd = fdpass_recv(sockfd);
+ *		read(newfd, buffer, sizeof(buffer));
+ *		printf("%.*s\n", (int)sizeof(buffer), buffer);
+ *		exit(0);
+ *	}
+ *	
+ *	static void parent(int sockfd)
+ *	{
+ *		int pfds[2];
+ *	
+ *		pipe(pfds);
+ *		fdpass_send(sockfd, pfds[0]);
+ *		close(pfds[0]);
+ *		write(pfds[1], "hello!", 6);
+ *		exit(0);
+ *	}
+ *	
+ *	int main(void)
+ *	{
+ *		int sv[2];
+ *	
+ *		socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+ *		if (fork() == 0)
+ *			child(sv[0]);
+ *		else
+ *			parent(sv[1]);
+ *	}
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0)
+		return 0;
+
+	return 1;
+}
+
+

+ 83 - 0
ccan/fdpass/fdpass.c

@@ -0,0 +1,83 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#include <ccan/fdpass/fdpass.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <string.h>
+
+bool fdpass_send(int sockout, int fd)
+{
+	/* From the cmsg(3) manpage: */
+	struct msghdr msg = { 0 };
+	struct cmsghdr *cmsg;
+	struct iovec iov;
+	char c = 0;
+	union {         /* Ancillary data buffer, wrapped in a union
+			   in order to ensure it is suitably aligned */
+		char buf[CMSG_SPACE(sizeof(fd))];
+		struct cmsghdr align;
+	} u;
+
+	msg.msg_control = u.buf;
+	msg.msg_controllen = sizeof(u.buf);
+	memset(&u, 0, sizeof(u));
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+	memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_flags = 0;
+
+	/* Keith Packard reports that 0-length sends don't work, so we
+	 * always send 1 byte. */
+	iov.iov_base = &c;
+	iov.iov_len = 1;
+
+	return sendmsg(sockout, &msg, 0) == 1;
+}
+
+int fdpass_recv(int sockin)
+{
+	/* From the cmsg(3) manpage: */
+	struct msghdr msg = { 0 };
+	struct cmsghdr *cmsg;
+	struct iovec iov;
+	int fd;
+	char c;
+	union {         /* Ancillary data buffer, wrapped in a union
+			   in order to ensure it is suitably aligned */
+		char buf[CMSG_SPACE(sizeof(fd))];
+		struct cmsghdr align;
+	} u;
+
+	msg.msg_control = u.buf;
+	msg.msg_controllen = sizeof(u.buf);
+
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_flags = 0;
+
+	iov.iov_base = &c;
+	iov.iov_len = 1;
+
+	if (recvmsg(sockin, &msg, 0) < 0)
+		return -1;
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+        if (!cmsg
+	    || cmsg->cmsg_len != CMSG_LEN(sizeof(fd))
+	    || cmsg->cmsg_level != SOL_SOCKET
+	    || cmsg->cmsg_type != SCM_RIGHTS) {
+		errno = -EINVAL;
+		return -1;
+	}
+
+	memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
+	return fd;
+}

+ 23 - 0
ccan/fdpass/fdpass.h

@@ -0,0 +1,23 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#ifndef CCAN_FDPASS_H
+#define CCAN_FDPASS_H
+
+#include <stdbool.h>
+
+/**
+ * fdpass_send - send a file descriptor across a socket
+ * @sockout: socket to write to
+ * @fd: file descriptor to pass
+ *
+ * On failure, sets errno and returns false.
+ */
+bool fdpass_send(int sockout, int fd);
+
+/**
+ * fdpass_recv - receive a file descriptor from a socket
+ * @sockin: socket to read from
+ *
+ * On failure, returns -1 and sets errno.  Otherwise returns fd.
+ */
+int fdpass_recv(int sockin);
+#endif /* CCAN_FDPASS_H */

+ 88 - 0
ccan/fdpass/test/run.c

@@ -0,0 +1,88 @@
+#include <ccan/fdpass/fdpass.h>
+/* Include the C files directly. */
+#include <ccan/fdpass/fdpass.c>
+#include <ccan/tap/tap.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+static void child(int sockfd)
+{
+	char c;
+	int newfd = fdpass_recv(sockfd);
+	assert(newfd >= 0);
+	assert(read(newfd, &c, 1) == 1);
+	assert(c == 0x77);
+	exit(0);
+}
+
+static void child_nofd(int sockfd)
+{
+	assert(fdpass_recv(sockfd) == -1);
+	exit(0);
+}
+
+static void parent(int sockfd)
+{
+	int pfds[2];
+
+	ok1(pipe(pfds) == 0);
+	ok1(fdpass_send(sockfd, pfds[0]));
+	ok1(close(pfds[0]) == 0);
+	ok1(write(pfds[1], "\x77", 1) == 1);
+	ok1(close(pfds[1]) == 0);
+}
+
+int main(void)
+{
+	int sv[2];
+	int pid, wstatus;
+
+	plan_tests(17);
+	ok1(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0);
+
+	pid = fork();
+	if (pid == 0) {
+		close(sv[1]);
+		child(sv[0]);
+	}
+
+	parent(sv[1]);
+	ok1(waitpid(pid, &wstatus, 0) == pid);
+	ok1(WIFEXITED(wstatus));
+	ok1(WEXITSTATUS(wstatus) == 0);
+
+	pid = fork();
+	if (pid == 0) {
+		close(sv[1]);
+		child_nofd(sv[0]);
+	}
+	/* Don't write an fd. */
+	ok1(write(sv[1], "1", 1) == 1);
+	ok1(waitpid(pid, &wstatus, 0) == pid);
+	ok1(WIFEXITED(wstatus));
+	ok1(WEXITSTATUS(wstatus) == 0);
+	
+	pid = fork();
+	if (pid == 0) {
+		close(sv[1]);
+		child_nofd(sv[0]);
+	}
+	/* Don't write anything. */
+	close(sv[1]);
+	ok1(waitpid(pid, &wstatus, 0) == pid);
+	ok1(WIFEXITED(wstatus));
+	ok1(WEXITSTATUS(wstatus) == 0);
+	
+	close(sv[0]);
+	/* Test fdpass_recv from invalid fd. */
+	ok1(fdpass_recv(sv[0]) == -1 && errno == EBADF);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}