Compare commits

...

13 commits

Author SHA1 Message Date
Demi Marie Obenour
7d5c30246a Merge branch 'cleanups' into 'main'
Miscellaneous cleanups

See merge request wayland/wayland!414
2025-10-07 20:50:06 -04:00
Isaac Freund
d81525a235 client: add wl_display_dispatch_pending_single
As well as wl_display_dispatch_queue_pending_single.

The motivation is writing libwayland bindings for a dynamic language
with exceptions/non-local returns. Since it is invalid for a
wl_dispatcher_func_t callback provided to libwayland to not return,
there is no way to prevent dispatching of further events in the case of
an exception in the dynamic language event handler.

Furthermore, since creating/destroying Wayland objects in an event
handler affects the dispatching of subsequent events by libwayland,
it is not possible to collect Wayland events in a queue outside
libwayland and dispatch them one-by-one after
wl_display_dispatch_pending() returns.

Adding libwayland API to dispatch at most one pending event solves this
problem cleanly. The bindings can have libwayland dispatch a single
event, wait for wl_display_dispatch_pending_single() to return, run the
dynamic language event handler (which may longjmp away), and continue
the loop for as long as there are more events to dispatch.

References: https://codeberg.org/ifreund/janet-wayland
Signed-off-by: Isaac Freund <mail@isaacfreund.com>
2025-09-16 11:48:33 +03:00
Kyle Brenneman
4673ef7e9c connection: Add a thread ID to WAYLAND_DEBUG output.
If WAYLAND_DEBUG contains the token "thread_id", and gettid() is
available, then include the current thread ID in the output from
wl_closure_print.

If multiple threads are sending requests, then those requests can get
interleaved. That's usually fine, but for wl_surface requests and
commits, that can cause problems ranging from incorrect behavior to
protocol errors.

Being able to see which requests are sent by different threads would
make such problems much easier to diagnose.

Signed-off-by: Kyle Brenneman <kbrenneman@nvidia.com>
2025-09-15 14:45:53 +01:00
Kyle Brenneman
77730f10a0 connection: Add a function to parse WAYLAND_DEBUG tokens
Add a new function, wl_check_env_token, to scan for a token in a
comma-separated string.

Change wl_display_create in wayland-server.c and
wl_display_connect_to_fd in wayland-client.c to use that instead of a
simple substring search.

This means that WAYLAND_DEBUG will accept a value like "client,server"
but not "clientserver". But, this will make it easier to add other
tokens without worrying about overlap between them.

Signed-off-by: Kyle Brenneman <kbrenneman@nvidia.com>
2025-09-15 14:45:53 +01:00
YaoBing Xiao
264da6a92b cursor: Free theme when size check fails to avoid memory leak
Signed-off-by: YaoBing Xiao <xiaoyaobing@uniontech.com>
2025-08-03 11:36:34 +00:00
ykla
cd0d1543c0 ci: upgrade FreeBSD to 14.3
Signed-off-by: ykla yklaxds@gmail.com
2025-07-20 02:09:35 +00:00
Demi Marie Obenour
3ea4b30700 connection: check for NULL string only once
This removes a redundant check and combines two others.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-11 19:23:21 -04:00
Demi Marie Obenour
bd0aa37eb9 server: Make wl_resource_post_no_memory() a wrapper function
Save some code size.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:56 -04:00
Demi Marie Obenour
2bd88ca4bf connection: Document correct use of atoi()
atoi() has undefined behavior on invalid input, but here the input comes
from wayland-scanner, which is trusted.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:43 -04:00
Demi Marie Obenour
ac630dd3b4 connection: More explanations for why the code is safe
This adds many assertions to check that buffers are valid.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:38 -04:00
Demi Marie Obenour
568a9325f0 connection: empty iovecs are never created
Neither ring_buffer_put_iov() nor ring_buffer_get_iov() distinguishes
between a full wl_ring_buffer and an empty one.  Instead, both just
assume that the returned iovec should not be empty.  However, both have
only one caller, and that caller does guarantee that the ring buffer is
not full (for ring_buffer_put_iov()) or empty (for
ring_buffer_get_iov()).  Therefore, the code is safe.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:32 -04:00
Demi Marie Obenour
f00586ee5f connection: Add comments explaining safety
No functional change intended.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:26 -04:00
Demi Marie Obenour
9e9de6c9cd connection: Use bool, not int, for a boolean variable
No functional change intended.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
2024-08-09 18:11:20 -04:00
9 changed files with 366 additions and 37 deletions

View file

@ -43,7 +43,7 @@ include:
# API changes. If you need new features from ci-templates you must bump
# this to the current SHA you require from the ci-templates repo, however
# be aware that you may need to account for API changes when doing so.
ref: 32afe5644697e503af18a736587c8619fa036a72
ref: 48c2c583a865bd59be21e8938df247faf460099c
file:
- '/templates/debian.yml'
- '/templates/freebsd.yml'
@ -306,11 +306,11 @@ armv7-release-debian-build:
.os-freebsd:
variables:
BUILD_OS: freebsd
FDO_DISTRIBUTION_VERSION: "14.2"
FDO_DISTRIBUTION_VERSION: "14.3"
FDO_DISTRIBUTION_PACKAGES: 'libxslt meson ninja pkgconf expat libffi libepoll-shim libxml2'
# bump this tag every time you change something which requires rebuilding the
# base image
FDO_DISTRIBUTION_TAG: "2025-06-23.1"
FDO_DISTRIBUTION_TAG: "2025-07-20.0"
# Don't build documentation since installing the required tools massively
# increases the VM image (and therefore container) size.
MESON_ARGS: "--fatal-meson-warnings -Dwerror=true -Ddocumentation=false"

View file

@ -398,7 +398,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm)
return NULL;
if (size < 0 || (size > 0 && INT_MAX / size / 4 < size))
return NULL;
goto err;
if (!name)
name = "default";
@ -409,7 +409,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm)
theme->pool = shm_pool_create(shm, size * size * 4);
if (!theme->pool)
goto out_error_pool;
goto err;
xcursor_load_theme(name, size, load_callback, theme);
@ -421,7 +421,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm)
return theme;
out_error_pool:
err:
free(theme);
return NULL;
}

View file

@ -46,6 +46,7 @@ have_funcs = [
'memfd_create',
'mremap',
'strndup',
'gettid',
]
foreach f: have_funcs
config_h.set('HAVE_' + f.underscorify().to_upper(), cc.has_function(f))

View file

@ -26,6 +26,8 @@
#define _GNU_SOURCE
#include "../config.h"
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
@ -91,19 +93,45 @@ ring_buffer_mask(const struct wl_ring_buffer *b, size_t i) {
return i & m;
}
static size_t
ring_buffer_size(struct wl_ring_buffer *b)
{
return b->head - b->tail;
}
/* Precondition: the data will not overflow the buffer */
static int
ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count)
{
size_t head, size;
size_t head, size, buffer_size, capacity;
if (b->head < b->tail) {
wl_abort("ring_buffer_put: ring buffer corrupt, %zu < %zu\n",
b->head, b->tail);
}
capacity = ring_buffer_capacity(b);
buffer_size = ring_buffer_size(b);
if (buffer_size > capacity) {
wl_abort("ring_buffer_put: ring buffer corrupt: "
"%zu - %zu > %zu\n", b->head, b->tail, capacity);
}
if (count == 0)
return 0;
if (capacity - buffer_size < count) {
wl_abort("ring_buffer_put: attempt to overfill buffer: "
"%zu - %zu < %zu\n", capacity, buffer_size, count);
}
head = ring_buffer_mask(b, b->head);
if (head + count <= ring_buffer_capacity(b)) {
size = capacity - head;
if (count <= size) {
/* Enough space after head to fulfill request */
memcpy(b->data + head, data, count);
} else {
size = ring_buffer_capacity(b) - head;
/* Need to wrap around */
memcpy(b->data + head, data, size);
memcpy(b->data, (const char *) data + size, count - size);
}
@ -113,78 +141,160 @@ ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count)
return 0;
}
/* Precondition: the buffer is not full */
static void
ring_buffer_put_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
{
size_t head, tail;
size_t head, tail, size, capacity;
if (b->head < b->tail) {
wl_abort("ring_buffer_put_iov: ring buffer corrupt, %zu < %zu\n",
b->head, b->tail);
}
size = ring_buffer_size(b);
capacity = ring_buffer_capacity(b);
if (size >= capacity) {
wl_abort("ring_buffer_put_iov: ring buffer full or corrupt: "
"%zu - %zu >= %zu\n", b->head, b->tail, capacity);
}
head = ring_buffer_mask(b, b->head);
tail = ring_buffer_mask(b, b->tail);
if (head < tail) {
/* Buffer is like this:
* head tail
* | |
* +---------+-----------------+---------+
* | VALID | INVALID | VALID |
* +---------+-----------------+---------+
*/
iov[0].iov_base = b->data + head;
iov[0].iov_len = tail - head;
*count = 1;
} else if (tail == 0) {
/* Buffer is like this:
* tail head
* | |
* +---------------------------+---------+
* | VALID | INVALID |
* +---------------------------+---------+
*/
iov[0].iov_base = b->data + head;
iov[0].iov_len = ring_buffer_capacity(b) - head;
iov[0].iov_len = capacity - head;
*count = 1;
} else {
/* Buffer is like this:
* tail head
* | |
* +---------------------------+---------+
* | INVALID | VALID | INVALID |
* +---------------------------+---------+
*/
iov[0].iov_base = b->data + head;
iov[0].iov_len = ring_buffer_capacity(b) - head;
iov[0].iov_len = capacity - head;
iov[1].iov_base = b->data;
iov[1].iov_len = tail;
*count = 2;
}
}
/* Precondition: the buffer is not empty */
static void
ring_buffer_get_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
{
size_t head, tail;
size_t head, tail, capacity;
if (b->head <= b->tail) {
wl_abort("ring_buffer_get_iov(): empty or corrupt buffer: %zu <= %zu\n",
b->head, b->tail);
}
capacity = ring_buffer_capacity(b);
if (ring_buffer_size(b) > capacity) {
wl_abort("ring_buffer_put_iov: ring buffer corrupt: "
"%zu - %zu > %zu\n", b->head, b->tail, capacity);
}
head = ring_buffer_mask(b, b->head);
tail = ring_buffer_mask(b, b->tail);
if (tail < head) {
/* Buffer is like this:
* tail head
* | |
* +---------+-----------------+---------+
* | INVALID | VALID | INVALID |
* +---------+-----------------+---------+
*/
iov[0].iov_base = b->data + tail;
iov[0].iov_len = head - tail;
*count = 1;
} else if (head == 0) {
/* Buffer is like this:
* head tail
* | |
* +---------------------------+---------+
* | INVALID | VALID |
* +---------------------------+---------+
*/
iov[0].iov_base = b->data + tail;
iov[0].iov_len = ring_buffer_capacity(b) - tail;
iov[0].iov_len = capacity - tail;
*count = 1;
} else {
/* Buffer is like this:
* head tail
* | |
* +-------+-------------------+---------+
* | VALID | INVALID | VALID |
* +---------------------------+---------+
*/
iov[0].iov_base = b->data + tail;
iov[0].iov_len = ring_buffer_capacity(b) - tail;
iov[0].iov_len = capacity - tail;
iov[1].iov_base = b->data;
iov[1].iov_len = head;
*count = 2;
}
}
/* Precondition: the data will not underflow the buffer */
static void
ring_buffer_copy(struct wl_ring_buffer *b, void *data, size_t count)
{
size_t tail, size;
size_t tail, size, buffer_size, capacity;
if (b->head < b->tail) {
wl_abort("ring_buffer_copy(): ring buffer corrupt, %zu < %zu\n",
b->head, b->tail);
}
buffer_size = ring_buffer_size(b);
capacity = ring_buffer_capacity(b);
if (buffer_size > capacity) {
wl_abort("ring_buffer_copy(): ring buffer corrupt: "
"%zu - %zu > %zu\n", b->head, b->tail, capacity);
}
if (count == 0)
return;
if (buffer_size < count) {
wl_abort("ring_buffer_copy(): attempt to copy %zu bytes "
"but buffer has %zu bytes\n",
count, buffer_size);
}
tail = ring_buffer_mask(b, b->tail);
if (tail + count <= ring_buffer_capacity(b)) {
size = capacity - tail;
if (count <= size) {
/* Enough data after the tail to fulfill the request */
memcpy(data, b->data + tail, count);
} else {
size = ring_buffer_capacity(b) - tail;
/* Must wrap buffer around */
memcpy(data, b->data + tail, size);
memcpy((char *) data + size, b->data, count - size);
}
}
static size_t
ring_buffer_size(struct wl_ring_buffer *b)
{
return b->head - b->tail;
}
static char *
ring_buffer_tail(const struct wl_ring_buffer *b)
{
@ -413,7 +523,7 @@ decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg)
{
struct cmsghdr *cmsg;
size_t size, i;
int overflow = 0;
bool overflow = false;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
@ -424,7 +534,7 @@ decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg)
size = cmsg->cmsg_len - CMSG_LEN(0);
if (ring_buffer_ensure_space(buffer, size) < 0 || overflow) {
overflow = 1;
overflow = true;
size /= sizeof(int32_t);
for (i = 0; i < size; i++)
close(((int*)CMSG_DATA(cmsg))[i]);
@ -456,6 +566,9 @@ wl_connection_flush(struct wl_connection *connection)
tail = connection->out.tail;
while (ring_buffer_size(&connection->out) > 0) {
/* Ring buffer is not empty, so this is safe. */
ring_buffer_get_iov(&connection->out, iov, &count);
build_cmsg(&connection->fds_out, cmsg, &clen);
if (clen >= CLEN) {
@ -530,6 +643,7 @@ wl_connection_read(struct wl_connection *connection)
if (ring_buffer_ensure_space(&connection->in, 1) < 0)
return -1;
/* Ring buffer is not full, so this is safe. */
ring_buffer_put_iov(&connection->in, iov, &count);
msg.msg_name = NULL;
@ -683,6 +797,7 @@ wl_message_get_since(const struct wl_message *message)
{
int since;
/* This is trusted input */
since = atoi(message->signature);
if (since == 0)
@ -950,14 +1065,14 @@ wl_connection_demarshal(struct wl_connection *connection,
case WL_ARG_STRING:
length = *p++;
if (length == 0 && !arg.nullable) {
wl_log("NULL string received on non-nullable "
"type, message %s(%s)\n", message->name,
message->signature);
errno = EINVAL;
goto err;
}
if (length == 0) {
if (!arg.nullable) {
wl_log("NULL string received on non-nullable "
"type, message %s(%s)\n", message->name,
message->signature);
errno = EINVAL;
goto err;
}
closure->args[i].s = NULL;
break;
}
@ -1060,7 +1175,10 @@ wl_connection_demarshal(struct wl_connection *connection,
goto err;
}
/* This ring buffer will always have a multiple of sizeof(int)
* bytes in it. */
ring_buffer_copy(&connection->fds_in, &fd, sizeof fd);
/* This can wrap but that is okay. */
connection->fds_in.tail += sizeof fd;
closure->args[i].h = fd;
break;
@ -1491,11 +1609,56 @@ wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection)
return result;
}
bool
wl_check_env_token(const char *env, const char *token)
{
const char *ptr = env;
size_t token_len;
if (env == NULL)
return false;
token_len = strlen(token);
// Scan the string for comma-separated tokens and look for a match.
while (true) {
const char *end;
size_t len;
// Skip over any leading separators.
while (*ptr == ',')
ptr++;
if (*ptr == '\x00')
return false;
end = strchr(ptr + 1, ',');
// If there isn't another separarator, then the rest of the string
// is one token.
if (end == NULL)
return (strcmp(ptr, token) == 0);
len = end - ptr;
if (len == token_len && memcmp(ptr, token, len) == 0) {
return true;
}
// Skip to the next token.
ptr += len;
}
return false;
}
void
wl_closure_print(struct wl_closure *closure, struct wl_object *target,
int send, int discarded, uint32_t (*n_parse)(union wl_argument *arg),
const char *queue_name, int color)
{
#if defined(HAVE_GETTID)
static int include_tid = -1;
#endif // defined(HAVE_GETTID)
int i;
struct argument_details arg;
const char *signature = closure->message->signature;
@ -1516,6 +1679,18 @@ wl_closure_print(struct wl_closure *closure, struct wl_object *target,
color ? WL_DEBUG_COLOR_GREEN : "",
time / 1000, time % 1000);
#if defined(HAVE_GETTID)
if (include_tid < 0) {
include_tid = wl_check_env_token(getenv("WAYLAND_DEBUG"), "thread_id");
}
if (include_tid) {
fprintf(f, "%sTID#%d ",
color ? WL_DEBUG_COLOR_CYAN : "",
(int) gettid());
}
#endif
if (queue_name) {
fprintf(f, "%s{%s} ",
color ? WL_DEBUG_COLOR_YELLOW : "",

View file

@ -268,9 +268,16 @@ int
wl_display_dispatch_queue_pending(struct wl_display *display,
struct wl_event_queue *queue);
int
wl_display_dispatch_queue_pending_single(struct wl_display *display,
struct wl_event_queue *queue);
int
wl_display_dispatch_pending(struct wl_display *display);
int
wl_display_dispatch_pending_single(struct wl_display *display);
int
wl_display_get_error(struct wl_display *display);

View file

@ -1236,7 +1236,7 @@ wl_display_connect_to_fd(int fd)
no_color = getenv("NO_COLOR");
force_color = getenv("FORCE_COLOR");
debug = getenv("WAYLAND_DEBUG");
if (debug && (strstr(debug, "client") || strstr(debug, "1"))) {
if (debug && (wl_check_env_token(debug, "client") || wl_check_env_token(debug, "1"))) {
debug_client = 1;
if (isatty(fileno(stderr)))
debug_color = 1;
@ -1882,6 +1882,34 @@ err:
return -1;
}
static int
dispatch_queue_single(struct wl_display *display, struct wl_event_queue *queue)
{
if (display->last_error)
goto err;
while (!wl_list_empty(&display->display_queue.event_list)) {
dispatch_event(display, &display->display_queue);
if (display->last_error)
goto err;
}
if (!wl_list_empty(&queue->event_list)) {
dispatch_event(display, queue);
if (display->last_error)
goto err;
return 1;
} else {
return 0;
}
err:
errno = display->last_error;
return -1;
}
/** Prepare to read events from the display's file descriptor to a queue
*
* \param display The display context object
@ -2212,6 +2240,34 @@ wl_display_dispatch_queue_pending(struct wl_display *display,
return ret;
}
/** Dispatch at most one pending event in an event queue
*
* \param display The display context object
* \param queue The event queue to dispatch
* \return The number of dispatched events (0 or 1) on success or -1 on failure
*
* Dispatch at most one pending event for objects assigned to the given
* event queue. On failure -1 is returned and errno set appropriately.
* If there are no events queued, this function returns immediately.
*
* \memberof wl_display
* \since 1.25.0
*/
WL_EXPORT int
wl_display_dispatch_queue_pending_single(struct wl_display *display,
struct wl_event_queue *queue)
{
int ret;
pthread_mutex_lock(&display->mutex);
ret = dispatch_queue_single(display, queue);
pthread_mutex_unlock(&display->mutex);
return ret;
}
/** Process incoming events
*
* \param display The display context object
@ -2272,6 +2328,25 @@ wl_display_dispatch_pending(struct wl_display *display)
&display->default_queue);
}
/** Dispatch at most one pending event in the default event queue.
*
* \param display The display context object
* \return The number of dispatched events (0 or 1) on success or -1 on failure
*
* Dispatch at most one pending event for objects assigned to the default
* event queue. On failure -1 is returned and errno set appropriately.
* If there are no events queued, this function returns immediately.
*
* \memberof wl_display
* \since 1.25.0
*/
WL_EXPORT int
wl_display_dispatch_pending_single(struct wl_display *display)
{
return wl_display_dispatch_queue_pending_single(display,
&display->default_queue);
}
/** Retrieve the last error that occurred on a display
*
* \param display The display context object

View file

@ -237,6 +237,9 @@ wl_closure_send(struct wl_closure *closure, struct wl_connection *connection);
int
wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection);
bool
wl_check_env_token(const char *env, const char *token);
void
wl_closure_print(struct wl_closure *closure,
struct wl_object *target, int send, int discarded,

View file

@ -740,8 +740,7 @@ wl_client_post_implementation_error(struct wl_client *client,
WL_EXPORT void
wl_resource_post_no_memory(struct wl_resource *resource)
{
wl_resource_post_error(resource->client->display_resource,
WL_DISPLAY_ERROR_NO_MEMORY, "no memory");
wl_client_post_no_memory(resource->client);
}
/** Detect if a wl_resource uses the deprecated public definition.
@ -1198,7 +1197,7 @@ wl_display_create(void)
no_color = getenv("NO_COLOR");
force_color = getenv("FORCE_COLOR");
debug = getenv("WAYLAND_DEBUG");
if (debug && (strstr(debug, "server") || strstr(debug, "1"))) {
if (debug && (wl_check_env_token(debug, "server") || wl_check_env_token(debug, "1"))) {
debug_server = 1;
if (isatty(fileno(stderr)))
debug_color = 1;

View file

@ -1695,6 +1695,75 @@ TEST(global_remove)
display_destroy(d);
}
static void
dispatch_single_read_events(struct wl_display *d)
{
if (wl_display_prepare_read(d) < 0) {
return;
}
int ret = 0;
do {
ret = wl_display_flush(d);
} while (ret < 0 && (errno == EINTR || errno == EAGAIN));
assert(ret >= 0);
struct pollfd pfd[1];
pfd[0].fd = wl_display_get_fd(d);
pfd[0].events = POLLIN;
do {
ret = poll(pfd, 1, -1);
} while (ret < 0 && errno == EINTR);
assert(ret > 0);
wl_display_read_events(d);
}
static void
dispatch_single_client(void)
{
struct client *c = client_connect();
assert(wl_display_dispatch_pending_single(c->wl_display) == 0);
struct wl_registry *registry = wl_display_get_registry(c->wl_display);
dispatch_single_read_events(c->wl_display);
// [1815110.061] {Default Queue} wl_registry#3.global(1, "test", 1)
assert(wl_display_dispatch_pending_single(c->wl_display) == 1);
dispatch_single_read_events(c->wl_display);
// [1815110.067] {Default Queue} wl_registry#3.global(2, "wl_seat", 1)
assert(wl_display_dispatch_pending_single(c->wl_display) == 1);
// No more events
assert(wl_display_dispatch_pending_single(c->wl_display) == 0);
wl_registry_destroy(registry);
client_disconnect(c);
}
TEST(dispatch_single)
{
struct display *d = display_create();
struct wl_global *global = wl_global_create(d->wl_display,
&wl_seat_interface,
1, d, bind_seat);
client_create_noarg(d, dispatch_single_client);
display_run(d);
wl_global_destroy(global);
display_destroy(d);
}
static void
terminate_display(void *arg)
{