From 8967bc9e1bdd68f2dc33e9d80bbf0f944fb7e772 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Dec 2016 15:25:10 +1030 Subject: [PATCH] fdpass: new module. Signed-off-by: Rusty Russell --- ccan/fdpass/LICENSE | 1 + ccan/fdpass/_info | 65 +++++++++++++++++++++++++++++++ ccan/fdpass/fdpass.c | 83 +++++++++++++++++++++++++++++++++++++++ ccan/fdpass/fdpass.h | 23 +++++++++++ ccan/fdpass/test/run.c | 88 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 260 insertions(+) create mode 120000 ccan/fdpass/LICENSE create mode 100644 ccan/fdpass/_info create mode 100644 ccan/fdpass/fdpass.c create mode 100644 ccan/fdpass/fdpass.h create mode 100644 ccan/fdpass/test/run.c diff --git a/ccan/fdpass/LICENSE b/ccan/fdpass/LICENSE new file mode 120000 index 00000000..b7951dab --- /dev/null +++ b/ccan/fdpass/LICENSE @@ -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 index 00000000..dfc87328 --- /dev/null +++ b/ccan/fdpass/_info @@ -0,0 +1,65 @@ +#include "config.h" +#include +#include + +/** + * 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 + * + * Example: + * // Outputs hello! + * #include + * #include + * #include + * #include + * #include + * #include + * + * 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 index 00000000..7331468f --- /dev/null +++ b/ccan/fdpass/fdpass.c @@ -0,0 +1,83 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include +#include +#include +#include + +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 index 00000000..127b66e5 --- /dev/null +++ b/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 + +/** + * 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 index 00000000..09d280e8 --- /dev/null +++ b/ccan/fdpass/test/run.c @@ -0,0 +1,88 @@ +#include +/* Include the C files directly. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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(); +} -- 2.39.2