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