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