1/* swc: libswc/shm.c
2 *
3 * Copyright (c) 2013-2020 Michael Forney
4 *
5 * Based in part upon wayland-shm.c from wayland, which is:
6 *
7 * Copyright © 2008 Kristian Høgsberg
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 * SOFTWARE.
26 */
27
28#include "shm.h"
29#include "internal.h"
30#include "util.h"
31#include "wayland_buffer.h"
32
33#include <errno.h>
34#include <stdlib.h>
35#include <string.h>
36#include <sys/mman.h>
37#include <sys/stat.h>
38#include <unistd.h>
39#include <wayland-server.h>
40#include <wld/pixman.h>
41#include <wld/wld.h>
42
43struct pool {
44 struct wl_resource *resource;
45 struct swc_shm *shm;
46 void *data;
47 uint32_t size;
48 int fd;
49 bool writable;
50 unsigned references;
51};
52
53struct pool_reference {
54 struct wld_destructor destructor;
55 struct pool *pool;
56};
57
58struct shm_buffer_record {
59 struct wl_list link;
60 struct wl_listener destroy_listener;
61 struct wl_resource *resource;
62 struct pool *pool;
63 uint32_t offset;
64 int32_t width;
65 int32_t height;
66 int32_t stride;
67 uint32_t format;
68};
69
70static struct wl_list shm_buffer_records;
71static bool shm_buffer_records_initialized;
72
73static void
74ensure_shm_buffer_records(void)
75{
76 if (!shm_buffer_records_initialized) {
77 wl_list_init(&shm_buffer_records);
78 shm_buffer_records_initialized = true;
79 }
80}
81
82static void
83handle_shm_buffer_resource_destroy(struct wl_listener *listener, void *data)
84{
85 struct shm_buffer_record *record =
86 wl_container_of(listener, record, destroy_listener);
87 (void)data;
88
89 wl_list_remove(&record->link);
90 free(record);
91}
92
93static void *
94swc_mremap(struct pool *pool, void *oldp, size_t oldsize, size_t newsize)
95{
96#ifdef __NetBSD__
97 return mremap(oldp, oldsize, NULL, newsize, 0);
98#elif defined(__linux__)
99 return mremap(oldp, oldsize, newsize, MREMAP_MAYMOVE);
100#else
101 void *newp;
102
103 newp = mmap(NULL, newsize, PROT_READ, MAP_SHARED, pool->fd, 0);
104 if (newp == MAP_FAILED) {
105 return MAP_FAILED;
106 }
107
108 (void)munmap(oldp, oldsize);
109 return newp;
110#endif
111}
112
113static void
114unref_pool(struct pool *pool)
115{
116 if (--pool->references > 0) {
117 return;
118 }
119
120 munmap(pool->data, pool->size);
121 close(pool->fd);
122 free(pool);
123}
124
125static void
126destroy_pool_resource(struct wl_resource *resource)
127{
128 struct pool *pool = wl_resource_get_user_data(resource);
129 unref_pool(pool);
130}
131
132static void
133handle_buffer_destroy(struct wld_destructor *destructor)
134{
135 struct pool_reference *reference =
136 wl_container_of(destructor, reference, destructor);
137 unref_pool(reference->pool);
138}
139
140static inline uint32_t
141format_shm_to_wld(uint32_t format)
142{
143 switch (format) {
144 case WL_SHM_FORMAT_ARGB8888:
145 return WLD_FORMAT_ARGB8888;
146 case WL_SHM_FORMAT_XRGB8888:
147 return WLD_FORMAT_XRGB8888;
148 default:
149 return format;
150 }
151}
152
153static void
154create_buffer(struct wl_client *client, struct wl_resource *resource,
155 uint32_t id, int32_t offset, int32_t width, int32_t height,
156 int32_t stride, uint32_t format)
157{
158 struct pool *pool = wl_resource_get_user_data(resource);
159 struct pool_reference *reference;
160 struct shm_buffer_record *record = NULL;
161 struct wld_buffer *buffer;
162 struct wl_resource *buffer_resource;
163 union wld_object object;
164
165 if (offset > pool->size || offset < 0) {
166 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE,
167 "offset is too big or negative");
168 return;
169 }
170
171 object.ptr = (void *)((uintptr_t)pool->data + offset);
172 buffer =
173 wld_import_buffer(pool->shm->context, WLD_OBJECT_DATA, object, width,
174 height, format_shm_to_wld(format), stride);
175
176 if (!buffer) {
177 goto error0;
178 }
179
180 buffer_resource = wayland_buffer_create_resource(
181 client, wl_resource_get_version(resource), id, buffer);
182
183 if (!buffer_resource) {
184 goto error1;
185 }
186
187 ensure_shm_buffer_records();
188 record = malloc(sizeof(*record));
189 if (!record) {
190 goto error2;
191 }
192 record->resource = buffer_resource;
193 record->pool = pool;
194 record->offset = (uint32_t)offset;
195 record->width = width;
196 record->height = height;
197 record->stride = stride;
198 record->format = format;
199 record->destroy_listener.notify = &handle_shm_buffer_resource_destroy;
200 wl_resource_add_destroy_listener(buffer_resource, &record->destroy_listener);
201 wl_list_insert(&shm_buffer_records, &record->link);
202
203 if (!(reference = malloc(sizeof(*reference)))) {
204 goto error3;
205 }
206
207 reference->pool = pool;
208 reference->destructor.destroy = &handle_buffer_destroy;
209 wld_buffer_add_destructor(buffer, &reference->destructor);
210 ++pool->references;
211
212 return;
213
214error3:
215 if (record) {
216 wl_list_remove(&record->destroy_listener.link);
217 wl_list_remove(&record->link);
218 free(record);
219 }
220error2:
221 wl_resource_destroy(buffer_resource);
222error1:
223 wld_buffer_unreference(buffer);
224error0:
225 wl_resource_post_no_memory(resource);
226}
227
228static void
229resize(struct wl_client *client, struct wl_resource *resource, int32_t size)
230{
231 struct pool *pool = wl_resource_get_user_data(resource);
232 void *data;
233 struct stat st;
234
235 if (fstat(pool->fd, &st) != 0) {
236 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
237 "fstat failed: %s", strerror(errno));
238 return;
239 }
240 if (st.st_size < size) {
241 if (ftruncate(pool->fd, size) != 0) {
242 int saved = errno;
243 /* some clients seal memfd if size is already fine,
244 * alloc will fail */
245 if ((saved == EPERM || saved == EACCES) &&
246 fstat(pool->fd, &st) == 0 && st.st_size >= size) {
247 goto remap;
248 }
249 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
250 "ftruncate failed: %s", strerror(saved));
251 return;
252 }
253 }
254
255remap:
256 data = swc_mremap(pool, pool->data, pool->size, size);
257 if (data == MAP_FAILED) {
258 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
259 "mremap failed: %s", strerror(errno));
260 return;
261 }
262 pool->data = data;
263 pool->size = size;
264}
265
266static const struct wl_shm_pool_interface shm_pool_impl = {
267 .create_buffer = create_buffer,
268 .destroy = destroy_resource,
269 .resize = resize,
270};
271
272static void
273create_pool(struct wl_client *client, struct wl_resource *resource, uint32_t id,
274 int32_t fd, int32_t size)
275{
276 struct swc_shm *shm = wl_resource_get_user_data(resource);
277 struct pool *pool;
278
279 pool = malloc(sizeof(*pool));
280 if (!pool) {
281 wl_resource_post_no_memory(resource);
282 goto error0;
283 }
284 pool->shm = shm;
285 pool->resource = wl_resource_create(client, &wl_shm_pool_interface,
286 wl_resource_get_version(resource), id);
287 if (!pool->resource) {
288 wl_resource_post_no_memory(resource);
289 goto error1;
290 }
291 wl_resource_set_implementation(pool->resource, &shm_pool_impl, pool,
292 &destroy_pool_resource);
293 pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
294 pool->writable = true;
295 if (pool->data == MAP_FAILED) {
296 pool->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
297 pool->writable = false;
298 if (pool->data == MAP_FAILED) {
299 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
300 "mmap failed: %s", strerror(errno));
301 goto error2;
302 }
303 }
304 /* close(fd); */
305 pool->size = size;
306 pool->references = 1;
307 pool->fd = fd;
308 return;
309
310error2:
311 wl_resource_destroy(pool->resource);
312error1:
313 free(pool);
314error0:
315 close(fd);
316}
317
318static const struct wl_shm_interface shm_impl = {.create_pool = &create_pool};
319
320static void
321bind_shm(struct wl_client *client, void *data, uint32_t version, uint32_t id)
322{
323 struct swc_shm *shm = data;
324 struct wl_resource *resource;
325
326 resource = wl_resource_create(client, &wl_shm_interface, version, id);
327 if (!resource) {
328 wl_client_post_no_memory(client);
329 return;
330 }
331 wl_resource_set_implementation(resource, &shm_impl, shm, NULL);
332
333 wl_shm_send_format(resource, WL_SHM_FORMAT_XRGB8888);
334 wl_shm_send_format(resource, WL_SHM_FORMAT_ARGB8888);
335}
336
337struct swc_shm *
338shm_create(struct wl_display *display)
339{
340 struct swc_shm *shm;
341
342 shm = malloc(sizeof(*shm));
343 if (!shm) {
344 goto error0;
345 }
346 shm->context = wld_pixman_create_context();
347 if (!shm->context) {
348 goto error1;
349 }
350 shm->renderer = wld_create_renderer(shm->context);
351 if (!shm->renderer) {
352 goto error2;
353 }
354 shm->global =
355 wl_global_create(display, &wl_shm_interface, 1, shm, &bind_shm);
356 if (!shm->global) {
357 goto error3;
358 }
359
360 return shm;
361
362error3:
363 wld_destroy_renderer(shm->renderer);
364error2:
365 wld_destroy_context(shm->context);
366error1:
367 free(shm);
368error0:
369 return NULL;
370}
371
372void
373shm_destroy(struct swc_shm *shm)
374{
375 wl_global_destroy(shm->global);
376 wld_destroy_renderer(shm->renderer);
377 wld_destroy_context(shm->context);
378 free(shm);
379}
380
381bool
382shm_buffer_get_info(struct wl_resource *resource, struct swc_shm_buffer_info *info)
383{
384 struct shm_buffer_record *record;
385 uint64_t size;
386
387 if (!shm_buffer_records_initialized) {
388 return false;
389 }
390
391 wl_list_for_each(record, &shm_buffer_records, link)
392 {
393 if (record->resource != resource) {
394 continue;
395 }
396
397 if (record->width < 0 || record->height < 0 || record->stride < 0) {
398 return false;
399 }
400
401 size = (uint64_t)record->stride * (uint64_t)record->height;
402 if ((uint64_t)record->offset + size > record->pool->size) {
403 return false;
404 }
405
406 info->data = (uint8_t *)record->pool->data + record->offset;
407 info->width = record->width;
408 info->height = record->height;
409 info->stride = record->stride;
410 info->format = record->format;
411 info->writable = record->pool->writable;
412 return true;
413 }
414
415 return false;
416}