1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #undef LOG_TAG
26 #define LOG_TAG "Shm"
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <dirent.h>
37 #include <signal.h>
38
39 #ifdef HAVE_SYS_MMAN_H
40 #include <sys/mman.h>
41 #endif
42
43 /* This is deprecated on glibc but is still used by FreeBSD */
44 #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
45 # define MAP_ANONYMOUS MAP_ANON
46 #endif
47
48 #include <pulse/xmalloc.h>
49 #include <pulse/gccmacro.h>
50
51 #include <pulsecore/memfd-wrappers.h>
52 #include <pulsecore/core-error.h>
53 #include <pulsecore/log.h>
54 #include <pulsecore/random.h>
55 #include <pulsecore/core-util.h>
56 #include <pulsecore/macro.h>
57 #include <pulsecore/atomic.h>
58 #include <pulsecore/mem.h>
59
60 #include "log/audio_log.h"
61
62 #include "shm.h"
63
64 #if defined(__linux__) && !defined(MADV_REMOVE)
65 #define MADV_REMOVE 9
66 #endif
67
68 /* 1 GiB at max */
69 #define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024))
70
71 #ifdef __linux__
72 /* On Linux we know that the shared memory blocks are files in
73 * /dev/shm. We can use that information to list all blocks and
74 * cleanup unused ones */
75 #define SHM_PATH "/dev/shm/"
76 #define SHM_ID_LEN 10
77 #elif defined(__sun)
78 #define SHM_PATH "/tmp"
79 #define SHM_ID_LEN 15
80 #else
81 #undef SHM_PATH
82 #undef SHM_ID_LEN
83 #endif
84
85 #define SHM_MARKER ((int) 0xbeefcafe)
86
87 /* We now put this SHM marker at the end of each segment. It's
88 * optional, to not require a reboot when upgrading, though. Note that
89 * on multiarch systems 32bit and 64bit processes might access this
90 * region simultaneously. The header fields need to be independent
91 * from the process' word with */
92 struct shm_marker {
93 pa_atomic_t marker; /* 0xbeefcafe */
94 pa_atomic_t pid;
95 uint64_t _reserved1;
96 uint64_t _reserved2;
97 uint64_t _reserved3;
98 uint64_t _reserved4;
99 };
100
101 // Ensure struct is appropriately packed
102 static_assert(sizeof(struct shm_marker) == 8 * 5, "`struct shm_marker` is not tightly packed");
103
shm_marker_size(pa_mem_type_t type)104 static inline size_t shm_marker_size(pa_mem_type_t type) {
105 if (type == PA_MEM_TYPE_SHARED_POSIX)
106 return PA_ALIGN(sizeof(struct shm_marker));
107
108 return 0;
109 }
110
111 #ifdef HAVE_SHM_OPEN
segment_name(char * fn,size_t l,unsigned id)112 static char *segment_name(char *fn, size_t l, unsigned id) {
113 pa_snprintf(fn, l, "/pulse-shm-%u", id);
114 return fn;
115 }
116 #endif
117
privatemem_create(pa_shm * m,size_t size)118 static int privatemem_create(pa_shm *m, size_t size) {
119 pa_assert(m);
120 pa_assert(size > 0);
121
122 m->type = PA_MEM_TYPE_PRIVATE;
123 m->id = 0;
124 m->size = size;
125 m->do_unlink = false;
126 m->fd = -1;
127
128 #ifdef MAP_ANONYMOUS
129 if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) {
130 AUDIO_ERR_LOG("mmap() failed: %{public}s", pa_cstrerror(errno));
131 return -1;
132 }
133 #elif defined(HAVE_POSIX_MEMALIGN)
134 {
135 int r;
136
137 if ((r = posix_memalign(&m->ptr, pa_page_size(), size)) < 0) {
138 AUDIO_ERR_LOG("posix_memalign() failed: %{public}s", pa_cstrerror(r));
139 return r;
140 }
141 }
142 #else
143 m->ptr = pa_xmalloc(m->size);
144 #endif
145
146 return 0;
147 }
148
sharedmem_create(pa_shm * m,pa_mem_type_t type,size_t size,mode_t mode)149 static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
150 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
151 char fn[32];
152 int fd = -1;
153 struct shm_marker *marker;
154 bool do_unlink = false;
155
156 /* Each time we create a new SHM area, let's first drop all stale
157 * ones */
158 pa_shm_cleanup();
159
160 pa_random(&m->id, sizeof(m->id));
161
162 switch (type) {
163 #ifdef HAVE_SHM_OPEN
164 case PA_MEM_TYPE_SHARED_POSIX:
165 segment_name(fn, sizeof(fn), m->id);
166 fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode);
167 do_unlink = true;
168 break;
169 #endif
170 #ifdef HAVE_MEMFD
171 case PA_MEM_TYPE_SHARED_MEMFD:
172 fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING);
173 break;
174 #endif
175 default:
176 goto fail;
177 }
178
179 if (fd < 0) {
180 AUDIO_ERR_LOG("%{public}s open() failed: %{public}s", pa_mem_type_to_string(type), pa_cstrerror(errno));
181 goto fail;
182 }
183
184 m->type = type;
185 m->size = size + shm_marker_size(type);
186 m->do_unlink = do_unlink;
187
188 if (ftruncate(fd, (off_t) m->size) < 0) {
189 AUDIO_ERR_LOG("ftruncate() failed: %{public}s", pa_cstrerror(errno));
190 goto fail;
191 }
192
193 #ifndef MAP_NORESERVE
194 #define MAP_NORESERVE 0
195 #endif
196
197 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
198 AUDIO_ERR_LOG("mmap() failed: %{public}s", pa_cstrerror(errno));
199 goto fail;
200 }
201
202 if (type == PA_MEM_TYPE_SHARED_POSIX) {
203 /* We store our PID at the end of the shm block, so that we
204 * can check for dead shm segments later */
205 marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(type));
206 pa_atomic_store(&marker->pid, (int) getpid());
207 pa_atomic_store(&marker->marker, SHM_MARKER);
208 }
209
210 /* For memfds, we keep the fd open until we pass it
211 * to the other PA endpoint over unix domain socket. */
212 if (type != PA_MEM_TYPE_SHARED_MEMFD) {
213 pa_assert_se(pa_close(fd) == 0);
214 m->fd = -1;
215 }
216 #ifdef HAVE_MEMFD
217 else
218 m->fd = fd;
219 #endif
220
221 return 0;
222
223 fail:
224 if (fd >= 0) {
225 #ifdef HAVE_SHM_OPEN
226 if (type == PA_MEM_TYPE_SHARED_POSIX)
227 shm_unlink(fn);
228 #endif
229 pa_close(fd);
230 }
231 #endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
232
233 return -1;
234 }
235
pa_shm_create_rw(pa_shm * m,pa_mem_type_t type,size_t size,mode_t mode)236 int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
237 pa_assert(m);
238 pa_assert(size > 0);
239 pa_assert(size <= MAX_SHM_SIZE);
240 pa_assert(!(mode & ~0777));
241 pa_assert(mode >= 0600);
242
243 /* Round up to make it page aligned */
244 size = PA_PAGE_ALIGN(size);
245
246 if (type == PA_MEM_TYPE_PRIVATE)
247 return privatemem_create(m, size);
248
249 return sharedmem_create(m, type, size, mode);
250 }
251
privatemem_free(pa_shm * m)252 static void privatemem_free(pa_shm *m) {
253 pa_assert(m);
254 pa_assert(m->ptr);
255 pa_assert(m->size > 0);
256
257 #ifdef MAP_ANONYMOUS
258 if (munmap(m->ptr, m->size) < 0)
259 AUDIO_ERR_LOG("munmap() failed: %{public}s", pa_cstrerror(errno));
260 #elif defined(HAVE_POSIX_MEMALIGN)
261 free(m->ptr);
262 #else
263 pa_xfree(m->ptr);
264 #endif
265 }
266
pa_shm_free(pa_shm * m)267 void pa_shm_free(pa_shm *m) {
268 pa_assert(m);
269 pa_assert(m->ptr);
270 pa_assert(m->size > 0);
271
272 #ifdef MAP_FAILED
273 pa_assert(m->ptr != MAP_FAILED);
274 #endif
275
276 if (m->type == PA_MEM_TYPE_PRIVATE) {
277 privatemem_free(m);
278 goto finish;
279 }
280
281 AUDIO_INFO_LOG("mem type: %{public}d", m->type);
282
283 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
284 if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
285 AUDIO_ERR_LOG("munmap() failed: %{public}s", pa_cstrerror(errno));
286
287 #ifdef HAVE_SHM_OPEN
288 if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) {
289 char fn[32];
290
291 segment_name(fn, sizeof(fn), m->id);
292 if (shm_unlink(fn) < 0)
293 AUDIO_ERR_LOG(" shm_unlink(%{public}s) failed: %{public}s", fn, pa_cstrerror(errno));
294 }
295 #endif
296 #ifdef HAVE_MEMFD
297 if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1)
298 pa_assert_se(pa_close(m->fd) == 0);
299 #endif
300
301 #else
302 /* We shouldn't be here without shm or memfd support */
303 AUDIO_ERR_LOG("remove pa_assert_not_reached call");
304 #endif
305
306 finish:
307 pa_zero(*m);
308 }
309
pa_shm_punch(pa_shm * m,size_t offset,size_t size)310 void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
311 void *ptr;
312 size_t o;
313 const size_t page_size = pa_page_size();
314
315 pa_assert(m);
316 pa_assert(m->ptr);
317 pa_assert(m->size > 0);
318 pa_assert(offset+size <= m->size);
319
320 #ifdef MAP_FAILED
321 pa_assert(m->ptr != MAP_FAILED);
322 #endif
323
324 /* You're welcome to implement this as NOOP on systems that don't
325 * support it */
326
327 /* Align the pointer up to multiples of the page size */
328 ptr = (uint8_t*) m->ptr + offset;
329 o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
330
331 if (o > 0) {
332 size_t delta = page_size - o;
333 ptr = (uint8_t*) ptr + delta;
334 size -= delta;
335 }
336
337 /* Align the size down to multiples of page size */
338 size = (size / page_size) * page_size;
339
340 #ifdef MADV_REMOVE
341 if (madvise(ptr, size, MADV_REMOVE) >= 0)
342 return;
343 #endif
344
345 #ifdef MADV_FREE
346 if (madvise(ptr, size, MADV_FREE) >= 0)
347 return;
348 #endif
349
350 #ifdef MADV_DONTNEED
351 madvise(ptr, size, MADV_DONTNEED);
352 #elif defined(POSIX_MADV_DONTNEED)
353 posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
354 #endif
355 }
356
shm_attach(pa_shm * m,pa_mem_type_t type,unsigned id,int memfd_fd,bool writable,bool for_cleanup)357 static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) {
358 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
359 char fn[32];
360 int fd = -1;
361 int prot;
362 struct stat st;
363
364 pa_assert(m);
365
366 switch (type) {
367 #ifdef HAVE_SHM_OPEN
368 case PA_MEM_TYPE_SHARED_POSIX:
369 pa_assert(memfd_fd == -1);
370 segment_name(fn, sizeof(fn), id);
371 if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) {
372 if ((errno != EACCES && errno != ENOENT) || !for_cleanup)
373 AUDIO_ERR_LOG("shm_open() failed: %{public}s", pa_cstrerror(errno));
374 goto fail;
375 }
376 break;
377 #endif
378 #ifdef HAVE_MEMFD
379 case PA_MEM_TYPE_SHARED_MEMFD:
380 pa_assert(memfd_fd != -1);
381 fd = memfd_fd;
382 break;
383 #endif
384 default:
385 goto fail;
386 }
387
388 if (fstat(fd, &st) < 0) {
389 AUDIO_ERR_LOG("fstat() failed: %{public}s", pa_cstrerror(errno));
390 goto fail;
391 }
392
393 if (st.st_size <= 0 ||
394 st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(type) ||
395 PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
396 AUDIO_ERR_LOG("Invalid shared memory segment size");
397 goto fail;
398 }
399
400 prot = writable ? PROT_READ | PROT_WRITE : PROT_READ;
401 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
402 AUDIO_ERR_LOG("mmap() failed: %{public}s", pa_cstrerror(errno));
403 goto fail;
404 }
405
406 /* In case of attaching to memfd areas, _the caller_ maintains
407 * ownership of the passed fd and has the sole responsibility
408 * of closing it down.. For other types, we're the code path
409 * which created the fd in the first place and we're thus the
410 * ones responsible for closing it down */
411 if (type != PA_MEM_TYPE_SHARED_MEMFD)
412 pa_assert_se(pa_close(fd) == 0);
413
414 AUDIO_INFO_LOG("shm_attach set mem type %{public}d", type);
415 m->type = type;
416 m->id = id;
417 m->size = (size_t) st.st_size;
418 m->do_unlink = false;
419 m->fd = -1;
420
421 return 0;
422
423 fail:
424 /* In case of memfds, caller maintains fd ownership */
425 if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD)
426 pa_close(fd);
427
428 #endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
429
430 return -1;
431 }
432
433 /* Caller owns passed @memfd_fd and must close it down when appropriate. */
pa_shm_attach(pa_shm * m,pa_mem_type_t type,unsigned id,int memfd_fd,bool writable)434 int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) {
435 return shm_attach(m, type, id, memfd_fd, writable, false);
436 }
437
pa_shm_cleanup(void)438 int pa_shm_cleanup(void) {
439 AUDIO_INFO_LOG("start pa_shm_cleanup");
440 #ifdef HAVE_SHM_OPEN
441 #ifdef SHM_PATH
442 DIR *d;
443 struct dirent *de;
444
445 if (!(d = opendir(SHM_PATH))) {
446 AUDIO_WARNING_LOG("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
447 return -1;
448 }
449
450 while ((de = readdir(d))) {
451 pa_shm seg;
452 unsigned id;
453 pid_t pid;
454 char fn[128];
455 struct shm_marker *m;
456
457 #if defined(__sun)
458 if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN))
459 #else
460 if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN))
461 #endif
462 continue;
463
464 if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0)
465 continue;
466
467 if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0)
468 continue;
469
470 if (seg.size < shm_marker_size(seg.type)) {
471 pa_shm_free(&seg);
472 continue;
473 }
474
475 m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(seg.type));
476
477 if (pa_atomic_load(&m->marker) != SHM_MARKER) {
478 pa_shm_free(&seg);
479 continue;
480 }
481
482 if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
483 pa_shm_free(&seg);
484 continue;
485 }
486
487 if (kill(pid, 0) == 0 || errno != ESRCH) {
488 pa_shm_free(&seg);
489 continue;
490 }
491
492 pa_shm_free(&seg);
493
494 /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
495 segment_name(fn, sizeof(fn), id);
496
497 if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
498 AUDIO_WARNING_LOG("Failed to remove SHM segment %{public}s: %{public}s", fn, pa_cstrerror(errno));
499 }
500
501 closedir(d);
502 #endif /* SHM_PATH */
503 #endif /* HAVE_SHM_OPEN */
504
505 return 0;
506 }
507