diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/connection.c | 341 | ||||
| -rw-r--r-- | src/wayland-client-core.h | 4 | ||||
| -rw-r--r-- | src/wayland-client.c | 28 | ||||
| -rw-r--r-- | src/wayland-private.h | 8 | ||||
| -rw-r--r-- | src/wayland-server-core.h | 7 | ||||
| -rw-r--r-- | src/wayland-server.c | 65 |
6 files changed, 379 insertions, 74 deletions
diff --git a/src/connection.c b/src/connection.c index a58b7a7..8870fd2 100644 --- a/src/connection.c +++ b/src/connection.c @@ -26,6 +26,7 @@ #define _GNU_SOURCE +#include <assert.h> #include <math.h> #include <stdlib.h> #include <stdint.h> @@ -55,12 +56,12 @@ div_roundup(uint32_t n, size_t a) } struct wl_ring_buffer { - char data[4096]; - uint32_t head, tail; + char *data; + size_t head, tail; + uint32_t size_bits; + uint32_t max_size_bits; /* 0 for unlimited */ }; -#define MASK(i) ((i) & 4095) - #define MAX_FDS_OUT 28 #define CLEN (CMSG_LEN(MAX_FDS_OUT * sizeof(int32_t))) @@ -71,26 +72,38 @@ struct wl_connection { int want_flush; }; +static inline size_t +size_pot(uint32_t size_bits) +{ + assert(size_bits < 8 * sizeof(size_t)); + + return ((size_t)1) << size_bits; +} + +static size_t +ring_buffer_capacity(const struct wl_ring_buffer *b) { + return size_pot(b->size_bits); +} + +static size_t +ring_buffer_mask(const struct wl_ring_buffer *b, size_t i) { + size_t m = ring_buffer_capacity(b) - 1; + return i & m; +} + static int ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count) { - uint32_t head, size; - - if (count > sizeof(b->data)) { - wl_log("Data too big for buffer (%d > %d).\n", - count, sizeof(b->data)); - errno = E2BIG; - return -1; - } + size_t head, size; if (count == 0) return 0; - head = MASK(b->head); - if (head + count <= sizeof b->data) { + head = ring_buffer_mask(b, b->head); + if (head + count <= ring_buffer_capacity(b)) { memcpy(b->data + head, data, count); } else { - size = sizeof b->data - head; + size = ring_buffer_capacity(b) - head; memcpy(b->data + head, data, size); memcpy(b->data, (const char *) data + size, count - size); } @@ -103,21 +116,21 @@ ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count) static void ring_buffer_put_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count) { - uint32_t head, tail; + size_t head, tail; - head = MASK(b->head); - tail = MASK(b->tail); + head = ring_buffer_mask(b, b->head); + tail = ring_buffer_mask(b, b->tail); if (head < tail) { iov[0].iov_base = b->data + head; iov[0].iov_len = tail - head; *count = 1; } else if (tail == 0) { iov[0].iov_base = b->data + head; - iov[0].iov_len = sizeof b->data - head; + iov[0].iov_len = ring_buffer_capacity(b) - head; *count = 1; } else { iov[0].iov_base = b->data + head; - iov[0].iov_len = sizeof b->data - head; + iov[0].iov_len = ring_buffer_capacity(b) - head; iov[1].iov_base = b->data; iov[1].iov_len = tail; *count = 2; @@ -127,21 +140,21 @@ ring_buffer_put_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count) static void ring_buffer_get_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count) { - uint32_t head, tail; + size_t head, tail; - head = MASK(b->head); - tail = MASK(b->tail); + head = ring_buffer_mask(b, b->head); + tail = ring_buffer_mask(b, b->tail); if (tail < head) { iov[0].iov_base = b->data + tail; iov[0].iov_len = head - tail; *count = 1; } else if (head == 0) { iov[0].iov_base = b->data + tail; - iov[0].iov_len = sizeof b->data - tail; + iov[0].iov_len = ring_buffer_capacity(b) - tail; *count = 1; } else { iov[0].iov_base = b->data + tail; - iov[0].iov_len = sizeof b->data - tail; + iov[0].iov_len = ring_buffer_capacity(b) - tail; iov[1].iov_base = b->data; iov[1].iov_len = head; *count = 2; @@ -151,29 +164,158 @@ ring_buffer_get_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count) static void ring_buffer_copy(struct wl_ring_buffer *b, void *data, size_t count) { - uint32_t tail, size; + size_t tail, size; if (count == 0) return; - tail = MASK(b->tail); - if (tail + count <= sizeof b->data) { + tail = ring_buffer_mask(b, b->tail); + if (tail + count <= ring_buffer_capacity(b)) { memcpy(data, b->data + tail, count); } else { - size = sizeof b->data - tail; + size = ring_buffer_capacity(b) - tail; memcpy(data, b->data + tail, size); memcpy((char *) data + size, b->data, count - size); } } -static uint32_t +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) +{ + return b->data + ring_buffer_mask(b, b->tail); +} + +static uint32_t +get_max_size_bits_for_size(size_t buffer_size) +{ + uint32_t max_size_bits = WL_BUFFER_DEFAULT_SIZE_POT; + + /* buffer_size == 0 means unbound buffer size */ + if (buffer_size == 0) + return 0; + + while (max_size_bits < 8 * sizeof(size_t) && size_pot(max_size_bits) < buffer_size) + max_size_bits++; + + return max_size_bits; +} + +static int +ring_buffer_allocate(struct wl_ring_buffer *b, size_t size_bits) +{ + char *new_data; + + new_data = calloc(size_pot(size_bits), 1); + if (!new_data) + return -1; + + ring_buffer_copy(b, new_data, ring_buffer_size(b)); + free(b->data); + b->data = new_data; + b->size_bits = size_bits; + b->head = ring_buffer_size(b); + b->tail = 0; + + return 0; +} + +static size_t +ring_buffer_get_bits_for_size(struct wl_ring_buffer *b, size_t net_size) +{ + size_t max_size_bits = get_max_size_bits_for_size(net_size); + + if (max_size_bits < WL_BUFFER_DEFAULT_SIZE_POT) + max_size_bits = WL_BUFFER_DEFAULT_SIZE_POT; + + if (b->max_size_bits > 0 && max_size_bits > b->max_size_bits) + max_size_bits = b->max_size_bits; + + return max_size_bits; +} + +static bool +ring_buffer_is_max_size_reached(struct wl_ring_buffer *b) +{ + size_t net_size = ring_buffer_size(b) + 1; + size_t size_bits = ring_buffer_get_bits_for_size(b, net_size); + + return net_size >= size_pot(size_bits); +} + +static int +ring_buffer_ensure_space(struct wl_ring_buffer *b, size_t count) +{ + size_t net_size = ring_buffer_size(b) + count; + size_t size_bits = ring_buffer_get_bits_for_size(b, net_size); + + /* The 'size_bits' value represents the required size (in POT) to store + * 'net_size', which depending whether the buffers are bounded or not + * might not be sufficient (i.e. we might have reached the maximum size + * allowed). + */ + if (net_size > size_pot(size_bits)) { + wl_log("Data too big for buffer (%d + %zd > %zd).\n", + ring_buffer_size(b), count, size_pot(size_bits)); + errno = E2BIG; + return -1; + } + + /* The following test here is a short-cut to avoid reallocating a buffer + * of the same size. + */ + if (size_bits == b->size_bits) + return 0; + + /* Otherwise, we (re)allocate the buffer to match the required size */ + return ring_buffer_allocate(b, size_bits); +} + +static void +ring_buffer_close_fds(struct wl_ring_buffer *buffer, int32_t count) +{ + int32_t i, *p; + size_t size, tail; + + size = ring_buffer_capacity(buffer); + tail = ring_buffer_mask(buffer, buffer->tail); + p = (int32_t *) (buffer->data + tail); + + for (i = 0; i < count; i++) { + if (p >= (int32_t *) (buffer->data + size)) + p = (int32_t *) buffer->data; + close(*p++); + } +} + +void +wl_connection_set_max_buffer_size(struct wl_connection *connection, + size_t max_buffer_size) +{ + uint32_t max_size_bits; + + max_size_bits = get_max_size_bits_for_size(max_buffer_size); + + connection->fds_in.max_size_bits = max_size_bits; + ring_buffer_ensure_space(&connection->fds_in, 0); + + connection->fds_out.max_size_bits = max_size_bits; + ring_buffer_ensure_space(&connection->fds_out, 0); + + connection->in.max_size_bits = max_size_bits; + ring_buffer_ensure_space(&connection->in, 0); + + connection->out.max_size_bits = max_size_bits; + ring_buffer_ensure_space(&connection->out, 0); +} + struct wl_connection * -wl_connection_create(int fd) +wl_connection_create(int fd, size_t max_buffer_size) { struct wl_connection *connection; @@ -181,6 +323,8 @@ wl_connection_create(int fd) if (connection == NULL) return NULL; + wl_connection_set_max_buffer_size(connection, max_buffer_size); + connection->fd = fd; return connection; @@ -189,20 +333,20 @@ wl_connection_create(int fd) static void close_fds(struct wl_ring_buffer *buffer, int max) { - int32_t fds[sizeof(buffer->data) / sizeof(int32_t)], i, count; size_t size; + int32_t count; size = ring_buffer_size(buffer); if (size == 0) return; - ring_buffer_copy(buffer, fds, size); - count = size / sizeof fds[0]; + count = size / sizeof(int32_t); if (max > 0 && max < count) count = max; - size = count * sizeof fds[0]; - for (i = 0; i < count; i++) - close(fds[i]); + + ring_buffer_close_fds(buffer, count); + + size = count * sizeof(int32_t); buffer->tail += size; } @@ -218,7 +362,13 @@ wl_connection_destroy(struct wl_connection *connection) int fd = connection->fd; close_fds(&connection->fds_out, -1); + free(connection->fds_out.data); + free(connection->out.data); + close_fds(&connection->fds_in, -1); + free(connection->fds_in.data); + free(connection->in.data); + free(connection); return fd; @@ -262,7 +412,7 @@ static int decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg) { struct cmsghdr *cmsg; - size_t size, max, i; + size_t size, i; int overflow = 0; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; @@ -272,8 +422,8 @@ decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg) continue; size = cmsg->cmsg_len - CMSG_LEN(0); - max = sizeof(buffer->data) - ring_buffer_size(buffer); - if (size > max || overflow) { + + if (ring_buffer_ensure_space(buffer, size) < 0 || overflow) { overflow = 1; size /= sizeof(int32_t); for (i = 0; i < size; i++) @@ -299,17 +449,40 @@ wl_connection_flush(struct wl_connection *connection) char cmsg[CLEN]; int len = 0, count; size_t clen; - uint32_t tail; + size_t tail; if (!connection->want_flush) return 0; tail = connection->out.tail; - while (connection->out.head - connection->out.tail > 0) { - ring_buffer_get_iov(&connection->out, iov, &count); - + while (ring_buffer_size(&connection->out) > 0) { build_cmsg(&connection->fds_out, cmsg, &clen); + if (clen >= CLEN) { + /* UNIX domain sockets allows to send file descriptors + * using ancillary data. + * + * As per the UNIX domain sockets man page (man 7 unix), + * "at least one byte of real data should be sent when + * sending ancillary data". + * + * This is why we send only a single byte here, to ensure + * all file descriptors are sent before the bytes are + * cleared out. + * + * Otherwise This can fail to clear the file descriptors + * first if individual messages are allowed to have 224 + * (8 bytes * MAX_FDS_OUT = 224) file descriptors . + */ + iov[0].iov_base = ring_buffer_tail(&connection->out); + iov[0].iov_len = 1; + count = 1; + } else { + ring_buffer_get_iov(&connection->out, iov, &count); + } + + msg.msg_name = NULL; + msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = count; msg.msg_control = (clen > 0) ? cmsg : NULL; @@ -347,35 +520,48 @@ wl_connection_read(struct wl_connection *connection) char cmsg[CLEN]; int len, count, ret; - if (ring_buffer_size(&connection->in) >= sizeof(connection->in.data)) { - errno = EOVERFLOW; - return -1; - } + while (1) { + int data_size = ring_buffer_size(&connection->in); - ring_buffer_put_iov(&connection->in, iov, &count); + /* Stop once we've read the max buffer size. */ + if (ring_buffer_is_max_size_reached(&connection->in)) + return data_size; - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = iov; - msg.msg_iovlen = count; - msg.msg_control = cmsg; - msg.msg_controllen = sizeof cmsg; - msg.msg_flags = 0; + if (ring_buffer_ensure_space(&connection->in, 1) < 0) + return -1; - do { - len = wl_os_recvmsg_cloexec(connection->fd, &msg, MSG_DONTWAIT); - } while (len < 0 && errno == EINTR); + ring_buffer_put_iov(&connection->in, iov, &count); - if (len <= 0) - return len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = count; + msg.msg_control = cmsg; + msg.msg_controllen = sizeof cmsg; + msg.msg_flags = 0; - ret = decode_cmsg(&connection->fds_in, &msg); - if (ret) - return -1; + do { + len = wl_os_recvmsg_cloexec(connection->fd, &msg, MSG_DONTWAIT); + } while (len < 0 && errno == EINTR); + + if (len == 0) { + /* EOF, return previously read data first */ + return data_size; + } + if (len < 0) { + if (errno == EAGAIN && data_size > 0) { + /* nothing new read, return previously read data */ + return data_size; + } + return len; + } - connection->in.head += len; + ret = decode_cmsg(&connection->fds_in, &msg); + if (ret) + return -1; - return wl_connection_pending_input(connection); + connection->in.head += len; + } } int @@ -394,13 +580,23 @@ int wl_connection_queue(struct wl_connection *connection, const void *data, size_t count) { - if (connection->out.head - connection->out.tail + - count > ARRAY_LENGTH(connection->out.data)) { + /* We want to try to flush when the buffer reaches the default maximum + * size even if the buffer has been previously expanded. + * + * Otherwise the larger buffer will cause us to flush less frequently, + * which could increase lag. + * + * We'd like to flush often and get the buffer size back down if possible. + */ + if (ring_buffer_size(&connection->out) + count > WL_BUFFER_DEFAULT_MAX_SIZE) { connection->want_flush = 1; - if (wl_connection_flush(connection) < 0) + if (wl_connection_flush(connection) < 0 && errno != EAGAIN) return -1; } + if (ring_buffer_ensure_space(&connection->out, count) < 0) + return -1; + return ring_buffer_put(&connection->out, data, count); } @@ -426,12 +622,15 @@ wl_connection_get_fd(struct wl_connection *connection) static int wl_connection_put_fd(struct wl_connection *connection, int32_t fd) { - if (ring_buffer_size(&connection->fds_out) == MAX_FDS_OUT * sizeof fd) { + if (ring_buffer_size(&connection->fds_out) >= MAX_FDS_OUT * sizeof fd) { connection->want_flush = 1; - if (wl_connection_flush(connection) < 0) + if (wl_connection_flush(connection) < 0 && errno != EAGAIN) return -1; } + if (ring_buffer_ensure_space(&connection->fds_out, sizeof fd) < 0) + return -1; + return ring_buffer_put(&connection->fds_out, &fd, sizeof fd); } diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h index 20b2a3b..a9d620e 100644 --- a/src/wayland-client-core.h +++ b/src/wayland-client-core.h @@ -298,6 +298,10 @@ wl_display_read_events(struct wl_display *display); void wl_log_set_handler_client(wl_log_func_t handler); +void +wl_display_set_max_buffer_size(struct wl_display *display, + size_t max_buffer_size); + #ifdef __cplusplus } #endif diff --git a/src/wayland-client.c b/src/wayland-client.c index 665fe55..75fad4f 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -1269,7 +1269,7 @@ wl_display_connect_to_fd(int fd) */ display->proxy.version = 0; - display->connection = wl_connection_create(display->fd); + display->connection = wl_connection_create(display->fd, 0); if (display->connection == NULL) goto err_connection; @@ -2238,6 +2238,32 @@ wl_display_flush(struct wl_display *display) return ret; } +/** Adjust the maximum size of the client connection buffers + * + * \param display The display context object + * \param max_buffer_size The maximum size of the connection buffers + * + * Client buffers are unbounded by default. This function sets a limit to the + * size of the connection buffers. + * + * A value of 0 for \a max_buffer_size requests the buffers to be unbounded. + * + * The actual size of the connection buffers is a power of two, the requested + * \a max_buffer_size is therefore rounded up to the nearest power of two value. + * + * Lowering the maximum size may not take effect immediately if the current + * content of the buffer does not fit within the new size limit. + * + * \memberof wl_display + * \since 1.22.90 + */ +WL_EXPORT void +wl_display_set_max_buffer_size(struct wl_display *display, + size_t max_buffer_size) +{ + wl_connection_set_max_buffer_size(display->connection, max_buffer_size); +} + /** Set the user data associated with a proxy * * \param proxy The proxy object diff --git a/src/wayland-private.h b/src/wayland-private.h index a370763..fe9120a 100644 --- a/src/wayland-private.h +++ b/src/wayland-private.h @@ -47,6 +47,8 @@ #define WL_SERVER_ID_START 0xff000000 #define WL_MAP_MAX_OBJECTS 0x00f00000 #define WL_CLOSURE_MAX_ARGS 20 +#define WL_BUFFER_DEFAULT_SIZE_POT 12 +#define WL_BUFFER_DEFAULT_MAX_SIZE (1 << WL_BUFFER_DEFAULT_SIZE_POT) /** * Argument types used in signatures. @@ -120,7 +122,7 @@ void wl_map_for_each(struct wl_map *map, wl_iterator_func_t func, void *data); struct wl_connection * -wl_connection_create(int fd); +wl_connection_create(int fd, size_t max_buffer_size); int wl_connection_destroy(struct wl_connection *connection); @@ -252,4 +254,8 @@ zalloc(size_t s) void wl_connection_close_fds_in(struct wl_connection *connection, int max); +void +wl_connection_set_max_buffer_size(struct wl_connection *connection, + size_t max_buffer_size); + #endif diff --git a/src/wayland-server-core.h b/src/wayland-server-core.h index 00a5443..63d0f02 100644 --- a/src/wayland-server-core.h +++ b/src/wayland-server-core.h @@ -217,6 +217,10 @@ wl_display_flush_clients(struct wl_display *display); void wl_display_destroy_clients(struct wl_display *display); +void +wl_display_set_default_max_buffer_size(struct wl_display *display, + size_t max_buffer_size); + struct wl_client; typedef void (*wl_global_bind_func_t)(struct wl_client *client, void *data, @@ -375,6 +379,9 @@ wl_client_set_user_data(struct wl_client *client, void * wl_client_get_user_data(struct wl_client *client); +void +wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size); + /** \class wl_listener * * \brief A single listener for Wayland signals diff --git a/src/wayland-server.c b/src/wayland-server.c index 9726807..2df2e6a 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -112,6 +112,8 @@ struct wl_display { int terminate_efd; struct wl_event_source *term_source; + + size_t max_buffer_size; }; struct wl_global { @@ -542,7 +544,8 @@ wl_client_create(struct wl_display *display, int fd) &client->pid) != 0) goto err_source; - client->connection = wl_connection_create(fd); + client->connection = wl_connection_create(fd, display->max_buffer_size); + if (client->connection == NULL) goto err_source; @@ -1172,6 +1175,7 @@ wl_display_create(void) display->global_filter = NULL; display->global_filter_data = NULL; + display->max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE; wl_array_init(&display->additional_shm_formats); @@ -1580,6 +1584,37 @@ wl_display_destroy_clients(struct wl_display *display) } } +/** Sets the default maximum size for connection buffers of new clients + * + * \param display The display object + * \param max_buffer_size The default maximum size of the connection buffers + * + * This function sets the default size of the internal connection buffers for + * new clients. It doesn't change the buffer size for existing wl_client. + * + * The connection buffer size of an existing wl_client can be adjusted using + * wl_client_set_max_buffer_size(). + * + * The actual size of the connection buffers is a power of two, the requested + * \a max_buffer_size is therefore rounded up to the nearest power of two value. + * + * The minimum buffer size is 4096. + * + * \sa wl_client_set_max_buffer_size + * + * \memberof wl_display + * \since 1.22.90 + */ +WL_EXPORT void +wl_display_set_default_max_buffer_size(struct wl_display *display, + size_t max_buffer_size) +{ + if (max_buffer_size < WL_BUFFER_DEFAULT_MAX_SIZE) + max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE; + + display->max_buffer_size = max_buffer_size; +} + static int socket_data(int fd, uint32_t mask, void *data) { @@ -2275,6 +2310,34 @@ wl_signal_emit_mutable(struct wl_signal *signal, void *data) wl_list_remove(&end.link); } +/** Adjust the maximum size of the client connection buffers + * + * \param client The client object + * \param max_buffer_size The maximum size of the connection buffers + * + * The actual size of the connection buffers is a power of two, the requested + * \a max_buffer_size is therefore rounded up to the nearest power of two value. + * + * Lowering the maximum size may not take effect immediately if the current content + * of the buffer does not fit within the new size limit. + * + * The minimum buffer size is 4096. The default buffers size can be set using + * wl_display_set_default_max_buffer_size(). + * + * \sa wl_display_set_default_max_buffer_size() + * + * \memberof wl_client + * \since 1.22.90 + */ +WL_EXPORT void +wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size) +{ + if (max_buffer_size < WL_BUFFER_DEFAULT_MAX_SIZE) + max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE; + + wl_connection_set_max_buffer_size(client->connection, max_buffer_size); +} + /** \cond INTERNAL */ /** Initialize a wl_priv_signal object |
