main 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, 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}