From 6b2cd23b0681a7ad253201071d243cbefc1bc0aa Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Thu, 7 Feb 2019 18:02:57 +0200 Subject: [PATCH 1/8] tests: fix main symbol duplication
So far I got these errors before patching: libtool: link: cc -o .libs/headers-test -pthread -Wall -Wextra -Wno-unused-parameter -g -Wstrict-prototypes -Wmissing-prototypes -fvisibility=hidden -O2 -pipe tests/headers-test.o tests/headers-protocol-test.o tests/headers-protocol-core-test.o /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a -L.libs -lwayland-client -lffi -lm -lwayland-server -lkvm -Wl,-rpath-link,/usr/local/lib ld: error: duplicate symbol: main >>> defined at headers-test.c:53 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/headers-test.c:53) >>> tests/headers-test.o:(main) >>> defined at test-runner.c:377 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/test-runner.c:377) >>> test-runner.o:(.text+0x250) in archive >>> /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a libtool: link: cc -o .libs/exec-fd-leak-checker -pthread -Wall -Wextra -Wno-unused-parameter -g -Wstrict-prototypes -Wmissing-prototypes -fvisibility=hidden -O2 -pipe tests/exec-fd-leak-checker.o /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a -L.libs -lwayland-client -lffi -lm -lwayland-server -lkvm -Wl,-rpath-link,/usr/local/lib ld: error: duplicate symbol: main >>> defined at exec-fd-leak-checker.c:57 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/exec-fd-leak-checker.c:57) >>> tests/exec-fd-leak-checker.o:(main) >>> defined at test-runner.c:377 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/test-runner.c:377) >>> test-runner.o:(.text+0x250) in archive >>> /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a Makefile.am: error: object 'tests/test-helpers.$(OBJEXT)' created both with libtool and without libtool: link: cc -o .libs/fixed-benchmark -pthread -Wall -Wextra -Wno-unused-parameter -g -Wstrict-prototypes -Wmissing-prototypes -fvisibility=hidden -O2 -pipe tests/fixed-benchmark.o /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a -L.libs -lwayland-client -lffi -lm -lwayland-server -lkvm -Wl,-rpath-link,/usr/local/lib ld: error: duplicate symbol: main >>> defined at fixed-benchmark.c:100 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/fixed-benchmark.c:100) >>> tests/fixed-benchmark.o:(main) >>> defined at test-runner.c:377 >>> (/tmp/obj/wayland-1.16.0/wayland-1.16.0/tests/test-runner.c:377) >>> test-runner.o:(.text+0x250) in archive >>> /tmp/obj/wayland-1.16.0/build-amd64/.libs/libtest-runner.a This commit fixes all of that. Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- Makefile.am | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile.am b/Makefile.am index 697c517..cce4d73 100644 --- a/Makefile.am +++ b/Makefile.am @@ -210,12 +210,16 @@ noinst_PROGRAMS = \ exec-fd-leak-checker \ fixed-benchmark -noinst_LTLIBRARIES += libtest-runner.la +noinst_LTLIBRARIES += \ + libtest-runner.la \ + libtest-helpers.la + +libtest_helpers_la_SOURCES = tests/test-helpers.c +libtest_helpers_la_LIBADD = -lrt -ldl $(FFI_LIBS) libtest_runner_la_SOURCES = \ tests/test-runner.c \ tests/test-runner.h \ - tests/test-helpers.c \ tests/test-compositor.h \ tests/test-compositor.c libtest_runner_la_LIBADD = \ @@ -223,8 +227,7 @@ libtest_runner_la_LIBADD = \ libwayland-util.la \ libwayland-client.la \ libwayland-server.la \ - -lrt -ldl $(FFI_LIBS) - + libtest-helpers.la array_test_SOURCES = tests/array-test.c array_test_LDADD = libtest-runner.la @@ -270,7 +273,6 @@ protocol_logger_test_LDADD = libtest-runner.la headers_test_SOURCES = tests/headers-test.c \ tests/headers-protocol-test.c \ tests/headers-protocol-core-test.c -headers_test_LDADD = libtest-runner.la nodist_headers_test_SOURCES = \ protocol/wayland-server-protocol-core.h \ protocol/wayland-client-protocol-core.h @@ -280,13 +282,12 @@ cpp_compile_test_SOURCES = tests/cpp-compile-test.cpp endif fixed_benchmark_SOURCES = tests/fixed-benchmark.c -fixed_benchmark_LDADD = libtest-runner.la os_wrappers_test_SOURCES = tests/os-wrappers-test.c os_wrappers_test_LDADD = libtest-runner.la exec_fd_leak_checker_SOURCES = tests/exec-fd-leak-checker.c -exec_fd_leak_checker_LDADD = libtest-runner.la +exec_fd_leak_checker_LDADD = libtest-helpers.la EXTRA_DIST += tests/scanner-test.sh \ protocol/tests.xml \ -- 2.20.1 From 6c51f608e7eab095310d3c6b0e46ccae05cef82d Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 10:07:56 +0200 Subject: [PATCH 2/8] tests: use *BSD replacement for /proc Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- Makefile.am | 2 +- configure.ac | 12 ++++++++++++ tests/test-helpers.c | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index cce4d73..52c7895 100644 --- a/Makefile.am +++ b/Makefile.am @@ -215,7 +215,7 @@ noinst_LTLIBRARIES += \ libtest-helpers.la libtest_helpers_la_SOURCES = tests/test-helpers.c -libtest_helpers_la_LIBADD = -lrt -ldl $(FFI_LIBS) +libtest_helpers_la_LIBADD = -lrt -ldl $(FFI_LIBS) $(KVM_LIBS) libtest_runner_la_SOURCES = \ tests/test-runner.c \ diff --git a/configure.ac b/configure.ac index 18fb649..912330e 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,18 @@ AC_SUBST(GCC_CFLAGS) AC_CHECK_HEADERS([sys/prctl.h]) AC_CHECK_FUNCS([accept4 mkostemp posix_fallocate prctl]) +# Replacement for /proc on BSD +AC_CHECK_HEADERS([kvm.h]) +SAVE_LIBS="$LIBS" +LIBS= +AC_CHECK_LIB([kvm], [kvm_getfiles]) +KVM_LIBS="$LIBS" +LIBS="$SAVE_LIBS" +AC_SUBST(KVM_LIBS) +if test "x$ac_cv_header_kvm_h" != "x" && test "x$ac_cv_lib_kvm_kvm_getfiles" != "x"; then + AC_DEFINE(USE_LIBKVM, 1, [use libkvm on BSD]) +fi + AC_ARG_ENABLE([libraries], [AC_HELP_STRING([--disable-libraries], [Disable compilation of wayland libraries])], diff --git a/tests/test-helpers.c b/tests/test-helpers.c index b2189d8..1c83e00 100644 --- a/tests/test-helpers.c +++ b/tests/test-helpers.c @@ -25,9 +25,20 @@ #include "config.h" -#include <assert.h> +#include "../config.h" + +#ifdef USE_LIBKVM +#include <sys/types.h> +#include <sys/sysctl.h> +#include <fcntl.h> +#include <kvm.h> +#include <limits.h> +#else #include <errno.h> #include <dirent.h> +#endif + +#include <assert.h> #include <stdio.h> #include <unistd.h> #include <time.h> @@ -43,6 +54,25 @@ int count_open_fds(void) { +#ifdef USE_LIBKVM + /* Use BSD-specific kernel memory interface */ + + struct kinfo_file *kif; + kvm_t *kd; + int count; + char errstr[_POSIX2_LINE_MAX]; + + kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY|KVM_NO_FILES, errstr); + assert(kd != NULL); + kif = kvm_getfiles(kd, KERN_FILE_BYPID, getpid(), sizeof(struct kinfo_file), &count); + assert(kif != NULL); + + /* KVM library frees memory on itself */ + kvm_close(kd); + return count; +#else + /* Use /proc filesystem. */ + DIR *dir; struct dirent *ent; int count = 0; @@ -62,6 +92,7 @@ count_open_fds(void) closedir(dir); return count; +#endif } void -- 2.20.1 From 5857190b2b45e05f67972aec87ae486b87e66ef6 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 10:24:14 +0200 Subject: [PATCH 3/8] server: add *BSD credentials support. Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- configure.ac | 3 ++ src/wayland-server.c | 46 ++++++++++++++++++++++ src/wayland-shm.c | 92 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 912330e..d106a41 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,9 @@ AC_SUBST(GCC_CFLAGS) AC_CHECK_HEADERS([sys/prctl.h]) AC_CHECK_FUNCS([accept4 mkostemp posix_fallocate prctl]) +# Credential support on BSD +AC_CHECK_HEADERS([sys/ucred.h]) + # Replacement for /proc on BSD AC_CHECK_HEADERS([kvm.h]) SAVE_LIBS="$LIBS" diff --git a/src/wayland-server.c b/src/wayland-server.c index 19f6a76..f4cdbf3 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -25,6 +25,13 @@ #define _GNU_SOURCE +#include "../config.h" + +#ifdef HAVE_SYS_UCRED_H +#include <sys/types.h> +#include <sys/ucred.h> +#endif + #include <stdlib.h> #include <stdint.h> #include <stddef.h> @@ -77,7 +84,13 @@ struct wl_client { struct wl_list link; struct wl_map objects; struct wl_priv_signal destroy_signal; +#ifdef HAVE_SYS_UCRED_H + /* BSD */ + struct xucred xucred; +#else + /* Linux */ struct ucred ucred; +#endif int error; struct wl_priv_signal resource_created_signal; }; @@ -312,7 +325,11 @@ wl_resource_post_error(struct wl_resource *resource, static void destroy_client_with_error(struct wl_client *client, const char *reason) { +#ifdef HAVE_SYS_UCRED_H + wl_log("%s (uid %u)\n", reason, client->xucred.cr_uid); +#else wl_log("%s (pid %u)\n", reason, client->ucred.pid); +#endif wl_client_destroy(client); } @@ -526,10 +543,29 @@ wl_client_create(struct wl_display *display, int fd) if (!client->source) goto err_client; +#ifndef SO_PEERCRED +/* FreeBSD */ +# define SO_PEERCRED LOCAL_PEERCRED +#endif + +#ifdef HAVE_SYS_UCRED_H + /* BSD */ + len = sizeof client->xucred; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, + &client->xucred, &len) < 0 +# ifdef XUCRED_VERSION + /* FreeBSD */ + || client->xucred.cr_version != XUCRED_VERSION +# endif + ) + goto err_source; +#else + /* Linux */ len = sizeof client->ucred; if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &client->ucred, &len) < 0) goto err_source; +#endif client->connection = wl_connection_create(fd); if (client->connection == NULL) @@ -583,12 +619,22 @@ WL_EXPORT void wl_client_get_credentials(struct wl_client *client, pid_t *pid, uid_t *uid, gid_t *gid) { +#ifdef HAVE_SYS_UCRED_H + /* BSD */ + *pid = 0; /* FIXME: pid is not defined on BSD */ + if (uid) + *uid = client->xucred.cr_uid; + if (gid) + *gid = client->xucred.cr_gid; +#else + /* Linux */ if (pid) *pid = client->ucred.pid; if (uid) *uid = client->ucred.uid; if (gid) *gid = client->ucred.gid; +#endif } /** Get the file descriptor for the client diff --git a/src/wayland-shm.c b/src/wayland-shm.c index 4191231..dbaf464 100644 --- a/src/wayland-shm.c +++ b/src/wayland-shm.c @@ -30,6 +30,8 @@ #define _GNU_SOURCE +#include "../config.h" + #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -59,6 +61,9 @@ struct wl_shm_pool { char *data; int32_t size; int32_t new_size; +#ifdef HAVE_SYS_UCRED_H + int fd; +#endif }; struct wl_shm_buffer { @@ -76,15 +81,91 @@ struct wl_shm_sigbus_data { int fallback_mapping_used; }; +#ifdef HAVE_MREMAP +static void * +mremap_compat_maymove(void *old_address, size_t old_size, size_t new_size, + int old_prot, int old_flags, int old_fd) +{ + return mremap(old_address, old_size, new_size, MREMAP_MAYMOVE); +} +#else +static void * +mremap_compat_maymove(void *old_address, size_t old_size, size_t new_size, + int old_prot, int old_flags, int old_fd) +{ + /* FreeBSD doesn't support mremap() yet, so we have to emulate it. + * This assumes MREMAP_MAYMOVE is the only flag in use. */ + if (new_size == old_size) { + return old_address; + } else if (new_size < old_size) { + /* Shrinking: munmap() the spare region. */ + munmap(old_address + old_size, new_size - old_size); + return old_address; + } else { + void *ret; + + /* Growing. Try and mmap() the extra region at the end of + * our existing allocation. If that gets mapped in the + * wrong place, fall back to mmap()ing an entirely new + * region of new_size and copying the data across. */ + ret = mmap(old_address + old_size, new_size - old_size, + old_prot, old_flags, old_fd, 0); + +/* FIXME TODO: msync() before munmap()? */ + if (ret == MAP_FAILED) { + /* Total failure! */ + return ret; + } else if (ret == old_address + old_size) { + /* Success. */ + return old_address; + } else if (ret != old_address + old_size) { + /* Partial failure. Fall back to mapping an + * entirely new region. Unmap the region we + * just mapped first. */ + munmap(ret, new_size - old_size); + + /* Map an entirely new region. */ + ret = mmap(NULL, new_size, + old_prot, old_flags, old_fd, 0); + if (ret == MAP_FAILED) { + /* Total failure! */ + return ret; + } + + /* Copy the old data across. Implicit assumption + * that the old and new regions don't overlap. */ + memcpy(ret, old_address, old_size); + + /* Unmap the old region. */ + munmap(old_address, old_size); + + return ret; + } + } + + /* Unreachable. */ + return MAP_FAILED; +} +#endif + static void shm_pool_finish_resize(struct wl_shm_pool *pool) { void *data; +#ifdef HAVE_SYS_UCRED_H + int fd = -1; +#endif if (pool->size == pool->new_size) return; +#ifdef HAVE_SYS_UCRED_H + fd = pool->fd; + data = mremap_compat_maymove(pool->data, pool->size, pool->new_size, + PROT_READ|PROT_WRITE, MAP_SHARED, fd); +#else data = mremap(pool->data, pool->size, pool->new_size, MREMAP_MAYMOVE); +#endif if (data == MAP_FAILED) { wl_resource_post_error(pool->resource, WL_SHM_ERROR_INVALID_FD, @@ -110,6 +191,10 @@ shm_pool_unref(struct wl_shm_pool *pool, bool external) if (pool->internal_refcount + pool->external_refcount) return; +#ifdef HAVE_SYS_UCRED_H + close(pool->fd); +#endif + munmap(pool->data, pool->size); free(pool); } @@ -284,7 +369,13 @@ shm_create_pool(struct wl_client *client, struct wl_resource *resource, "failed mmap fd %d: %m", fd); goto err_free; } +#ifdef HAVE_SYS_UCRED_H + /* We need to keep the FD around on FreeBSD so we can implement + * mremap(). See: mremap_compat_maymove(). */ + pool->fd = fd; +#else close(fd); +#endif pool->resource = wl_resource_create(client, &wl_shm_pool_interface, 1, id); @@ -364,7 +455,6 @@ wl_shm_buffer_get_stride(struct wl_shm_buffer *buffer) return buffer->stride; } - /** Get a pointer to the memory for the SHM buffer * * \param buffer The buffer object -- 2.20.1 From 4111a5e2e54d1fe13a6cb813681b9e823a379b89 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 10:32:58 +0200 Subject: [PATCH 4/8] gitignore: ignore *.orig files generated by patch(1) Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eadea12..8d2c614 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.trs *.tar.xz *~ +*.orig *-test .libs .dirstamp -- 2.20.1 From 8d5f298fb9df46e5856e178a06a24df78e46af68 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 11:50:57 +0200 Subject: [PATCH 5/8] core: support kqueue Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- Makefile.am | 3 +- configure.ac | 19 +- doc/doxygen/Makefile.am | 3 +- src/{event-loop.c => event-loop-epoll.c} | 4 + src/event-loop-kqueue.c | 825 +++++++++++++++++++++++ src/wayland-os.c | 47 +- src/wayland-os.h | 2 +- tests/os-wrappers-test.c | 55 +- 8 files changed, 940 insertions(+), 18 deletions(-) rename src/{event-loop.c => event-loop-epoll.c} (99%) create mode 100644 src/event-loop-kqueue.c diff --git a/Makefile.am b/Makefile.am index 52c7895..426e20a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,7 +76,8 @@ libwayland_server_la_LDFLAGS = -version-info 1:0:1 libwayland_server_la_SOURCES = \ src/wayland-server.c \ src/wayland-shm.c \ - src/event-loop.c + src/event-loop-epoll.c \ + src/event-loop-kqueue.c nodist_libwayland_server_la_SOURCES = \ protocol/wayland-server-protocol.h \ diff --git a/configure.ac b/configure.ac index d106a41..941749d 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,11 @@ AC_SUBST(GCC_CFLAGS) AC_CHECK_HEADERS([sys/prctl.h]) AC_CHECK_FUNCS([accept4 mkostemp posix_fallocate prctl]) +AC_CHECK_HEADERS([sys/epoll.h sys/event.h]) +if test "x$ac_cv_header_sys_epoll_h" != "xyes" && test "x$ac_cv_header_sys_event_h" != "xyes"; then + AC_MSG_ERROR([Can't find sys/epoll.h or sys/event.h. Please ensure either epoll or kqueue is available.]) +fi + # Credential support on BSD AC_CHECK_HEADERS([sys/ucred.h]) @@ -115,12 +120,14 @@ AC_SUBST([ICONDIR]) if test "x$enable_libraries" = "xyes"; then PKG_CHECK_MODULES(FFI, [libffi]) - AC_CHECK_DECL(SFD_CLOEXEC,[], - [AC_MSG_ERROR("SFD_CLOEXEC is needed to compile wayland libraries")], - [[#include <sys/signalfd.h>]]) - AC_CHECK_DECL(TFD_CLOEXEC,[], - [AC_MSG_ERROR("TFD_CLOEXEC is needed to compile wayland libraries")], - [[#include <sys/timerfd.h>]]) + if test "x$ac_cv_header_sys_epoll_h" == "xyes"; then + AC_CHECK_DECL(SFD_CLOEXEC,[], + [AC_MSG_ERROR("SFD_CLOEXEC is needed to compile wayland libraries")], + [[#include <sys/signalfd.h>]]) + AC_CHECK_DECL(TFD_CLOEXEC,[], + [AC_MSG_ERROR("TFD_CLOEXEC is needed to compile wayland libraries")], + [[#include <sys/timerfd.h>]]) + fi AC_CHECK_DECL(CLOCK_MONOTONIC,[], [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile wayland libraries")], [[#include <time.h>]]) diff --git a/doc/doxygen/Makefile.am b/doc/doxygen/Makefile.am index f8b0b3a..60bed53 100644 --- a/doc/doxygen/Makefile.am +++ b/doc/doxygen/Makefile.am @@ -19,7 +19,8 @@ scanned_src_files_Client = \ scanned_src_files_Server = \ $(scanned_src_files_shared) \ - $(top_srcdir)/src/event-loop.c \ + $(top_srcdir)/src/event-loop-epoll.c \ + $(top_srcdir)/src/event-loop-kqueue.c \ $(top_srcdir)/src/wayland-server.c \ $(top_srcdir)/src/wayland-server.h \ $(top_srcdir)/src/wayland-server-core.h \ diff --git a/src/event-loop.c b/src/event-loop-epoll.c similarity index 99% rename from src/event-loop.c rename to src/event-loop-epoll.c index eb2dce6..76cbfae 100644 --- a/src/event-loop.c +++ b/src/event-loop-epoll.c @@ -23,6 +23,9 @@ * SOFTWARE. */ +#include "../config.h" + +#ifdef HAVE_SYS_EPOLL_H #include <stddef.h> #include <stdio.h> #include <errno.h> @@ -702,3 +705,4 @@ wl_event_loop_get_destroy_listener(struct wl_event_loop *loop, { return wl_signal_get(&loop->destroy_signal, notify); } +#endif diff --git a/src/event-loop-kqueue.c b/src/event-loop-kqueue.c new file mode 100644 index 0000000..2713416 --- /dev/null +++ b/src/event-loop-kqueue.c @@ -0,0 +1,825 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "../config.h" + +#ifdef HAVE_SYS_EVENT_H +#include <stddef.h> +#include <stdio.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <fcntl.h> +#include <sys/event.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <unistd.h> +#include "wayland-util.h" +#include "wayland-private.h" +#include "wayland-server-core.h" +#include "wayland-os.h" + +/** \cond INTERNAL */ + +struct wl_event_loop { + int event_fd; + struct wl_list check_list; + struct wl_list idle_list; + struct wl_list destroy_list; + + struct wl_signal destroy_signal; + + /* + * kqueue doesn't allow to setup multiple handlers of the same signal. + * Also, with kqueue we need to keep list of registered timers, + * to avoid accidental timer ID reuse. + * Thus we have to maintain lists of signal & timer handlers separately. + * + * TODO: locking. + */ + struct wl_list signals[NSIG]; + struct wl_list timers; +}; + +struct wl_event_source_interface { + int (*dispatch)(struct wl_event_source *source, + struct kevent *ep); +}; + +struct wl_event_source { + struct wl_event_source_interface *interface; + struct wl_event_loop *loop; + struct wl_list link; + void *data; + int fd; +}; + +struct wl_event_source_fd { + struct wl_event_source base; + wl_event_loop_fd_func_t func; +}; + +/** \endcond */ + +static int +wl_event_source_fd_dispatch(struct wl_event_source *source, + struct kevent *ev) +{ + struct wl_event_source_fd *fd_source = (struct wl_event_source_fd *) source; + uint32_t mask; + + mask = 0; + if (ev->filter == EVFILT_READ) + mask |= WL_EVENT_READABLE; + if (ev->filter == EVFILT_WRITE) + mask |= WL_EVENT_WRITABLE; + if (ev->flags & EV_ERROR) + mask |= WL_EVENT_ERROR; + if (ev->flags & EV_EOF) + mask |= WL_EVENT_HANGUP; + + return fd_source->func(source->fd, mask, source->data); +} + +struct wl_event_source_interface fd_source_interface = { + wl_event_source_fd_dispatch, +}; + +/** Create a file descriptor event source + * + * \param loop The event loop that will process the new source. + * \param fd The file descriptor to watch. + * \param mask A bitwise-or of which events to watch for: \c WL_EVENT_READABLE, + * \c WL_EVENT_WRITABLE. + * \param func The file descriptor dispatch function. + * \param data User data. + * \return A new file descriptor event source. + * + * The given file descriptor is initially watched for the events given in + * \c mask. This can be changed as needed with wl_event_source_fd_update(). + * + * If it is possible that program execution causes the file descriptor to be + * read while leaving the data in a buffer without actually processing it, + * it may be necessary to register the file descriptor source to be re-checked, + * see wl_event_source_check(). This will ensure that the dispatch function + * gets called even if the file descriptor is not readable or writable + * anymore. This is especially useful with IPC libraries that automatically + * buffer incoming data, possibly as a side-effect of other operations. + * + * \sa wl_event_loop_fd_func_t + * \memberof wl_event_source + */ +WL_EXPORT struct wl_event_source * +wl_event_loop_add_fd(struct wl_event_loop *loop, + int fd, uint32_t mask, + wl_event_loop_fd_func_t func, + void *data) +{ + struct wl_event_source_fd *source; + + source = malloc(sizeof *source); + if (source == NULL) + return NULL; + + /* save original fd, but monitor a duplicate */ + source->base.interface = &fd_source_interface; + source->base.fd = fd; + source->func = func; + source->base.loop = loop; + source->base.data = data; + wl_list_init(&source->base.link); + if (wl_event_source_fd_update(&source->base, mask) == -1) { + free(source); + return NULL; + } + + return &source->base; +} + +/** Update a file descriptor source's event mask + * + * \param source The file descriptor event source to update. + * \param mask The new mask, a bitwise-or of: \c WL_EVENT_READABLE, + * \c WL_EVENT_WRITABLE. + * \return 0 on success, -1 on failure. + * + * This changes which events, readable and/or writable, cause the dispatch + * callback to be called on. + * + * File descriptors are usually writable to begin with, so they do not need to + * be polled for writable until a write actually fails. When a write fails, + * the event mask can be changed to poll for readable and writable, delivering + * a dispatch callback when it is possible to write more. Once all data has + * been written, the mask can be changed to poll only for readable to avoid + * busy-looping on dispatch. + * + * \sa wl_event_loop_add_fd() + * \memberof wl_event_source + */ +WL_EXPORT int +wl_event_source_fd_update(struct wl_event_source *source, uint32_t mask) +{ + struct wl_event_loop *loop = source->loop; + struct kevent events[2]; + + if (mask & WL_EVENT_READABLE) + EV_SET(&events[0], source->fd, EVFILT_READ, + EV_ADD | EV_ENABLE, 0, 0, source); + else + EV_SET(&events[0], source->fd, EVFILT_READ, + EV_ADD | EV_DISABLE, 0, 0, source); + + if (mask & WL_EVENT_WRITABLE) + EV_SET(&events[1], source->fd, EVFILT_WRITE, + EV_ADD | EV_ENABLE, 0, 0, source); + else + EV_SET(&events[1], source->fd, EVFILT_WRITE, + EV_ADD | EV_DISABLE, 0, 0, source); + + return kevent(loop->event_fd, events, 2, NULL, 0, NULL); +} + +/** \cond INTERNAL */ + +struct wl_event_source_timer { + struct wl_event_source base; + wl_event_loop_timer_func_t func; + struct wl_list timer_link; +}; + +/** \endcond */ + +static int +wl_event_source_timer_dispatch(struct wl_event_source *source, + struct kevent *ev) +{ + struct wl_event_source_timer *timer_source = + (struct wl_event_source_timer *) source; + uint64_t expires; + + expires = ev->data; /* XXX unused?! */ + return timer_source->func(timer_source->base.data); +} + +struct wl_event_source_interface timer_source_interface = { + wl_event_source_timer_dispatch, +}; + +/** Create a timer event source + * + * \param loop The event loop that will process the new source. + * \param func The timer dispatch function. + * \param data User data. + * \return A new timer event source. + * + * The timer is initially disarmed. It needs to be armed with a call to + * wl_event_source_timer_update() before it can trigger a dispatch call. + * + * \sa wl_event_loop_timer_func_t + * \memberof wl_event_source + */ +WL_EXPORT struct wl_event_source * +wl_event_loop_add_timer(struct wl_event_loop *loop, + wl_event_loop_timer_func_t func, + void *data) +{ + /* + * BSD. We use kqueue() timers directly, without additional FD. + * See: wl_event_source_timer_update(). + * + * FIXME: timer ID wrapping: need to keep list of used timers. + */ + static int next_timer_id = 0; + struct wl_event_source_timer *source; + + source = malloc(sizeof *source); + if (source == NULL) + return NULL; + source->base.interface = &timer_source_interface; + source->base.fd = next_timer_id++; /* TODO: locking/atomic ops */ + source->base.loop = loop; + source->base.data = data; + source->func = func; + wl_list_insert(&loop->timers, &source->timer_link); + wl_list_init(&source->base.link); + + return &source->base; +} + +/** Arm or disarm a timer + * + * \param source The timer event source to modify. + * \param ms_delay The timeout in milliseconds. + * \return 0 on success, -1 on failure. + * + * If the timeout is zero, the timer is disarmed. + * + * If the timeout is non-zero, the timer is set to expire after the given + * timeout in milliseconds. When the timer expires, the dispatch function + * set with wl_event_loop_add_timer() is called once from + * wl_event_loop_dispatch(). If another dispatch is desired after another + * expiry, wl_event_source_timer_update() needs to be called again. + * + * \memberof wl_event_source + */ +WL_EXPORT int +wl_event_source_timer_update(struct wl_event_source *source, int ms_delay) +{ + struct kevent ev; + + EV_SET(&ev, source->fd, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, + ms_delay, source); + if (kevent(source->loop->event_fd, &ev, 1, NULL, 0, NULL) < 0) { + fprintf(stderr, "could not set kqueue timer: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +/** \cond INTERNAL */ + +struct wl_event_source_signal { + struct wl_event_source base; + wl_event_loop_signal_func_t func; + struct wl_list signal_link; +}; + +/** \endcond */ + +static int +wl_event_source_signal_dispatch(struct wl_event_source *source, + struct kevent *ev) +{ + struct wl_event_source_signal *signal_source; + + signal_source = (struct wl_event_source_signal *) source; + return signal_source->func(signal_source->base.fd, + signal_source->base.data); +} + +struct wl_event_source_interface signal_source_interface = { + wl_event_source_signal_dispatch, +}; + +/** Create a POSIX signal event source + * + * \param loop The event loop that will process the new source. + * \param signal_number Number of the signal to watch for. + * \param func The signal dispatch function. + * \param data User data. + * \return A new signal event source. + * + * This function blocks the normal delivery of the given signal in the calling + * thread, and creates a "watch" for it. Signal delivery no longer happens + * asynchronously, but by wl_event_loop_dispatch() calling the dispatch + * callback function \c func. + * + * It is the caller's responsibility to ensure that all other threads have + * also blocked the signal. + * + * \sa wl_event_loop_signal_func_t + * \memberof wl_event_source + */ +static struct wl_event_source_signal * +wl_event_loop_create_signal_source(int signal_number, + wl_event_loop_signal_func_t func) +{ + struct wl_event_source_signal *source; + sigset_t mask; + + source = calloc(1, sizeof(struct wl_event_source_signal)); + if (source == NULL) + return NULL; + + source->base.interface = &signal_source_interface; + wl_list_init(&source->base.link); + source->base.fd = signal_number; + source->func = func; + + /* Block delivery of signal_number to this process. */ + sigemptyset(&mask); + sigaddset(&mask, signal_number); + sigprocmask(SIG_BLOCK, &mask, NULL); + + source->func = func; + return source; +} + +static int +dispatch_signal_event(struct wl_event_loop *loop, int signal_number) +{ + struct wl_event_source_signal *ksh, *next; + int n; + + n = 0; + warnx("SIGNAL %d DISPATCH BEGIN", signal_number); + wl_list_for_each_safe(ksh, next, &loop->signals[signal_number], signal_link) { + warnx("\t0x%p 0x%p %d", ksh, next, n); + n += ksh->func(signal_number, ksh->base.data); + } + return n; +} + +/* BSD. Use kqueue. Multiple handlers need special treatment. */ +WL_EXPORT struct wl_event_source * +wl_event_loop_add_signal(struct wl_event_loop *loop, + int signal_number, + wl_event_loop_signal_func_t func, + void *data) +{ + struct wl_event_source_signal *source = NULL;; + struct kevent ev; + + source = wl_event_loop_create_signal_source(signal_number, func); + if (source == NULL) + return NULL; + source->base.loop = loop; + source->base.data = data; + if (wl_list_empty(&loop->signals[signal_number])) { + /* set up kevent object */ + EV_SET(&ev, signal_number, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, + 0, 0, source); + if (kevent(loop->event_fd, &ev, 1, NULL, 0, NULL) < 0) { + fprintf(stderr, + "error adding handler for signal %i (%p) " + "in event loop %p: %s", + signal_number, source, loop, strerror(errno)); + free(source); + return NULL; + } + } + + wl_list_insert(&loop->signals[signal_number], &source->signal_link); + return &source->base; +} + +/** \cond INTERNAL */ + +struct wl_event_source_idle { + struct wl_event_source base; + wl_event_loop_idle_func_t func; +}; + +/** \endcond */ + +struct wl_event_source_interface idle_source_interface = { + NULL, +}; + +/** Create an idle task + * + * \param loop The event loop that will process the new task. + * \param func The idle task dispatch function. + * \param data User data. + * \return A new idle task (an event source). + * + * Idle tasks are dispatched before wl_event_loop_dispatch() goes to sleep. + * See wl_event_loop_dispatch() for more details. + * + * Idle tasks fire once, and are automatically destroyed right after the + * callback function has been called. + * + * An idle task can be cancelled before the callback has been called by + * wl_event_source_remove(). Calling wl_event_source_remove() after or from + * within the callback results in undefined behaviour. + * + * \sa wl_event_loop_idle_func_t + * \memberof wl_event_source + */ +WL_EXPORT struct wl_event_source * +wl_event_loop_add_idle(struct wl_event_loop *loop, + wl_event_loop_idle_func_t func, + void *data) +{ + struct wl_event_source_idle *source; + + source = malloc(sizeof *source); + if (source == NULL) + return NULL; + + source->base.interface = &idle_source_interface; + source->base.loop = loop; + source->base.fd = -1; + + source->func = func; + source->base.data = data; + + wl_list_insert(loop->idle_list.prev, &source->base.link); + + return &source->base; +} + +/** Mark event source to be re-checked + * + * \param source The event source to be re-checked. + * + * This function permanently marks the event source to be re-checked after + * the normal dispatch of sources in wl_event_loop_dispatch(). Re-checking + * will keep iterating over all such event sources until the dispatch + * function for them all returns zero. + * + * Re-checking is used on sources that may become ready to dispatch as a + * side-effect of dispatching themselves or other event sources, including idle + * sources. Re-checking ensures all the incoming events have been fully drained + * before wl_event_loop_dispatch() returns. + * + * \memberof wl_event_source + */ +WL_EXPORT void +wl_event_source_check(struct wl_event_source *source) +{ + wl_list_insert(source->loop->check_list.prev, &source->link); +} + +/** Remove an event source from its event loop + * + * \param source The event source to be removed. + * \return Zero. + * + * The event source is removed from the event loop it was created for, + * and is effectively destroyed. This invalidates \c source . + * The dispatch function of the source will no longer be called through this + * source. + * + * \memberof wl_event_source + */ +WL_EXPORT int +wl_event_source_remove(struct wl_event_source *source) +{ + struct wl_event_loop *loop = source->loop; + int ret = 0, saved_errno = 0; + + /* + * Since BSD doesn't treat all event sources as FDs, we need to + * differentiate by source interface. + */ + if (source->interface == &fd_source_interface && source->fd >= 0) { + struct kevent ev[2]; + int _ret[2], _saved_errno[2]; + + /* + * We haven't stored state about the mask used when adding the + * source, so we have to try and remove both READ and WRITE + * filters. One may fail, which is OK. Removal of the source has + * only failed if _both_ kevent() calls fail. We have to do two + * kevent() calls so that we can get independent return values + * for the two kevents. + */ + EV_SET(&ev[0], source->fd, EVFILT_READ, EV_DELETE, 0, 0, + source); + EV_SET(&ev[1], source->fd, EVFILT_WRITE, EV_DELETE, 0, 0, + source); + + _ret[0] = kevent(loop->event_fd, &ev[0], 1, NULL, 0, NULL); + _saved_errno[0] = errno; + _ret[1] = kevent(loop->event_fd, &ev[1], 1, NULL, 0, NULL); + _saved_errno[1] = errno; + + if (_ret[0] >= _ret[1]) { + ret = _ret[0]; + saved_errno = _saved_errno[0]; + } else { + ret = _ret[1]; + saved_errno = _saved_errno[1]; + } + } else if (source->interface == &timer_source_interface) { + struct kevent ev; + struct wl_event_source_timer *source_t; + + EV_SET(&ev, source->fd, EVFILT_TIMER, EV_DELETE, 0, 0, source); + ret = kevent(loop->event_fd, &ev, 1, NULL, 0, NULL); + saved_errno = errno; + source_t = (struct wl_event_source_timer*)source; + wl_list_remove(&source_t->timer_link); + } else if (source->interface == &signal_source_interface) { + struct kevent ev; + struct wl_event_source_signal *source_s; + + source_s = (struct wl_event_source_signal *) source; + wl_list_remove(&source_s->signal_link); + + if (wl_list_empty(&loop->signals[source->fd])) { + EV_SET(&ev, source->fd, EVFILT_SIGNAL, EV_DELETE, 0, 0, + source); + ret = kevent(loop->event_fd, &ev, 1, NULL, 0, NULL); + saved_errno = errno; + } + } + + /* Handle any errors from kevent() calls. */ + if (ret < 0) { + fprintf(stderr, + "error removing event (%i) from kqueue: %s\n", + source->fd, strerror(saved_errno)); + } + + /* Tidy up the source. */ + source->fd = -1; + + wl_list_remove(&source->link); + wl_list_insert(&loop->destroy_list, &source->link); + + return 0; +} + +static void +wl_event_loop_process_destroy_list(struct wl_event_loop *loop) +{ + struct wl_event_source *source, *next; + + wl_list_for_each_safe(source, next, &loop->destroy_list, link) + free(source); + + wl_list_init(&loop->destroy_list); +} + +/** Create a new event loop context + * + * \return A new event loop context object. + * + * This creates a new event loop context. Initially this context is empty. + * Event sources need to be explicitly added to it. + * + * Normally the event loop is run by calling wl_event_loop_dispatch() in + * a loop until the program terminates. Alternatively, an event loop can be + * embedded in another event loop by its file descriptor, see + * wl_event_loop_get_fd(). + * + * \memberof wl_event_loop + */ +WL_EXPORT struct wl_event_loop * +wl_event_loop_create(void) +{ + struct wl_event_loop *loop; + int i; + + loop = malloc(sizeof *loop); + if (loop == NULL) + return NULL; + loop->event_fd = wl_os_queue_create_cloexec(); + if (loop->event_fd < 0) { + free(loop); + return NULL; + } + + wl_list_init(&loop->check_list); + wl_list_init(&loop->idle_list); + wl_list_init(&loop->destroy_list); + wl_list_init(&loop->timers); + for (i = 0; i < NSIG; i++) + wl_list_init(&loop->signals[i]); + + wl_signal_init(&loop->destroy_signal); + + return loop; +} + +/** Destroy an event loop context + * + * \param loop The event loop to be destroyed. + * + * This emits the event loop destroy signal, closes the event loop file + * descriptor, and frees \c loop. + * + * If the event loop has existing sources, those cannot be safely removed + * afterwards. Therefore one must call wl_event_source_remove() on all + * event sources before destroying the event loop context. + * + * \memberof wl_event_loop + */ +WL_EXPORT void +wl_event_loop_destroy(struct wl_event_loop *loop) +{ + wl_signal_emit(&loop->destroy_signal, loop); + + wl_event_loop_process_destroy_list(loop); + close(loop->event_fd); + free(loop); +} + +static bool +post_dispatch_check(struct wl_event_loop *loop) +{ + struct wl_event_source *source, *next; + bool needs_recheck = false; + + struct kevent ev; + memset(&ev, 0, sizeof(struct kevent)); + wl_list_for_each_safe(source, next, &loop->check_list, link) { + int dispatch_result; + + dispatch_result = source->interface->dispatch(source, &ev); + if (dispatch_result < 0) { + wl_log("Source dispatch function returned negative value!"); + wl_log("This would previously accidentally suppress a follow-up dispatch"); + } + needs_recheck |= dispatch_result != 0; + } + + return needs_recheck; +} + +/** Dispatch the idle sources + * + * \param loop The event loop whose idle sources are dispatched. + * + * \sa wl_event_loop_add_idle() + * \memberof wl_event_loop + */ +WL_EXPORT void +wl_event_loop_dispatch_idle(struct wl_event_loop *loop) +{ + struct wl_event_source_idle *source; + + while (!wl_list_empty(&loop->idle_list)) { + source = container_of(loop->idle_list.next, + struct wl_event_source_idle, base.link); + source->func(source->base.data); + wl_event_source_remove(&source->base); + } +} + +/** Wait for events and dispatch them + * + * \param loop The event loop whose sources to wait for. + * \param timeout The polling timeout in milliseconds. + * \return 0 for success, -1 for polling error. + * + * All the associated event sources are polled. This function blocks until + * any event source delivers an event (idle sources excluded), or the timeout + * expires. A timeout of -1 disables the timeout, causing the function to block + * indefinitely. A timeout of zero causes the poll to always return immediately. + * + * All idle sources are dispatched before blocking. An idle source is destroyed + * when it is dispatched. After blocking, all other ready sources are + * dispatched. Then, idle sources are dispatched again, in case the dispatched + * events created idle sources. Finally, all sources marked with + * wl_event_source_check() are dispatched in a loop until their dispatch + * functions all return zero. + * + * \memberof wl_event_loop + */ +WL_EXPORT int +wl_event_loop_dispatch(struct wl_event_loop *loop, int timeout) +{ + /* kqueue sends separate events for r/w */ + struct kevent ev[64]; + struct wl_event_source *source; + int i, count, n; + struct timespec timeout_spec; + + wl_event_loop_dispatch_idle(loop); + + /* timeout is provided in milliseconds */ + timeout_spec.tv_sec = timeout / 1000; + timeout_spec.tv_nsec = (timeout % 1000) * 1000000; + + count = kevent(loop->event_fd, NULL, 0, ev, ARRAY_LENGTH(ev), + (timeout != -1) ? &timeout_spec : NULL); + if (count < 0) + return -1; + + for (i = 0; i < count; i++) { + if (ev[i].filter == EVFILT_SIGNAL) { + warnx("dispatching signal %d", ev[i].ident); + dispatch_signal_event(loop, ev[i].ident); + } else { + source = ev[i].udata; + if (source->fd != -1) + source->interface->dispatch(source, &ev[i]); + } + } + + wl_event_loop_process_destroy_list(loop); + + do { + n = post_dispatch_check(loop); + } while (n > 0); + + return 0; +} + +/** Get the event loop file descriptor + * + * \param loop The event loop context. + * \return The aggregate file descriptor. + * + * This function returns the aggregate file descriptor, that represents all + * the event sources (idle sources excluded) associated with the given event + * loop context. When any event source makes an event available, it will be + * reflected in the aggregate file descriptor. + * + * When the aggregate file descriptor delivers an event, one can call + * wl_event_loop_dispatch() on the event loop context to dispatch all the + * available events. + * + * \memberof wl_event_loop + */ +WL_EXPORT int +wl_event_loop_get_fd(struct wl_event_loop *loop) +{ + return loop->event_fd; +} + +/** Register a destroy listener for an event loop context + * + * \param loop The event loop context whose destruction to listen for. + * \param listener The listener with the callback to be called. + * + * \sa wl_listener + * \memberof wl_event_loop + */ +WL_EXPORT void +wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, + struct wl_listener *listener) +{ + wl_signal_add(&loop->destroy_signal, listener); +} + +/** Get the listener struct for the specified callback + * + * \param loop The event loop context to inspect. + * \param notify The destroy callback to find. + * \return The wl_listener registered to the event loop context with + * the given callback pointer. + * + * \memberof wl_event_loop + */ +WL_EXPORT struct wl_listener * +wl_event_loop_get_destroy_listener(struct wl_event_loop *loop, + wl_notify_func_t notify) +{ + return wl_signal_get(&loop->destroy_signal, notify); +} +#endif diff --git a/src/wayland-os.c b/src/wayland-os.c index 93b6f5f..1614c94 100644 --- a/src/wayland-os.c +++ b/src/wayland-os.c @@ -25,14 +25,20 @@ #define _GNU_SOURCE +#include "../config.h" + #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> +#ifdef HAVE_SYS_EPOLL_H #include <sys/epoll.h> +#endif +#ifdef HAVE_SYS_EVENT_H +#include <sys/event.h> +#endif -#include "../config.h" #include "wayland-os.h" static int @@ -62,26 +68,50 @@ wl_os_socket_cloexec(int domain, int type, int protocol) { int fd; +#ifdef SOCK_CLOEXEC 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_socketpair_cloexec(int domain, int type, int protocol, int sv[2]) +{ + int retval; + +#ifdef SOCK_CLOEXEC + retval = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (retval >= 0) + return retval; + if (errno != EINVAL) + return -1; +#endif + + retval = socketpair(domain, type, protocol, sv); + if (set_cloexec_or_close(sv[0]) < 0 || set_cloexec_or_close(sv[1]) < 0) + retval = -1; + + return retval; +} + int wl_os_dupfd_cloexec(int fd, long minfd) { int newfd; +#ifdef F_DUPFD_CLOEXEC newfd = fcntl(fd, F_DUPFD_CLOEXEC, minfd); if (newfd >= 0) return newfd; if (errno != EINVAL) return -1; +#endif newfd = fcntl(fd, F_DUPFD, minfd); return set_cloexec_or_close(newfd); @@ -123,17 +153,20 @@ wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags) { ssize_t len; +#ifdef MSG_CMSG_CLOEXEC len = recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC); if (len >= 0) return len; if (errno != EINVAL) return -1; +#endif return recvmsg_cloexec_fallback(sockfd, msg, flags); } +#if defined(HAVE_SYS_EPOLL_H) int -wl_os_epoll_create_cloexec(void) +wl_os_queue_create_cloexec(void) { int fd; @@ -148,6 +181,16 @@ wl_os_epoll_create_cloexec(void) fd = epoll_create(1); return set_cloexec_or_close(fd); } +#elif defined(HAVE_SYS_EVENT_H) +int +wl_os_queue_create_cloexec(void) +{ + int fd; + + fd = kqueue(); + return set_cloexec_or_close(fd); +} +#endif int wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) diff --git a/src/wayland-os.h b/src/wayland-os.h index f51efaa..321e34d 100644 --- a/src/wayland-os.h +++ b/src/wayland-os.h @@ -36,7 +36,7 @@ ssize_t wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags); int -wl_os_epoll_create_cloexec(void); +wl_os_queue_create_cloexec(void); int wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen); diff --git a/tests/os-wrappers-test.c b/tests/os-wrappers-test.c index 102622c..64e04b0 100644 --- a/tests/os-wrappers-test.c +++ b/tests/os-wrappers-test.c @@ -26,6 +26,7 @@ #define _GNU_SOURCE +#include <err.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> @@ -38,7 +39,11 @@ #include <stdarg.h> #include <fcntl.h> #include <stdio.h> -#include <sys/epoll.h> +#ifdef HAVE_SYS_EPOLL_H +# include <sys/epoll.h> +#else +# include <sys/event.h> +#endif #include "wayland-private.h" #include "test-runner.h" @@ -55,8 +60,15 @@ static int wrapped_calls_fcntl; static ssize_t (*real_recvmsg)(int, struct msghdr *, int); static int wrapped_calls_recvmsg; +#ifdef HAVE_SYS_EPOLL_H static int (*real_epoll_create1)(int); static int wrapped_calls_epoll_create1; +#endif + +#if HAVE_SYS_EVENT_H +static int (*real_kqueue)(void); +static int wrapped_calls_kqueue; +#endif static void init_fallbacks(int do_fallbacks) @@ -65,7 +77,12 @@ init_fallbacks(int do_fallbacks) real_socket = dlsym(RTLD_NEXT, "socket"); real_fcntl = dlsym(RTLD_NEXT, "fcntl"); real_recvmsg = dlsym(RTLD_NEXT, "recvmsg"); +#ifdef HAVE_SYS_EPOLL_H real_epoll_create1 = dlsym(RTLD_NEXT, "epoll_create1"); +#endif +#ifdef HAVE_SYS_EVENT_H + real_kqueue = dlsym(RTLD_NEXT, "kqueue"); +#endif } __attribute__ ((visibility("default"))) int @@ -81,6 +98,8 @@ socket(int domain, int type, int protocol) return real_socket(domain, type, protocol); } +/* won't work on OpenBSD, since real fcntl have to be called early */ +#if !defined(__OpenBSD__) __attribute__ ((visibility("default"))) int fcntl(int fd, int cmd, ...) { @@ -100,6 +119,7 @@ fcntl(int fd, int cmd, ...) return real_fcntl(fd, cmd, arg); } +#endif __attribute__ ((visibility("default"))) ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) @@ -114,6 +134,7 @@ recvmsg(int sockfd, struct msghdr *msg, int flags) return real_recvmsg(sockfd, msg, flags); } +#ifdef HAVE_SYS_EPOLL_H __attribute__ ((visibility("default"))) int epoll_create1(int flags) { @@ -127,6 +148,23 @@ epoll_create1(int flags) return real_epoll_create1(flags); } +#endif + +#ifdef HAVE_SYS_EVENT_H +__attribute__ ((visibility("default"))) int +kqueue(void) +{ + wrapped_calls_kqueue++; + + if (fall_back) { + wrapped_calls_kqueue++; /* kqueue() not wrapped */ + errno = EINVAL; + return -1; + } + + return real_kqueue(); +} +#endif static void do_os_wrappers_socket_cloexec(int n) @@ -184,6 +222,7 @@ do_os_wrappers_dupfd_cloexec(int n) * Must have 4 calls if falling back, but must also allow * falling back without a forced fallback. */ + warnx("wrapped_calls_fcntl is %d, should be larger than %d", wrapped_calls_fcntl, n); assert(wrapped_calls_fcntl > n); exec_fd_leak_check(nr_fds); @@ -335,14 +374,14 @@ TEST(os_wrappers_recvmsg_cloexec_fallback) } static void -do_os_wrappers_epoll_create_cloexec(int n) +do_os_wrappers_queue_create_cloexec(int n) { int fd; int nr_fds; nr_fds = count_open_fds(); - fd = wl_os_epoll_create_cloexec(); + fd = wl_os_queue_create_cloexec(); assert(fd >= 0); #ifdef EPOLL_CLOEXEC @@ -354,16 +393,18 @@ do_os_wrappers_epoll_create_cloexec(int n) exec_fd_leak_check(nr_fds); } -TEST(os_wrappers_epoll_create_cloexec) +TEST(os_wrappers_queue_create_cloexec) { init_fallbacks(0); - do_os_wrappers_epoll_create_cloexec(1); + do_os_wrappers_queue_create_cloexec(1); } -TEST(os_wrappers_epoll_create_cloexec_fallback) +TEST(os_wrappers_queue_create_cloexec_fallback) { init_fallbacks(1); - do_os_wrappers_epoll_create_cloexec(2); + do_os_wrappers_queue_create_cloexec(2); } /* FIXME: add tests for wl_os_accept_cloexec() */ + +/* FIXME: add tests for kqueue() */ -- 2.20.1 From 60741cd3ba92d6bcb784e4c4fe15e48750029d15 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 11:53:20 +0200 Subject: [PATCH 6/8] client: define EPROTO if it's not defined Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- src/wayland-client.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wayland-client.h b/src/wayland-client.h index 9f70fa3..524f79d 100644 --- a/src/wayland-client.h +++ b/src/wayland-client.h @@ -36,6 +36,10 @@ #ifndef WAYLAND_CLIENT_H #define WAYLAND_CLIENT_H +#ifndef EPROTO +# define EPROTO ENOPROTOOPT +#endif + #include "wayland-client-core.h" #include "wayland-client-protocol.h" -- 2.20.1 From 809256b3a8b3c237f0d2f65575ee80862ac71d26 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 12:11:49 +0200 Subject: [PATCH 7/8] tests: support waitpid() Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- configure.ac | 7 ++++++ tests/test-compositor.c | 23 +++++++++++++++-- tests/test-runner.c | 55 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 941749d..1d1a25b 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,13 @@ if test "x$ac_cv_header_kvm_h" != "x" && test "x$ac_cv_lib_kvm_kvm_getfiles" != AC_DEFINE(USE_LIBKVM, 1, [use libkvm on BSD]) fi +# Defines __FreeBSD__ if we're on FreeBSD, same for other *BSD +AC_CHECK_HEADERS([sys/param.h]) + +# waitid() and signal.h are needed for the test suite. +AC_CHECK_FUNCS([waitid]) +AC_CHECK_HEADERS([signal.h]) + AC_ARG_ENABLE([libraries], [AC_HELP_STRING([--disable-libraries], [Disable compilation of wayland libraries])], diff --git a/tests/test-compositor.c b/tests/test-compositor.c index 72f6351..27285ee 100644 --- a/tests/test-compositor.c +++ b/tests/test-compositor.c @@ -86,8 +86,8 @@ get_socket_name(void) static char retval[64]; gettimeofday(&tv, NULL); - snprintf(retval, sizeof retval, "wayland-test-%d-%ld%ld", - getpid(), tv.tv_sec, tv.tv_usec); + snprintf(retval, sizeof retval, "wayland-test-%d-%lld%lld", + getpid(), (long long)tv.tv_sec, (long long)tv.tv_usec); return retval; } @@ -97,10 +97,15 @@ handle_client_destroy(void *data) { struct client_info *ci = data; struct display *d; +#ifdef HAVE_WAITID siginfo_t status; +#else + int istatus; +#endif d = ci->display; +#ifdef HAVE_WAITID assert(waitid(P_PID, ci->pid, &status, WEXITED) != -1); switch (status.si_code) { @@ -118,6 +123,20 @@ handle_client_destroy(void *data) ci->exit_code = status.si_status; break; } +#else + assert(waitpid(ci->pid, &istatus, WNOHANG) != -1); + + if (WIFSIGNALED(istatus)) { + fprintf(stderr, "Client '%s' was killed by signal %d\n", + ci->name, WTERMSIG(istatus)); + ci->exit_code = WEXITSTATUS(istatus); + } else if (WIFEXITED(istatus)) { + if (WEXITSTATUS(istatus) != EXIT_SUCCESS) + fprintf(stderr, "Client '%s' exited with code %d\n", + ci->name, WEXITSTATUS(istatus)); + ci->exit_code = WEXITSTATUS(istatus); + } +#endif ++d->clients_terminated_no; if (d->clients_no == d->clients_terminated_no) { diff --git a/tests/test-runner.c b/tests/test-runner.c index 1487dc4..2936a59 100644 --- a/tests/test-runner.c +++ b/tests/test-runner.c @@ -25,6 +25,12 @@ #define _GNU_SOURCE +#include "../config.h" + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + #include <unistd.h> #include <stdio.h> #include <stdlib.h> @@ -32,18 +38,30 @@ #include <sys/wait.h> #include <sys/stat.h> #include <string.h> +#include <signal.h> #include <assert.h> #include <dlfcn.h> #include <errno.h> #include <limits.h> #include <sys/ptrace.h> +#ifdef HAVE_SYS_PRCTL_H #include <sys/prctl.h> +#endif #ifndef PR_SET_PTRACER # define PR_SET_PTRACER 0x59616d61 #endif #include "test-runner.h" +extern const struct test __start_test_section, __stop_test_section; + +#ifndef __linux__ +/* XXX review ptrace() usage */ +#define PTRACE_ATTACH PT_ATTACH +#define PTRACE_CONT PT_CONTINUE +#define PTRACE_DETACH PT_DETACH +#endif + /* when set to 1, check if tests are not leaking opened files. * It is turned on by default. It can be turned off by * WAYLAND_TEST_NO_LEAK_CHECK environment variable. */ @@ -51,7 +69,7 @@ int fd_leak_check_enabled; /* when this var is set to 0, every call to test_set_timeout() is * suppressed - handy when debugging the test. Can be set by - * WAYLAND_TEST_NO_TIMEOUTS environment variable. */ + * WAYLAND_TESTS_NO_TIMEOUTS evnironment var */ static int timeouts_enabled = 1; /* set to one if the output goes to the terminal */ @@ -229,6 +247,10 @@ stderr_reset_color(void) static int is_debugger_attached(void) { +#ifdef __OpenBSD__ + /* OpenBSD doesn't allow to trace parent process */ + return 0; +#else int status; int rc; pid_t pid; @@ -239,6 +261,8 @@ is_debugger_attached(void) return 0; } + +// xxx start here pid = fork(); if (pid == -1) { perror("fork"); @@ -259,7 +283,7 @@ is_debugger_attached(void) _exit(1); if (!waitpid(-1, NULL, 0)) _exit(1); - ptrace(PTRACE_CONT, NULL, NULL); + ptrace(PTRACE_CONT, ppid, NULL, NULL); ptrace(PTRACE_DETACH, ppid, NULL, NULL); _exit(0); } else { @@ -286,6 +310,7 @@ is_debugger_attached(void) } return rc; +#endif } int main(int argc, char *argv[]) @@ -293,7 +318,11 @@ int main(int argc, char *argv[]) const struct test *t; pid_t pid; int total, pass; +#ifdef HAVE_WAITID siginfo_t info; +#else + int status; +#endif if (isatty(fileno(stderr))) is_atty = 1; @@ -336,7 +365,8 @@ int main(int argc, char *argv[]) if (pid == 0) run_test(t); /* never returns */ - if (waitid(P_PID, pid, &info, WEXITED)) { +#ifdef HAVE_WAITID + if (waitid(P_PID, 0, &info, WEXITED)) { stderr_set_color(RED); fprintf(stderr, "waitid failed: %m\n"); stderr_reset_color(); @@ -367,6 +397,25 @@ int main(int argc, char *argv[]) break; } +#else + if (waitpid(-1, &status, 0) == -1) { + fprintf(stderr, "waitpid failed: %s\n", + strerror(errno)); + abort(); + } + + fprintf(stderr, "test \"%s\":\t", t->name); + if (WIFEXITED(status)) { + fprintf(stderr, "exit status %d", WEXITSTATUS(status)); + if (WEXITSTATUS(status) == EXIT_SUCCESS) + success = 1; + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "signal %d", WTERMSIG(status)); + } +#endif + + if (t->must_fail) + success = !success; if (success) { pass++; -- 2.20.1 From 264d6a361e867f5c96e7ac36b487a81e71ece456 Mon Sep 17 00:00:00 2001 From: Leonid Bobrov <mazoc...@disroot.org> Date: Fri, 8 Feb 2019 13:02:42 +0200 Subject: [PATCH 8/8] configure: detect libdl and librt Signed-off-by: Leonid Bobrov <mazoc...@disroot.org> --- Makefile.am | 6 +++--- configure.ac | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 426e20a..b973cbe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,7 +71,7 @@ nodist_include_HEADERS = \ protocol/wayland-client-protocol.h libwayland_server_la_CFLAGS = $(FFI_CFLAGS) $(AM_CFLAGS) -pthread -libwayland_server_la_LIBADD = $(FFI_LIBS) libwayland-private.la libwayland-util.la -lrt -lm +libwayland_server_la_LIBADD = $(FFI_LIBS) libwayland-private.la libwayland-util.la $(RT_LIBS) -lm libwayland_server_la_LDFLAGS = -version-info 1:0:1 libwayland_server_la_SOURCES = \ src/wayland-server.c \ @@ -84,7 +84,7 @@ nodist_libwayland_server_la_SOURCES = \ protocol/wayland-protocol.c libwayland_client_la_CFLAGS = $(FFI_CFLAGS) $(AM_CFLAGS) -pthread -libwayland_client_la_LIBADD = $(FFI_LIBS) libwayland-private.la libwayland-util.la -lrt -lm +libwayland_client_la_LIBADD = $(FFI_LIBS) libwayland-private.la libwayland-util.la $(RT_LIBS) -lm libwayland_client_la_LDFLAGS = -version-info 3:0:3 libwayland_client_la_SOURCES = \ src/wayland-client.c @@ -216,7 +216,7 @@ noinst_LTLIBRARIES += \ libtest-helpers.la libtest_helpers_la_SOURCES = tests/test-helpers.c -libtest_helpers_la_LIBADD = -lrt -ldl $(FFI_LIBS) $(KVM_LIBS) +libtest_helpers_la_LIBADD = $(RT_LIBS) $(DL_LIBS) $(FFI_LIBS) $(KVM_LIBS) libtest_runner_la_SOURCES = \ tests/test-runner.c \ diff --git a/configure.ac b/configure.ac index 1d1a25b..40cbd08 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,22 @@ if test "x$ac_cv_header_kvm_h" != "x" && test "x$ac_cv_lib_kvm_kvm_getfiles" != AC_DEFINE(USE_LIBKVM, 1, [use libkvm on BSD]) fi +# *BSD don't have libdl, but they have its functions +SAVE_LIBS="$LIBS" +LIBS= +AC_CHECK_LIB([dl], [dlsym]) +DL_LIBS="$LIBS" +LIBS="$SAVE_LIBS" +AC_SUBST(DL_LIBS) + +# *BSD don't have librt, but they have its functions +SAVE_LIBS="$LIBS" +LIBS= +AC_CHECK_LIB([rt], [clock_gettime]) +RT_LIBS="$LIBS" +LIBS="$SAVE_LIBS" +AC_SUBST(RT_LIBS) + # Defines __FreeBSD__ if we're on FreeBSD, same for other *BSD AC_CHECK_HEADERS([sys/param.h]) -- 2.20.1 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/wayland-devel