diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 26ceae1f..eeed7899 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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" diff --git a/cursor/wayland-cursor.c b/cursor/wayland-cursor.c index 89ecc9a1..2e21db73 100644 --- a/cursor/wayland-cursor.c +++ b/cursor/wayland-cursor.c @@ -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; } diff --git a/meson.build b/meson.build index 37c14687..ce386a4c 100644 --- a/meson.build +++ b/meson.build @@ -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)) diff --git a/src/connection.c b/src/connection.c index 593f52f3..043ddfb8 100644 --- a/src/connection.c +++ b/src/connection.c @@ -26,6 +26,8 @@ #define _GNU_SOURCE +#include "../config.h" + #include #include #include @@ -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 : "", diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h index 970e6254..e0523e49 100644 --- a/src/wayland-client-core.h +++ b/src/wayland-client-core.h @@ -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); diff --git a/src/wayland-client.c b/src/wayland-client.c index c8633046..ed686b5c 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -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 diff --git a/src/wayland-private.h b/src/wayland-private.h index d7ba9dae..d0e4cfc6 100644 --- a/src/wayland-private.h +++ b/src/wayland-private.h @@ -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, diff --git a/src/wayland-server.c b/src/wayland-server.c index 482743b3..cdcdb642 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -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; diff --git a/tests/display-test.c b/tests/display-test.c index 89606c73..fe78b521 100644 --- a/tests/display-test.c +++ b/tests/display-test.c @@ -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) {