]> git.ozlabs.org Git - ccan/commitdiff
fdpass: new module.
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 7 Dec 2016 04:55:10 +0000 (15:25 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 7 Dec 2016 04:55:21 +0000 (15:25 +1030)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/fdpass/LICENSE [new symlink]
ccan/fdpass/_info [new file with mode: 0644]
ccan/fdpass/fdpass.c [new file with mode: 0644]
ccan/fdpass/fdpass.h [new file with mode: 0644]
ccan/fdpass/test/run.c [new file with mode: 0644]

diff --git a/ccan/fdpass/LICENSE b/ccan/fdpass/LICENSE
new file mode 120000 (symlink)
index 0000000..b7951da
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/CC0
\ No newline at end of file
diff --git a/ccan/fdpass/_info b/ccan/fdpass/_info
new file mode 100644 (file)
index 0000000..dfc8732
--- /dev/null
@@ -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;
+}
+
+
diff --git a/ccan/fdpass/fdpass.c b/ccan/fdpass/fdpass.c
new file mode 100644 (file)
index 0000000..7331468
--- /dev/null
@@ -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;
+}
diff --git a/ccan/fdpass/fdpass.h b/ccan/fdpass/fdpass.h
new file mode 100644 (file)
index 0000000..127b66e
--- /dev/null
@@ -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 */
diff --git a/ccan/fdpass/test/run.c b/ccan/fdpass/test/run.c
new file mode 100644 (file)
index 0000000..09d280e
--- /dev/null
@@ -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();
+}