1/*
2 * Copyright © 2008 Kristian Høgsberg
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the
13 * next paragraph) shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * Authors:
26 * Kristian Høgsberg <krh@bitplanet.net>
27 * Benjamin Franzke <benjaminfranzke@googlemail.com>
28 *
29 */
30
31#define _GNU_SOURCE
32
33#include "config.h"
34
35#include <stdbool.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <stdint.h>
39#include <string.h>
40#include <sys/mman.h>
41#include <sys/stat.h>
42#include <unistd.h>
43#include <assert.h>
44#include <signal.h>
45#include <pthread.h>
46#include <errno.h>
47#include <fcntl.h>
48
49#include "wayland-os.h"
50#include "wayland-util.h"
51#include "wayland-private.h"
52#include "wayland-server.h"
53
54/* This once_t is used to synchronize installing the SIGBUS handler
55 * and creating the TLS key. This will be done in the first call
56 * wl_shm_buffer_begin_access which can happen from any thread */
57static pthread_once_t wl_shm_sigbus_once = PTHREAD_ONCE_INIT;
58static pthread_key_t wl_shm_sigbus_data_key;
59static struct sigaction wl_shm_old_sigbus_action;
60
61struct wl_shm_pool {
62 struct wl_resource *resource;
63 int internal_refcount;
64 int external_refcount;
65 char *data;
66 ssize_t size;
67 ssize_t new_size;
68 /* The following three fields are needed for mremap() emulation. */
69 int mmap_fd;
70 int mmap_flags;
71 int mmap_prot;
72 bool sigbus_is_impossible;
73};
74
75/** \class wl_shm_buffer
76 *
77 * \brief A SHM buffer
78 *
79 * wl_shm_buffer provides a helper for accessing the contents of a wl_buffer
80 * resource created via the wl_shm interface.
81 *
82 * A wl_shm_buffer becomes invalid as soon as its #wl_resource is destroyed.
83 */
84struct wl_shm_buffer {
85 struct wl_resource *resource;
86 int32_t width, height;
87 int32_t stride;
88 uint32_t format;
89 int offset;
90 struct wl_shm_pool *pool;
91};
92
93struct wl_shm_sigbus_data {
94 struct wl_shm_pool *current_pool;
95 int access_count;
96 int fallback_mapping_used;
97};
98
99static void *
100shm_pool_grow_mapping(struct wl_shm_pool *pool)
101{
102 void *data;
103
104#ifdef MREMAP_MAYMOVE
105 data = mremap(addr: pool->data, old_len: pool->size, new_len: pool->new_size, MREMAP_MAYMOVE);
106#else
107 data = wl_os_mremap_maymove(pool->mmap_fd, pool->data, &pool->size,
108 pool->new_size, pool->mmap_prot,
109 pool->mmap_flags);
110 if (pool->size != 0) {
111 wl_resource_post_error(pool->resource,
112 WL_SHM_ERROR_INVALID_FD,
113 "leaked old mapping");
114 }
115#endif
116 return data;
117}
118
119static void
120shm_pool_finish_resize(struct wl_shm_pool *pool)
121{
122 void *data;
123
124 if (pool->size == pool->new_size)
125 return;
126
127 data = shm_pool_grow_mapping(pool);
128 if (data == MAP_FAILED) {
129 wl_resource_post_error(resource: pool->resource,
130 code: WL_SHM_ERROR_INVALID_FD,
131 msg: "failed mremap");
132 return;
133 }
134
135 pool->data = data;
136 pool->size = pool->new_size;
137}
138
139static void
140shm_pool_unref(struct wl_shm_pool *pool, bool external)
141{
142 if (external) {
143 pool->external_refcount--;
144 assert(pool->external_refcount >= 0);
145 if (pool->external_refcount == 0)
146 shm_pool_finish_resize(pool);
147 } else {
148 pool->internal_refcount--;
149 assert(pool->internal_refcount >= 0);
150 }
151
152 if (pool->internal_refcount + pool->external_refcount > 0)
153 return;
154
155 munmap(addr: pool->data, len: pool->size);
156 close(fd: pool->mmap_fd);
157 free(ptr: pool);
158}
159
160static void
161destroy_buffer(struct wl_resource *resource)
162{
163 struct wl_shm_buffer *buffer = wl_resource_get_user_data(resource);
164
165 shm_pool_unref(pool: buffer->pool, false);
166 free(ptr: buffer);
167}
168
169static void
170shm_buffer_destroy(struct wl_client *client, struct wl_resource *resource)
171{
172 wl_resource_destroy(resource);
173}
174
175static const struct wl_buffer_interface shm_buffer_interface = {
176 shm_buffer_destroy
177};
178
179static bool
180format_is_supported(struct wl_client *client, uint32_t format)
181{
182 struct wl_display *display = wl_client_get_display(client);
183 struct wl_array *formats;
184 uint32_t *p;
185
186 switch (format) {
187 case WL_SHM_FORMAT_ARGB8888:
188 case WL_SHM_FORMAT_XRGB8888:
189 return true;
190 default:
191 formats = wl_display_get_additional_shm_formats(display);
192 wl_array_for_each(p, formats)
193 if (*p == format)
194 return true;
195 }
196
197 return false;
198}
199
200static void
201shm_pool_create_buffer(struct wl_client *client, struct wl_resource *resource,
202 uint32_t id, int32_t offset,
203 int32_t width, int32_t height,
204 int32_t stride, uint32_t format)
205{
206 struct wl_shm_pool *pool = wl_resource_get_user_data(resource);
207 struct wl_shm_buffer *buffer;
208
209 if (!format_is_supported(client, format)) {
210 wl_resource_post_error(resource,
211 code: WL_SHM_ERROR_INVALID_FORMAT,
212 msg: "invalid format 0x%x", format);
213 return;
214 }
215
216 if (offset < 0 || width <= 0 || height <= 0 || stride < width ||
217 INT32_MAX / stride < height ||
218 offset > pool->size - stride * height) {
219 wl_resource_post_error(resource,
220 code: WL_SHM_ERROR_INVALID_STRIDE,
221 msg: "invalid width, height or stride (%dx%d, %u)",
222 width, height, stride);
223 return;
224 }
225
226 buffer = malloc(size: sizeof *buffer);
227 if (buffer == NULL) {
228 wl_client_post_no_memory(client);
229 return;
230 }
231
232 buffer->width = width;
233 buffer->height = height;
234 buffer->format = format;
235 buffer->stride = stride;
236 buffer->offset = offset;
237 buffer->pool = pool;
238 pool->internal_refcount++;
239
240 buffer->resource =
241 wl_resource_create(client, interface: &wl_buffer_interface, version: 1, id);
242 if (buffer->resource == NULL) {
243 wl_client_post_no_memory(client);
244 shm_pool_unref(pool, false);
245 free(ptr: buffer);
246 return;
247 }
248
249 wl_resource_set_implementation(resource: buffer->resource,
250 implementation: &shm_buffer_interface,
251 data: buffer, destroy: destroy_buffer);
252}
253
254static void
255destroy_pool(struct wl_resource *resource)
256{
257 struct wl_shm_pool *pool = wl_resource_get_user_data(resource);
258
259 shm_pool_unref(pool, false);
260}
261
262static void
263shm_pool_destroy(struct wl_client *client, struct wl_resource *resource)
264{
265 wl_resource_destroy(resource);
266}
267
268static void
269shm_pool_resize(struct wl_client *client, struct wl_resource *resource,
270 int32_t size)
271{
272 struct wl_shm_pool *pool = wl_resource_get_user_data(resource);
273
274 if (size < pool->size) {
275 wl_resource_post_error(resource,
276 code: WL_SHM_ERROR_INVALID_FD,
277 msg: "shrinking pool invalid");
278 return;
279 }
280
281 pool->new_size = size;
282
283 /* If the compositor has taken references on this pool it
284 * may be caching pointers into it. In that case we
285 * defer the resize (which may move the entire mapping)
286 * until the compositor finishes dereferencing the pool.
287 */
288 if (pool->external_refcount == 0)
289 shm_pool_finish_resize(pool);
290}
291
292static const struct wl_shm_pool_interface shm_pool_interface = {
293 shm_pool_create_buffer,
294 shm_pool_destroy,
295 shm_pool_resize
296};
297
298static void
299shm_create_pool(struct wl_client *client, struct wl_resource *resource,
300 uint32_t id, int fd, int32_t size)
301{
302 struct wl_shm_pool *pool;
303 struct stat statbuf;
304 int seals;
305 int prot;
306 int flags;
307
308 if (size <= 0) {
309 wl_resource_post_error(resource,
310 code: WL_SHM_ERROR_INVALID_STRIDE,
311 msg: "invalid size (%d)", size);
312 goto err_close;
313 }
314
315 pool = malloc(size: sizeof *pool);
316 if (pool == NULL) {
317 wl_client_post_no_memory(client);
318 goto err_close;
319 }
320
321#ifdef HAVE_MEMFD_CREATE
322 seals = fcntl(fd: fd, F_GET_SEALS);
323 if (seals == -1)
324 seals = 0;
325
326 if ((seals & F_SEAL_SHRINK) && fstat(fd: fd, buf: &statbuf) >= 0)
327 pool->sigbus_is_impossible = statbuf.st_size >= size;
328 else
329 pool->sigbus_is_impossible = false;
330#else
331 pool->sigbus_is_impossible = false;
332#endif
333
334 pool->internal_refcount = 1;
335 pool->external_refcount = 0;
336 pool->size = size;
337 pool->new_size = size;
338 prot = PROT_READ | PROT_WRITE;
339 flags = MAP_SHARED;
340 pool->data = mmap(NULL, len: size, prot: prot, flags: flags, fd: fd, offset: 0);
341 if (pool->data == MAP_FAILED) {
342 wl_resource_post_error(resource, code: WL_SHM_ERROR_INVALID_FD,
343 msg: "failed mmap fd %d: %s", fd,
344 strerror(errno));
345 goto err_free;
346 }
347 /* We may need to keep the fd, prot and flags to emulate mremap(). */
348 pool->mmap_fd = fd;
349 pool->mmap_prot = prot;
350 pool->mmap_flags = flags;
351 pool->resource =
352 wl_resource_create(client, interface: &wl_shm_pool_interface, version: 1, id);
353 if (!pool->resource) {
354 wl_client_post_no_memory(client);
355 munmap(addr: pool->data, len: pool->size);
356 free(ptr: pool);
357 return;
358 }
359
360 wl_resource_set_implementation(resource: pool->resource,
361 implementation: &shm_pool_interface,
362 data: pool, destroy: destroy_pool);
363
364 return;
365
366err_free:
367 free(ptr: pool);
368err_close:
369 close(fd: fd);
370}
371
372static const struct wl_shm_interface shm_interface = {
373 shm_create_pool
374};
375
376static void
377bind_shm(struct wl_client *client,
378 void *data, uint32_t version, uint32_t id)
379{
380 struct wl_resource *resource;
381 struct wl_display *display = wl_client_get_display(client);
382 struct wl_array *additional_formats;
383 uint32_t *p;
384
385 resource = wl_resource_create(client, interface: &wl_shm_interface, version: 1, id);
386 if (!resource) {
387 wl_client_post_no_memory(client);
388 return;
389 }
390
391 wl_resource_set_implementation(resource, implementation: &shm_interface, data, NULL);
392
393 wl_shm_send_format(resource_: resource, format: WL_SHM_FORMAT_ARGB8888);
394 wl_shm_send_format(resource_: resource, format: WL_SHM_FORMAT_XRGB8888);
395
396 additional_formats = wl_display_get_additional_shm_formats(display);
397 wl_array_for_each(p, additional_formats)
398 wl_shm_send_format(resource_: resource, format: *p);
399}
400
401WL_EXPORT int
402wl_display_init_shm(struct wl_display *display)
403{
404 if (!wl_global_create(display, interface: &wl_shm_interface, version: 1, NULL, bind: bind_shm))
405 return -1;
406
407 return 0;
408}
409
410WL_EXPORT struct wl_shm_buffer *
411wl_shm_buffer_get(struct wl_resource *resource)
412{
413 if (resource == NULL)
414 return NULL;
415
416 if (wl_resource_instance_of(resource, interface: &wl_buffer_interface,
417 implementation: &shm_buffer_interface))
418 return wl_resource_get_user_data(resource);
419 else
420 return NULL;
421}
422
423WL_EXPORT int32_t
424wl_shm_buffer_get_stride(struct wl_shm_buffer *buffer)
425{
426 return buffer->stride;
427}
428
429
430/** Get a pointer to the memory for the SHM buffer
431 *
432 * \param buffer The buffer object
433 *
434 * Returns a pointer which can be used to read the data contained in
435 * the given SHM buffer.
436 *
437 * As this buffer is memory-mapped, reading from it may generate
438 * SIGBUS signals. This can happen if the client claims that the
439 * buffer is larger than it is or if something truncates the
440 * underlying file. To prevent this signal from causing the compositor
441 * to crash you should call wl_shm_buffer_begin_access and
442 * wl_shm_buffer_end_access around code that reads from the memory.
443 *
444 * \memberof wl_shm_buffer
445 */
446WL_EXPORT void *
447wl_shm_buffer_get_data(struct wl_shm_buffer *buffer)
448{
449 if (buffer->pool->external_refcount &&
450 (buffer->pool->size != buffer->pool->new_size))
451 wl_log(fmt: "Buffer address requested when its parent pool "
452 "has an external reference and a deferred resize "
453 "pending.\n");
454 return buffer->pool->data + buffer->offset;
455}
456
457WL_EXPORT uint32_t
458wl_shm_buffer_get_format(struct wl_shm_buffer *buffer)
459{
460 return buffer->format;
461}
462
463WL_EXPORT int32_t
464wl_shm_buffer_get_width(struct wl_shm_buffer *buffer)
465{
466 return buffer->width;
467}
468
469WL_EXPORT int32_t
470wl_shm_buffer_get_height(struct wl_shm_buffer *buffer)
471{
472 return buffer->height;
473}
474
475/** Get a reference to a shm_buffer's shm_pool
476 *
477 * \param buffer The buffer object
478 *
479 * Returns a pointer to a buffer's shm_pool and increases the
480 * shm_pool refcount.
481 *
482 * The compositor must remember to call wl_shm_pool_unref when
483 * it no longer needs the reference to ensure proper destruction
484 * of the pool.
485 *
486 * \memberof wl_shm_buffer
487 * \sa wl_shm_pool_unref
488 */
489WL_EXPORT struct wl_shm_pool *
490wl_shm_buffer_ref_pool(struct wl_shm_buffer *buffer)
491{
492 assert(buffer->pool->internal_refcount +
493 buffer->pool->external_refcount);
494
495 buffer->pool->external_refcount++;
496 return buffer->pool;
497}
498
499/** Unreference a shm_pool
500 *
501 * \param pool The pool object
502 *
503 * Drops a reference to a wl_shm_pool object.
504 *
505 * This is only necessary if the compositor has explicitly
506 * taken a reference with wl_shm_buffer_ref_pool(), otherwise
507 * the pool will be automatically destroyed when appropriate.
508 *
509 * \memberof wl_shm_pool
510 * \sa wl_shm_buffer_ref_pool
511 */
512WL_EXPORT void
513wl_shm_pool_unref(struct wl_shm_pool *pool)
514{
515 shm_pool_unref(pool, true);
516}
517
518static void
519reraise_sigbus(void)
520{
521 /* If SIGBUS is raised for some other reason than accessing
522 * the pool then we'll uninstall the signal handler so we can
523 * reraise it. This would presumably kill the process */
524 sigaction(SIGBUS, act: &wl_shm_old_sigbus_action, NULL);
525 raise(SIGBUS);
526}
527
528static void
529sigbus_handler(int signum, siginfo_t *info, void *context)
530{
531 struct wl_shm_sigbus_data *sigbus_data =
532 pthread_getspecific(key: wl_shm_sigbus_data_key);
533 struct wl_shm_pool *pool;
534
535 if (sigbus_data == NULL) {
536 reraise_sigbus();
537 return;
538 }
539
540 pool = sigbus_data->current_pool;
541
542 /* If the offending address is outside the mapped space for
543 * the pool then the error is a real problem so we'll reraise
544 * the signal */
545 if (pool == NULL ||
546 (char *) info->si_addr < pool->data ||
547 (char *) info->si_addr >= pool->data + pool->size) {
548 reraise_sigbus();
549 return;
550 }
551
552 sigbus_data->fallback_mapping_used = 1;
553
554 /* This should replace the previous mapping */
555 if (mmap(addr: pool->data, len: pool->size, PROT_READ | PROT_WRITE,
556 MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, fd: 0, offset: 0) == MAP_FAILED) {
557 reraise_sigbus();
558 return;
559 }
560}
561
562static void
563destroy_sigbus_data(void *data)
564{
565 struct wl_shm_sigbus_data *sigbus_data = data;
566
567 free(ptr: sigbus_data);
568}
569
570static void
571init_sigbus_data_key(void)
572{
573 struct sigaction new_action = {
574 .sa_sigaction = sigbus_handler,
575 .sa_flags = SA_SIGINFO | SA_NODEFER
576 };
577
578 sigemptyset(set: &new_action.sa_mask);
579
580 sigaction(SIGBUS, act: &new_action, oact: &wl_shm_old_sigbus_action);
581
582 pthread_key_create(key: &wl_shm_sigbus_data_key, destr_function: destroy_sigbus_data);
583}
584
585/** Mark that the given SHM buffer is about to be accessed
586 *
587 * \param buffer The SHM buffer
588 *
589 * An SHM buffer is a memory-mapped file given by the client.
590 * According to POSIX, reading from a memory-mapped region that
591 * extends off the end of the file will cause a SIGBUS signal to be
592 * generated. Normally this would cause the compositor to terminate.
593 * In order to make the compositor robust against clients that change
594 * the size of the underlying file or lie about its size, you should
595 * protect access to the buffer by calling this function before
596 * reading from the memory and call wl_shm_buffer_end_access
597 * afterwards. This will install a signal handler for SIGBUS which
598 * will prevent the compositor from crashing.
599 *
600 * After calling this function the signal handler will remain
601 * installed for the lifetime of the compositor process. Note that
602 * this function will not work properly if the compositor is also
603 * installing its own handler for SIGBUS.
604 *
605 * If a SIGBUS signal is received for an address within the range of
606 * the SHM pool of the given buffer then the client will be sent an
607 * error event when wl_shm_buffer_end_access is called. If the signal
608 * is for an address outside that range then the signal handler will
609 * reraise the signal which would will likely cause the compositor to
610 * terminate.
611 *
612 * It is safe to nest calls to these functions as long as the nested
613 * calls are all accessing the same buffer. The number of calls to
614 * wl_shm_buffer_end_access must match the number of calls to
615 * wl_shm_buffer_begin_access. These functions are thread-safe and it
616 * is allowed to simultaneously access different buffers or the same
617 * buffer from multiple threads.
618 *
619 * \memberof wl_shm_buffer
620 */
621WL_EXPORT void
622wl_shm_buffer_begin_access(struct wl_shm_buffer *buffer)
623{
624 struct wl_shm_pool *pool = buffer->pool;
625 struct wl_shm_sigbus_data *sigbus_data;
626
627 if (pool->sigbus_is_impossible)
628 return;
629
630 pthread_once(once_control: &wl_shm_sigbus_once, init_routine: init_sigbus_data_key);
631
632 sigbus_data = pthread_getspecific(key: wl_shm_sigbus_data_key);
633 if (sigbus_data == NULL) {
634 sigbus_data = zalloc(s: sizeof *sigbus_data);
635 if (sigbus_data == NULL)
636 return;
637
638 pthread_setspecific(key: wl_shm_sigbus_data_key, pointer: sigbus_data);
639 }
640
641 assert(sigbus_data->current_pool == NULL ||
642 sigbus_data->current_pool == pool);
643
644 sigbus_data->current_pool = pool;
645 sigbus_data->access_count++;
646}
647
648/** Ends the access to a buffer started by wl_shm_buffer_begin_access
649 *
650 * \param buffer The SHM buffer
651 *
652 * This should be called after wl_shm_buffer_begin_access once the
653 * buffer is no longer being accessed. If a SIGBUS signal was
654 * generated in-between these two calls then the resource for the
655 * given buffer will be sent an error.
656 *
657 * \memberof wl_shm_buffer
658 */
659WL_EXPORT void
660wl_shm_buffer_end_access(struct wl_shm_buffer *buffer)
661{
662 struct wl_shm_pool *pool = buffer->pool;
663 struct wl_shm_sigbus_data *sigbus_data;
664
665 if (pool->sigbus_is_impossible)
666 return;
667
668 sigbus_data = pthread_getspecific(key: wl_shm_sigbus_data_key);
669 assert(sigbus_data && sigbus_data->access_count >= 1);
670
671 if (--sigbus_data->access_count == 0) {
672 if (sigbus_data->fallback_mapping_used) {
673 wl_resource_post_error(resource: buffer->resource,
674 code: WL_SHM_ERROR_INVALID_FD,
675 msg: "error accessing SHM buffer");
676 sigbus_data->fallback_mapping_used = 0;
677 }
678
679 sigbus_data->current_pool = NULL;
680 }
681}
682
683/** \cond */ /* Deprecated functions below. */
684
685WL_EXPORT struct wl_shm_buffer *
686wl_shm_buffer_create(struct wl_client *client,
687 uint32_t id, int32_t width, int32_t height,
688 int32_t stride, uint32_t format)
689{
690 return NULL;
691}
692
693/** \endcond */
694
695/* Functions at the end of this file are deprecated. Instead of adding new
696 * code here, add it before the comment above that states:
697 * Deprecated functions below.
698 */
699

source code of gtk/subprojects/wayland/src/wayland-shm.c