summaryrefslogtreecommitdiffstats
path: root/shared/os-compatibility.c
blob: 3013f1f2a8ab07c64d9373e6aaa42d7715f28200 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/*
 * Copyright © 2012 Collabora, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <string.h>
#include <stdlib.h>
#include <libweston-6/zalloc.h>

#ifdef HAVE_MEMFD_CREATE
#include <sys/mman.h>
#endif

#include "os-compatibility.h"

#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)

int
os_fd_set_cloexec(int fd)
{
	long flags;

	if (fd == -1)
		return -1;

	flags = fcntl(fd, F_GETFD);
	if (flags == -1)
		return -1;

	if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
		return -1;

	return 0;
}

static int
set_cloexec_or_close(int fd)
{
	if (os_fd_set_cloexec(fd) != 0) {
		close(fd);
		return -1;
	}
	return fd;
}

int
os_socketpair_cloexec(int domain, int type, int protocol, int *sv)
{
	int ret;

#ifdef SOCK_CLOEXEC
	ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
	if (ret == 0 || errno != EINVAL)
		return ret;
#endif

	ret = socketpair(domain, type, protocol, sv);
	if (ret < 0)
		return ret;

	sv[0] = set_cloexec_or_close(sv[0]);
	sv[1] = set_cloexec_or_close(sv[1]);

	if (sv[0] != -1 && sv[1] != -1)
		return 0;

	close(sv[0]);
	close(sv[1]);
	return -1;
}

int
os_epoll_create_cloexec(void)
{
	int fd;

#ifdef EPOLL_CLOEXEC
	fd = epoll_create1(EPOLL_CLOEXEC);
	if (fd >= 0)
		return fd;
	if (errno != EINVAL)
		return -1;
#endif

	fd = epoll_create(1);
	return set_cloexec_or_close(fd);
}

static int
create_tmpfile_cloexec(char *tmpname)
{
	int fd;

#ifdef HAVE_MKOSTEMP
	fd = mkostemp(tmpname, O_CLOEXEC);
	if (fd >= 0)
		unlink(tmpname);
#else
	fd = mkstemp(tmpname);
	if (fd >= 0) {
		fd = set_cloexec_or_close(fd);
		unlink(tmpname);
	}
#endif

	return fd;
}

/*
 * Create a new, unique, anonymous file of the given size, and
 * return the file descriptor for it. The file descriptor is set
 * CLOEXEC. The file is immediately suitable for mmap()'ing
 * the given size at offset zero.
 *
 * The file should not have a permanent backing store like a disk,
 * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
 *
 * The file name is deleted from the file system.
 *
 * The file is suitable for buffer sharing between processes by
 * transmitting the file descriptor over Unix sockets using the
 * SCM_RIGHTS methods.
 *
 * If the C library implements posix_fallocate(), it is used to
 * guarantee that disk space is available for the file at the
 * given size. If disk space is insufficient, errno is set to ENOSPC.
 * If posix_fallocate() is not supported, program may receive
 * SIGBUS on accessing mmap()'ed file contents instead.
 *
 * If the C library implements memfd_create(), it is used to create the
 * file purely in memory, without any backing file name on the file
 * system, and then sealing off the possibility of shrinking it.  This
 * can then be checked before accessing mmap()'ed file contents, to
 * make sure SIGBUS can't happen.  It also avoids requiring
 * XDG_RUNTIME_DIR.
 */
int
os_create_anonymous_file(off_t size)
{
	static const char template[] = "/weston-shared-XXXXXX";
	const char *path;
	char *name;
	int fd;
	int ret;

#ifdef HAVE_MEMFD_CREATE
	fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
	if (fd >= 0) {
		/* We can add this seal before calling posix_fallocate(), as
		 * the file is currently zero-sized anyway.
		 *
		 * There is also no need to check for the return value, we
		 * couldn't do anything with it anyway.
		 */
		fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
	} else
#endif
	{
		path = getenv("XDG_RUNTIME_DIR");
		if (!path) {
			errno = ENOENT;
			return -1;
		}

		name = malloc(strlen(path) + sizeof(template));
		if (!name)
			return -1;

		strcpy(name, path);
		strcat(name, template);

		fd = create_tmpfile_cloexec(name);

		free(name);

		if (fd < 0)
			return -1;
	}

#ifdef HAVE_POSIX_FALLOCATE
	do {
		ret = posix_fallocate(fd, 0, size);
	} while (ret == EINTR);
	if (ret != 0) {
		close(fd);
		errno = ret;
		return -1;
	}
#else
	do {
		ret = ftruncate(fd, size);
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
		close(fd);
		return -1;
	}
#endif

	return fd;
}

#ifndef HAVE_STRCHRNUL
char *
strchrnul(const char *s, int c)
{
	while (*s && *s != c)
		s++;
	return (char *)s;
}
#endif

struct ro_anonymous_file {
	int fd;
	size_t size;
};

/** Create a new anonymous read-only file of the given size and the given data
 *
 * \param size The size of \p data.
 * \param data The data of the file with the size \p size.
 * \return A new \c ro_anonymous_file, or NULL on failure.
 *
 * The intended use-case is for sending mid-sized data from the compositor
 * to clients.
 * If the function fails errno is set.
 */
struct ro_anonymous_file *
os_ro_anonymous_file_create(size_t size,
			    const char *data)
{
	struct ro_anonymous_file *file;
	void *map;

	file = zalloc(sizeof *file);
	if (!file) {
		errno = ENOMEM;
		return NULL;
	}

	file->size = size;
	file->fd = os_create_anonymous_file(size);
	if (file->fd == -1)
		goto err_free;

	map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0);
	if (map == MAP_FAILED)
		goto err_close;

	memcpy(map, data, size);

	munmap(map, size);

#ifdef HAVE_MEMFD_CREATE
	/* try to put seals on the file to make it read-only so that we can
	 * return the fd later directly when support_shared is not set.
	 * os_ro_anonymous_file_get_fd can handle the fd even if it is not
	 * sealed read-only and will instead create a new anonymous file on
	 * each invocation.
	 */
	fcntl(file->fd, F_ADD_SEALS, READONLY_SEALS);
#endif

	return file;

err_close:
	close(file->fd);
err_free:
	free(file);
	return NULL;
}

/** Destroy an anonymous read-only file
 *
 * \param file The file to destroy.
 */
void
os_ro_anonymous_file_destroy(struct ro_anonymous_file *file)
{
	close(file->fd);
	free(file);
}

/** Get the size of an anonymous read-only file
 *
 * \param file The file to get the size of.
 * \return The size of the file.
 */
size_t
os_ro_anonymous_file_size(struct ro_anonymous_file *file)
{
	return file->size;
}

/** Returns a file descriptor for the given file, ready to be send to a client.
 *
 * \param file The file for which to get a file descriptor.
 * \param mapmode Describes the ways in which the returned file descriptor can
 * be used with mmap.
 * \return A file descriptor for the given file that can be send to a client
 * or -1 on failure.
 *
 * The returned file descriptor must not be shared between multiple clients.
 * When \p mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is
 * only guaranteed to be mmapable with \c MAP_PRIVATE, when \p mapmode is
 * RO_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with
 * either MAP_PRIVATE or MAP_SHARED.
 * When you're done with the fd you must call \c os_ro_anonymous_file_put_fd
 * instead of calling \c close.
 * If the function fails errno is set.
 */
int
os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file,
			    enum ro_anonymous_file_mapmode mapmode)
{
	void *src, *dst;
	int seals, fd;

	seals = fcntl(file->fd, F_GET_SEALS);

	/* file was sealed for read-only and we don't have to support MAP_SHARED
	 * so we can simply pass the memfd fd
	 */
	if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE &&
	    (seals & READONLY_SEALS) == READONLY_SEALS)
		return file->fd;

	/* for all other cases we create a new anonymous file that can be mapped
	 * with MAP_SHARED and copy the contents to it and return that instead
	 */
	fd = os_create_anonymous_file(file->size);
	if (fd == -1)
		return fd;

	src = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0);
	if (src == MAP_FAILED) {
		close(fd);
		return -1;
	}

	dst = mmap(NULL, file->size, PROT_WRITE, MAP_SHARED, fd, 0);
	if (dst == MAP_FAILED) {
		close(fd);
		munmap(src, file->size);
		return -1;
	}

	memcpy(dst, src, file->size);
	munmap(src, file->size);
	munmap(dst, file->size);

	return fd;
}

/** Release a file descriptor returned by \c os_ro_anonymous_file_get_fd
 *
 * \param fd A file descriptor returned by \c os_ro_anonymous_file_get_fd.
 * \return 0 on success, or -1 on failure.
 *
 * This function must be called for every file descriptor created with
 * \c os_ro_anonymous_file_get_fd to not leake any resources.
 * If the function fails errno is set.
 */
int
os_ro_anonymous_file_put_fd(int fd)
{
	int seals = fcntl(fd, F_GET_SEALS);
	if (seals == -1 && errno != EINVAL)
		return -1;

	/* If the fd cannot be sealed seals is -1 at this point
	 * or the file can be sealed but has not been sealed for writing.
	 * In both cases we created a new anonymous file that we have to
	 * close.
	 */
	if (seals == -1 || !(seals & F_SEAL_WRITE))
		close(fd);

	return 0;
}