1 /*
2 * Copyright © 2012 Collabora, Ltd.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the
13 * next paragraph) shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26 #define _GNU_SOURCE
27
28 #include "config.h"
29
30 #include <sys/types.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <signal.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38
39 #ifdef HAVE_MEMFD_CREATE
40 #include <sys/mman.h>
41 #endif
42
43 #include "os-compatibility.h"
44
45 #ifndef HAVE_MKOSTEMP
46 static int
set_cloexec_or_close(int fd)47 set_cloexec_or_close(int fd)
48 {
49 long flags;
50
51 if (fd == -1)
52 return -1;
53
54 flags = fcntl(fd, F_GETFD);
55 if (flags == -1)
56 goto err;
57
58 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
59 goto err;
60
61 return fd;
62
63 err:
64 close(fd);
65 return -1;
66 }
67 #endif
68
69 static int
create_tmpfile_cloexec(char * tmpname)70 create_tmpfile_cloexec(char *tmpname)
71 {
72 int fd;
73
74 #ifdef HAVE_MKOSTEMP
75 fd = mkostemp(tmpname, O_CLOEXEC);
76 if (fd >= 0)
77 unlink(tmpname);
78 #else
79 fd = mkstemp(tmpname);
80 if (fd >= 0) {
81 fd = set_cloexec_or_close(fd);
82 unlink(tmpname);
83 }
84 #endif
85
86 return fd;
87 }
88
89 /*
90 * Create a new, unique, anonymous file of the given size, and
91 * return the file descriptor for it. The file descriptor is set
92 * CLOEXEC. The file is immediately suitable for mmap()'ing
93 * the given size at offset zero.
94 *
95 * The file should not have a permanent backing store like a disk,
96 * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
97 *
98 * The file name is deleted from the file system.
99 *
100 * The file is suitable for buffer sharing between processes by
101 * transmitting the file descriptor over Unix sockets using the
102 * SCM_RIGHTS methods.
103 *
104 * If the C library implements posix_fallocate(), it is used to
105 * guarantee that disk space is available for the file at the
106 * given size. If disk space is insufficient, errno is set to ENOSPC.
107 * If posix_fallocate() is not supported, program may receive
108 * SIGBUS on accessing mmap()'ed file contents instead.
109 *
110 * If the C library implements memfd_create(), it is used to create the
111 * file purely in memory, without any backing file name on the file
112 * system, and then sealing off the possibility of shrinking it. This
113 * can then be checked before accessing mmap()'ed file contents, to
114 * make sure SIGBUS can't happen. It also avoids requiring
115 * XDG_RUNTIME_DIR.
116 */
117 int
os_create_anonymous_file(off_t size)118 os_create_anonymous_file(off_t size)
119 {
120 static const char template[] = "/wayland-cursor-shared-XXXXXX";
121 const char *path;
122 char *name;
123 size_t name_size;
124 int fd;
125
126 #ifdef HAVE_MEMFD_CREATE
127 fd = memfd_create("wayland-cursor", MFD_CLOEXEC | MFD_ALLOW_SEALING);
128 if (fd >= 0) {
129 /* We can add this seal before calling posix_fallocate(), as
130 * the file is currently zero-sized anyway.
131 *
132 * There is also no need to check for the return value, we
133 * couldn't do anything with it anyway.
134 */
135 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
136 } else
137 #endif
138 {
139 path = getenv("XDG_RUNTIME_DIR");
140 if (!path || path[0] != '/') {
141 errno = ENOENT;
142 return -1;
143 }
144
145 name_size = strlen(path) + sizeof(template);
146 name = malloc(name_size);
147 if (!name)
148 return -1;
149
150 snprintf(name, name_size, "%s%s", path, template);
151
152 fd = create_tmpfile_cloexec(name);
153
154 free(name);
155
156 if (fd < 0)
157 return -1;
158 }
159
160 if (os_resize_anonymous_file(fd, size) < 0) {
161 close(fd);
162 return -1;
163 }
164
165 return fd;
166 }
167
168 int
os_resize_anonymous_file(int fd,off_t size)169 os_resize_anonymous_file(int fd, off_t size)
170 {
171 #ifdef HAVE_POSIX_FALLOCATE
172 sigset_t mask;
173 sigset_t old_mask;
174
175 /*
176 * posix_fallocate() might be interrupted, so we need to check
177 * for EINTR and retry in that case.
178 * However, in the presence of an alarm, the interrupt may trigger
179 * repeatedly and prevent a large posix_fallocate() to ever complete
180 * successfully, so we need to first block SIGALRM to prevent
181 * this.
182 */
183 sigemptyset(&mask);
184 sigaddset(&mask, SIGALRM);
185 sigprocmask(SIG_BLOCK, &mask, &old_mask);
186 /*
187 * Filesystems that do not support fallocate will return EINVAL or
188 * EOPNOTSUPP. In this case we need to fall back to ftruncate
189 */
190 do {
191 errno = posix_fallocate(fd, 0, size);
192 } while (errno == EINTR);
193 sigprocmask(SIG_SETMASK, &old_mask, NULL);
194 if (errno == 0)
195 return 0;
196 else if (errno != EINVAL && errno != EOPNOTSUPP)
197 return -1;
198 #endif
199 if (ftruncate(fd, size) < 0)
200 return -1;
201
202 return 0;
203 }
204