diff options
author | Kristian Høgsberg <krh@bitplanet.net> | 2012-04-25 10:45:48 -0400 |
---|---|---|
committer | Kristian Høgsberg <krh@bitplanet.net> | 2012-04-25 10:45:48 -0400 |
commit | 5be33520b3416d2b0b138d5d30ddcf6776918007 (patch) | |
tree | 8f34cbdb723cf7161f614a4a728e6e7ee2607d56 | |
parent | f72c0c2a241a0cce353761ee0bd7e6809e4bfced (diff) | |
parent | 9f91feb5d9c56d7047dbeac4e1612190ca77ab4d (diff) |
Merge remote-tracking branch 'pq/for-krh'
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/connection.c | 5 | ||||
-rw-r--r-- | src/event-loop.c | 3 | ||||
-rw-r--r-- | src/wayland-os.c | 101 | ||||
-rw-r--r-- | src/wayland-os.h | 34 | ||||
-rw-r--r-- | src/wayland-server.c | 14 | ||||
-rw-r--r-- | tests/os-wrappers-test.c | 306 | ||||
-rw-r--r-- | tests/sanity-test.c | 8 |
8 files changed, 444 insertions, 29 deletions
diff --git a/configure.ac b/configure.ac index 615dfdb..63081e1 100644 --- a/configure.ac +++ b/configure.ac @@ -39,6 +39,8 @@ if test "x$GCC" = "xyes"; then fi AC_SUBST(GCC_CFLAGS) +AC_CHECK_FUNCS([accept4]) + AC_ARG_ENABLE([scanner], [AC_HELP_STRING([--disable-scanner], [Disable compilation of wayland-scannner])], diff --git a/src/connection.c b/src/connection.c index a599f91..06cc66f 100644 --- a/src/connection.c +++ b/src/connection.c @@ -37,6 +37,7 @@ #include "wayland-util.h" #include "wayland-private.h" +#include "wayland-os.h" #define DIV_ROUNDUP(n, a) ( ((n) + ((a) - 1)) / (a) ) @@ -306,7 +307,7 @@ wl_connection_data(struct wl_connection *connection, uint32_t mask) msg.msg_flags = 0; do { - len = recvmsg(connection->fd, &msg, MSG_CMSG_CLOEXEC); + len = wl_os_recvmsg_cloexec(connection->fd, &msg, 0); } while (len < 0 && errno == EINTR); if (len < 0) { @@ -518,7 +519,7 @@ wl_closure_vmarshal(struct wl_closure *closure, extra += sizeof *fd_ptr; fd = va_arg(ap, int); - dup_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + dup_fd = wl_os_dupfd_cloexec(fd, 0); if (dup_fd < 0) { fprintf(stderr, "dup failed: %m"); abort(); diff --git a/src/event-loop.c b/src/event-loop.c index 3bab3df..ee2eae5 100644 --- a/src/event-loop.c +++ b/src/event-loop.c @@ -34,6 +34,7 @@ #include <unistd.h> #include <assert.h> #include "wayland-server.h" +#include "wayland-os.h" struct wl_event_loop { int epoll_fd; @@ -392,7 +393,7 @@ wl_event_loop_create(void) if (loop == NULL) return NULL; - loop->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + loop->epoll_fd = wl_os_epoll_create_cloexec(); if (loop->epoll_fd < 0) { free(loop); return NULL; diff --git a/src/wayland-os.c b/src/wayland-os.c index 5ff4391..1185e1d 100644 --- a/src/wayland-os.c +++ b/src/wayland-os.c @@ -20,12 +20,16 @@ * OF THIS SOFTWARE. */ +#define _GNU_SOURCE + #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> +#include <sys/epoll.h> +#include "../config.h" #include "wayland-os.h" static int @@ -55,15 +59,106 @@ wl_os_socket_cloexec(int domain, int type, int protocol) { int fd; -#ifdef SOCK_CLOEXEC - errno = 0; fd = socket(domain, type | SOCK_CLOEXEC, protocol); if (fd >= 0) return fd; if (errno != EINVAL) return -1; -#endif fd = socket(domain, type, protocol); return set_cloexec_or_close(fd); } + +int +wl_os_dupfd_cloexec(int fd, long minfd) +{ + int newfd; + + newfd = fcntl(fd, F_DUPFD_CLOEXEC, minfd); + if (newfd >= 0) + return newfd; + if (errno != EINVAL) + return -1; + + newfd = fcntl(fd, F_DUPFD, minfd); + return set_cloexec_or_close(newfd); +} + +static ssize_t +recvmsg_cloexec_fallback(int sockfd, struct msghdr *msg, int flags) +{ + ssize_t len; + struct cmsghdr *cmsg; + unsigned char *data; + int *fd; + int *end; + + len = recvmsg(sockfd, msg, flags); + if (len == -1) + return -1; + + if (!msg->msg_control || msg->msg_controllen == 0) + return len; + + cmsg = CMSG_FIRSTHDR(msg); + for (; cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) + continue; + + data = CMSG_DATA(cmsg); + end = (int *)(data + cmsg->cmsg_len - CMSG_LEN(0)); + for (fd = (int *)data; fd < end; ++fd) + *fd = set_cloexec_or_close(*fd); + } + + return len; +} + +ssize_t +wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags) +{ + ssize_t len; + + len = recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC); + if (len >= 0) + return len; + if (errno != EINVAL) + return -1; + + return recvmsg_cloexec_fallback(sockfd, msg, flags); +} + +int +wl_os_epoll_create_cloexec(void) +{ + int fd; + +#ifdef EPOLL_CLOEXEC + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != EINVAL) + return -1; +#endif + + fd = epoll_create(1); + return set_cloexec_or_close(fd); +} + +int +wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + int fd; + +#ifdef HAVE_ACCEPT4 + fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != ENOSYS) + return -1; +#endif + + fd = accept(sockfd, addr, addrlen); + return set_cloexec_or_close(fd); +} diff --git a/src/wayland-os.h b/src/wayland-os.h index 49adc2b..c612975 100644 --- a/src/wayland-os.h +++ b/src/wayland-os.h @@ -26,4 +26,38 @@ int wl_os_socket_cloexec(int domain, int type, int protocol); +int +wl_os_dupfd_cloexec(int fd, long minfd); + +ssize_t +wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags); + +int +wl_os_epoll_create_cloexec(void); + +int +wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + + +/* + * The following are for wayland-os.c and the unit tests. + * Do not use them elsewhere. + */ + +#ifdef __linux__ + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 02000000 +#endif + +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC 1030 +#endif + +#ifndef MSG_CMSG_CLOEXEC +#define MSG_CMSG_CLOEXEC 0x40000000 +#endif + +#endif /* __linux__ */ + #endif diff --git a/src/wayland-server.c b/src/wayland-server.c index 94eb308..97d8918 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -902,18 +902,12 @@ socket_data(int fd, uint32_t mask, void *data) int client_fd; length = sizeof name; - client_fd = - accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC); - if (client_fd < 0 && errno == ENOSYS) { - client_fd = accept(fd, (struct sockaddr *) &name, &length); - if (client_fd >= 0 && fcntl(client_fd, F_SETFD, FD_CLOEXEC) == -1) - fprintf(stderr, "failed to set FD_CLOEXEC flag on client fd, errno: %d\n", errno); - } - + client_fd = wl_os_accept_cloexec(fd, (struct sockaddr *) &name, + &length); if (client_fd < 0) fprintf(stderr, "failed to accept, errno: %d\n", errno); - - wl_client_create(display, client_fd); + else + wl_client_create(display, client_fd); return 1; } diff --git a/tests/os-wrappers-test.c b/tests/os-wrappers-test.c index aa2631b..9c4e7b2 100644 --- a/tests/os-wrappers-test.c +++ b/tests/os-wrappers-test.c @@ -1,5 +1,6 @@ /* * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Intel Corporation * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -26,40 +27,103 @@ #include <assert.h> #include <sys/types.h> #include <sys/socket.h> +#include <sys/stat.h> #include <unistd.h> #include <dlfcn.h> #include <errno.h> +#include <stdarg.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/epoll.h> +#include "../src/wayland-private.h" #include "test-runner.h" #include "../src/wayland-os.h" static int fall_back; -static int wrapped_calls; static int (*real_socket)(int, int, int); +static int wrapped_calls_socket; + +static int (*real_fcntl)(int, int, ...); +static int wrapped_calls_fcntl; + +static ssize_t (*real_recvmsg)(int, struct msghdr *, int); +static int wrapped_calls_recvmsg; + +static int (*real_epoll_create1)(int); +static int wrapped_calls_epoll_create1; static void init_fallbacks(int do_fallbacks) { fall_back = do_fallbacks; real_socket = dlsym(RTLD_NEXT, "socket"); + real_fcntl = dlsym(RTLD_NEXT, "fcntl"); + real_recvmsg = dlsym(RTLD_NEXT, "recvmsg"); + real_epoll_create1 = dlsym(RTLD_NEXT, "epoll_create1"); } __attribute__ ((visibility("default"))) int socket(int domain, int type, int protocol) { - wrapped_calls++; + wrapped_calls_socket++; -#ifdef SOCK_CLOEXEC if (fall_back && (type & SOCK_CLOEXEC)) { errno = EINVAL; return -1; } -#endif return real_socket(domain, type, protocol); } +__attribute__ ((visibility("default"))) int +fcntl(int fd, int cmd, ...) +{ + va_list ap; + void *arg; + + wrapped_calls_fcntl++; + + if (fall_back && (cmd == F_DUPFD_CLOEXEC)) { + errno = EINVAL; + return -1; + } + + va_start(ap, cmd); + arg = va_arg(ap, void*); + va_end(ap); + + return real_fcntl(fd, cmd, arg); +} + +__attribute__ ((visibility("default"))) ssize_t +recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + wrapped_calls_recvmsg++; + + if (fall_back && (flags & MSG_CMSG_CLOEXEC)) { + errno = EINVAL; + return -1; + } + + return real_recvmsg(sockfd, msg, flags); +} + +__attribute__ ((visibility("default"))) int +epoll_create1(int flags) +{ + wrapped_calls_epoll_create1++; + + if (fall_back) { + wrapped_calls_epoll_create1++; /* epoll_create() not wrapped */ + errno = EINVAL; + return -1; + } + + return real_epoll_create1(flags); +} + static void do_os_wrappers_socket_cloexec(int n) { @@ -70,17 +134,13 @@ do_os_wrappers_socket_cloexec(int n) /* simply create a socket that closes on exec */ fd = wl_os_socket_cloexec(PF_LOCAL, SOCK_STREAM, 0); + assert(fd >= 0); -#ifdef SOCK_CLOEXEC /* * Must have 2 calls if falling back, but must also allow * falling back without a forced fallback. */ - assert(wrapped_calls > n); -#else - assert(wrapped_calls == 1); -#endif - assert(fd >= 0); + assert(wrapped_calls_socket > n); exec_fd_leak_check(nr_fds); } @@ -98,3 +158,229 @@ TEST(os_wrappers_socket_cloexec_fallback) init_fallbacks(1); do_os_wrappers_socket_cloexec(1); } + +static void +do_os_wrappers_dupfd_cloexec(int n) +{ + int base_fd; + int fd; + int nr_fds; + + nr_fds = count_open_fds(); + + base_fd = socket(PF_LOCAL, SOCK_STREAM, 0); + assert(base_fd >= 0); + + fd = wl_os_dupfd_cloexec(base_fd, 13); + assert(fd >= 13); + + close(base_fd); + + /* + * Must have 4 calls if falling back, but must also allow + * falling back without a forced fallback. + */ + assert(wrapped_calls_fcntl > n); + + exec_fd_leak_check(nr_fds); +} + +TEST(os_wrappers_dupfd_cloexec) +{ + init_fallbacks(0); + do_os_wrappers_dupfd_cloexec(0); +} + +TEST(os_wrappers_dupfd_cloexec_fallback) +{ + init_fallbacks(1); + do_os_wrappers_dupfd_cloexec(3); +} + +struct marshal_data { + struct wl_connection *read_connection; + struct wl_connection *write_connection; + int s[2]; + uint32_t read_mask; + uint32_t write_mask; + union { + int h[3]; + } value; + int nr_fds_begin; + int nr_fds_conn; + int wrapped_calls; +}; + +static int +update_func(struct wl_connection *connection, uint32_t mask, void *data) +{ + uint32_t *m = data; + + *m = mask; + + return 0; +} + +static void +setup_marshal_data(struct marshal_data *data) +{ + assert(socketpair(AF_UNIX, + SOCK_STREAM | SOCK_CLOEXEC, 0, data->s) == 0); + + data->read_connection = + wl_connection_create(data->s[0], + update_func, &data->read_mask); + assert(data->read_connection); + assert(data->read_mask == WL_CONNECTION_READABLE); + + data->write_connection = + wl_connection_create(data->s[1], + update_func, &data->write_mask); + assert(data->write_connection); + assert(data->write_mask == WL_CONNECTION_READABLE); +} + +static void +marshal_demarshal(struct marshal_data *data, + void (*func)(void), int size, const char *format, ...) +{ + struct wl_closure closure; + static const int opcode = 4444; + static struct wl_object sender = { NULL, NULL, 1234 }; + struct wl_message message = { "test", format, NULL }; + struct wl_map objects; + struct wl_object object; + va_list ap; + uint32_t msg[1] = { 1234 }; + int ret; + + va_start(ap, format); + ret = wl_closure_vmarshal(&closure, &sender, opcode, ap, &message); + va_end(ap); + + assert(ret == 0); + assert(wl_closure_send(&closure, data->write_connection) == 0); + wl_closure_destroy(&closure); + assert(data->write_mask == + (WL_CONNECTION_WRITABLE | WL_CONNECTION_READABLE)); + assert(wl_connection_data(data->write_connection, + WL_CONNECTION_WRITABLE) == 0); + assert(data->write_mask == WL_CONNECTION_READABLE); + + assert(wl_connection_data(data->read_connection, + WL_CONNECTION_READABLE) == size); + + wl_map_init(&objects); + object.id = msg[0]; + ret = wl_connection_demarshal(data->read_connection, + &closure, size, &objects, &message); + wl_closure_invoke(&closure, &object, func, data); + wl_closure_destroy(&closure); +} + +static void +validate_recvmsg_h(struct marshal_data *data, + struct wl_object *object, int fd1, int fd2, int fd3) +{ + struct stat buf1, buf2; + + assert(fd1 >= 0); + assert(fd2 >= 0); + assert(fd3 >= 0); + + assert(fd1 != data->value.h[0]); + assert(fd2 != data->value.h[1]); + assert(fd3 != data->value.h[2]); + + assert(fstat(fd3, &buf1) == 0); + assert(fstat(data->value.h[2], &buf2) == 0); + assert(buf1.st_dev == buf2.st_dev); + assert(buf1.st_ino == buf2.st_ino); + + /* close the original file descriptors */ + close(data->value.h[0]); + close(data->value.h[1]); + close(data->value.h[2]); + + /* the dup'd (received) fds should still be open */ + assert(count_open_fds() == data->nr_fds_conn + 3); + + /* + * Must have 2 calls if falling back, but must also allow + * falling back without a forced fallback. + */ + assert(wrapped_calls_recvmsg > data->wrapped_calls); + + if (data->wrapped_calls == 0 && wrapped_calls_recvmsg > 1) + printf("recvmsg fell back unforced.\n"); + + /* all fds opened during the test in any way should be gone on exec */ + exec_fd_leak_check(data->nr_fds_begin); +} + +static void +do_os_wrappers_recvmsg_cloexec(int n) +{ + struct marshal_data data; + + data.nr_fds_begin = count_open_fds(); + data.wrapped_calls = n; + + setup_marshal_data(&data); + data.nr_fds_conn = count_open_fds(); + + assert(pipe(data.value.h) >= 0); + + data.value.h[2] = open("/dev/zero", O_RDONLY); + assert(data.value.h[2] >= 0); + + marshal_demarshal(&data, (void *) validate_recvmsg_h, + 8, "hhh", data.value.h[0], data.value.h[1], + data.value.h[2]); +} + +TEST(os_wrappers_recvmsg_cloexec) +{ + init_fallbacks(0); + do_os_wrappers_recvmsg_cloexec(0); +} + +TEST(os_wrappers_recvmsg_cloexec_fallback) +{ + init_fallbacks(1); + do_os_wrappers_recvmsg_cloexec(1); +} + +static void +do_os_wrappers_epoll_create_cloexec(int n) +{ + int fd; + int nr_fds; + + nr_fds = count_open_fds(); + + fd = wl_os_epoll_create_cloexec(); + assert(fd >= 0); + +#ifdef EPOLL_CLOEXEC + assert(wrapped_calls_epoll_create1 == n); +#else + printf("No epoll_create1.\n"); +#endif + + exec_fd_leak_check(nr_fds); +} + +TEST(os_wrappers_epoll_create_cloexec) +{ + init_fallbacks(0); + do_os_wrappers_epoll_create_cloexec(1); +} + +TEST(os_wrappers_epoll_create_cloexec_fallback) +{ + init_fallbacks(1); + do_os_wrappers_epoll_create_cloexec(2); +} + +/* FIXME: add tests for wl_os_accept_cloexec() */ diff --git a/tests/sanity-test.c b/tests/sanity-test.c index 65e0144..4e6f281 100644 --- a/tests/sanity-test.c +++ b/tests/sanity-test.c @@ -91,7 +91,8 @@ FAIL_TEST(sanity_fd_leak) int fd[2]; /* leak 2 file descriptors */ - pipe(fd); + if (pipe(fd) < 0) + exit(EXIT_SUCCESS); /* failed to fail */ } FAIL_TEST(sanity_fd_leak_exec) @@ -100,7 +101,8 @@ FAIL_TEST(sanity_fd_leak_exec) int nr_fds = count_open_fds(); /* leak 2 file descriptors */ - pipe(fd); + if (pipe(fd) < 0) + exit(EXIT_SUCCESS); /* failed to fail */ exec_fd_leak_check(nr_fds); } @@ -111,7 +113,7 @@ TEST(sanity_fd_exec) int nr_fds = count_open_fds(); /* create 2 file descriptors, that should pass over exec */ - pipe(fd); + assert(pipe(fd) >= 0); exec_fd_leak_check(nr_fds + 2); } |