summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristian Høgsberg <krh@bitplanet.net>2012-04-25 10:45:48 -0400
committerKristian Høgsberg <krh@bitplanet.net>2012-04-25 10:45:48 -0400
commit5be33520b3416d2b0b138d5d30ddcf6776918007 (patch)
tree8f34cbdb723cf7161f614a4a728e6e7ee2607d56
parentf72c0c2a241a0cce353761ee0bd7e6809e4bfced (diff)
parent9f91feb5d9c56d7047dbeac4e1612190ca77ab4d (diff)
Merge remote-tracking branch 'pq/for-krh'
-rw-r--r--configure.ac2
-rw-r--r--src/connection.c5
-rw-r--r--src/event-loop.c3
-rw-r--r--src/wayland-os.c101
-rw-r--r--src/wayland-os.h34
-rw-r--r--src/wayland-server.c14
-rw-r--r--tests/os-wrappers-test.c306
-rw-r--r--tests/sanity-test.c8
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);
}