main shrub/neuswc / libswc / shm.c
  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}