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, allo */
244 if ((saved == EPERM || saved == EACCES) &&
245 fstat(pool->fd, &st) == 0 && st.st_size >= size) {
246 goto remap;
247 }
248 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
249 "ftruncate failed: %s", strerror(saved));
250 return;
251 }
252 }
253
254remap:
255 data = swc_mremap(pool, pool->data, pool->size, size);
256 if (data == MAP_FAILED) {
257 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
258 "mremap failed: %s", strerror(errno));
259 return;
260 }
261 pool->data = data;
262 pool->size = size;
263}
264
265static const struct wl_shm_pool_interface shm_pool_impl = {
266 .create_buffer = create_buffer,
267 .destroy = destroy_resource,
268 .resize = resize,
269};
270
271static void
272create_pool(struct wl_client *client, struct wl_resource *resource, uint32_t id,
273 int32_t fd, int32_t size)
274{
275 struct swc_shm *shm = wl_resource_get_user_data(resource);
276 struct pool *pool;
277
278 pool = malloc(sizeof(*pool));
279 if (!pool) {
280 wl_resource_post_no_memory(resource);
281 goto error0;
282 }
283 pool->shm = shm;
284 pool->resource = wl_resource_create(client, &wl_shm_pool_interface,
285 wl_resource_get_version(resource), id);
286 if (!pool->resource) {
287 wl_resource_post_no_memory(resource);
288 goto error1;
289 }
290 wl_resource_set_implementation(pool->resource, &shm_pool_impl, pool,
291 &destroy_pool_resource);
292 pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
293 pool->writable = true;
294 if (pool->data == MAP_FAILED) {
295 pool->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
296 pool->writable = false;
297 if (pool->data == MAP_FAILED) {
298 wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
299 "mmap failed: %s", strerror(errno));
300 goto error2;
301 }
302 }
303 /* close(fd); */
304 pool->size = size;
305 pool->references = 1;
306 pool->fd = fd;
307 return;
308
309error2:
310 wl_resource_destroy(pool->resource);
311error1:
312 free(pool);
313error0:
314 close(fd);
315}
316
317static const struct wl_shm_interface shm_impl = {.create_pool = &create_pool};
318
319static void
320bind_shm(struct wl_client *client, void *data, uint32_t version, uint32_t id)
321{
322 struct swc_shm *shm = data;
323 struct wl_resource *resource;
324
325 resource = wl_resource_create(client, &wl_shm_interface, version, id);
326 if (!resource) {
327 wl_client_post_no_memory(client);
328 return;
329 }
330 wl_resource_set_implementation(resource, &shm_impl, shm, NULL);
331
332 wl_shm_send_format(resource, WL_SHM_FORMAT_XRGB8888);
333 wl_shm_send_format(resource, WL_SHM_FORMAT_ARGB8888);
334}
335
336struct swc_shm *
337shm_create(struct wl_display *display)
338{
339 struct swc_shm *shm;
340
341 shm = malloc(sizeof(*shm));
342 if (!shm) {
343 goto error0;
344 }
345 shm->context = wld_pixman_create_context();
346 if (!shm->context) {
347 goto error1;
348 }
349 shm->renderer = wld_create_renderer(shm->context);
350 if (!shm->renderer) {
351 goto error2;
352 }
353 shm->global =
354 wl_global_create(display, &wl_shm_interface, 1, shm, &bind_shm);
355 if (!shm->global) {
356 goto error3;
357 }
358
359 return shm;
360
361error3:
362 wld_destroy_renderer(shm->renderer);
363error2:
364 wld_destroy_context(shm->context);
365error1:
366 free(shm);
367error0:
368 return NULL;
369}
370
371void
372shm_destroy(struct swc_shm *shm)
373{
374 wl_global_destroy(shm->global);
375 wld_destroy_renderer(shm->renderer);
376 wld_destroy_context(shm->context);
377 free(shm);
378}
379
380bool
381shm_buffer_get_info(struct wl_resource *resource, struct swc_shm_buffer_info *info)
382{
383 struct shm_buffer_record *record;
384 uint64_t size;
385
386 if (!shm_buffer_records_initialized) {
387 return false;
388 }
389
390 wl_list_for_each(record, &shm_buffer_records, link)
391 {
392 if (record->resource != resource) {
393 continue;
394 }
395
396 if (record->width < 0 || record->height < 0 || record->stride < 0) {
397 return false;
398 }
399
400 size = (uint64_t)record->stride * (uint64_t)record->height;
401 if ((uint64_t)record->offset + size > record->pool->size) {
402 return false;
403 }
404
405 info->data = (uint8_t *)record->pool->data + record->offset;
406 info->width = record->width;
407 info->height = record->height;
408 info->stride = record->stride;
409 info->format = record->format;
410 info->writable = record->pool->writable;
411 return true;
412 }
413
414 return false;
415}