xref: /third_party/pulseaudio/src/pulsecore/shm.c (revision 53a5a1b3)
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 */
91struct 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
101static_assert(sizeof(struct shm_marker) == 8 * 5, "`struct shm_marker` is not tightly packed");
102
103static 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
111static 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
117static 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
148static 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        fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING);
172        break;
173#endif
174    default:
175        goto fail;
176    }
177
178    if (fd < 0) {
179        pa_log_error("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno));
180        goto fail;
181    }
182
183    m->type = type;
184    m->size = size + shm_marker_size(type);
185    m->do_unlink = do_unlink;
186
187    if (ftruncate(fd, (off_t) m->size) < 0) {
188        pa_log_error("ftruncate() failed: %s", pa_cstrerror(errno));
189        goto fail;
190    }
191
192#ifndef MAP_NORESERVE
193#define MAP_NORESERVE 0
194#endif
195
196    if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
197        pa_log_error("mmap() failed: %s", pa_cstrerror(errno));
198        goto fail;
199    }
200
201    if (type == PA_MEM_TYPE_SHARED_POSIX) {
202        /* We store our PID at the end of the shm block, so that we
203         * can check for dead shm segments later */
204        marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(type));
205        pa_atomic_store(&marker->pid, (int) getpid());
206        pa_atomic_store(&marker->marker, SHM_MARKER);
207    }
208
209    /* For memfds, we keep the fd open until we pass it
210     * to the other PA endpoint over unix domain socket. */
211    if (type != PA_MEM_TYPE_SHARED_MEMFD) {
212        pa_assert_se(pa_close(fd) == 0);
213        m->fd = -1;
214    }
215#ifdef HAVE_MEMFD
216    else
217        m->fd = fd;
218#endif
219
220    return 0;
221
222fail:
223    if (fd >= 0) {
224#ifdef HAVE_SHM_OPEN
225        if (type == PA_MEM_TYPE_SHARED_POSIX)
226            shm_unlink(fn);
227#endif
228        pa_close(fd);
229    }
230#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
231
232    return -1;
233}
234
235int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
236    pa_assert(m);
237    pa_assert(size > 0);
238    pa_assert(size <= MAX_SHM_SIZE);
239    pa_assert(!(mode & ~0777));
240    pa_assert(mode >= 0600);
241
242    /* Round up to make it page aligned */
243    size = PA_PAGE_ALIGN(size);
244
245    if (type == PA_MEM_TYPE_PRIVATE)
246        return privatemem_create(m, size);
247
248    return sharedmem_create(m, type, size, mode);
249}
250
251static void privatemem_free(pa_shm *m) {
252    pa_assert(m);
253    pa_assert(m->ptr);
254    pa_assert(m->size > 0);
255
256#ifdef MAP_ANONYMOUS
257    if (munmap(m->ptr, m->size) < 0)
258        pa_log_error("munmap() failed: %s", pa_cstrerror(errno));
259#elif defined(HAVE_POSIX_MEMALIGN)
260    free(m->ptr);
261#else
262    pa_xfree(m->ptr);
263#endif
264}
265
266void pa_shm_free(pa_shm *m) {
267    pa_assert(m);
268    pa_assert(m->ptr);
269    pa_assert(m->size > 0);
270
271#ifdef MAP_FAILED
272    pa_assert(m->ptr != MAP_FAILED);
273#endif
274
275    if (m->type == PA_MEM_TYPE_PRIVATE) {
276        privatemem_free(m);
277        goto finish;
278    }
279
280    pa_log_info("mem type: %d", m->type);
281
282#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
283    if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
284        pa_log_error("munmap() failed: %s", pa_cstrerror(errno));
285
286#ifdef HAVE_SHM_OPEN
287    if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) {
288        char fn[32];
289
290        segment_name(fn, sizeof(fn), m->id);
291        if (shm_unlink(fn) < 0)
292            pa_log_error(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
293    }
294#endif
295#ifdef HAVE_MEMFD
296    if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1)
297        pa_assert_se(pa_close(m->fd) == 0);
298#endif
299
300#else
301    /* We shouldn't be here without shm or memfd support */
302    pa_log_error("remove pa_assert_not_reached call");
303#endif
304
305finish:
306    pa_zero(*m);
307}
308
309void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
310    void *ptr;
311    size_t o;
312    const size_t page_size = pa_page_size();
313
314    pa_assert(m);
315    pa_assert(m->ptr);
316    pa_assert(m->size > 0);
317    pa_assert(offset+size <= m->size);
318
319#ifdef MAP_FAILED
320    pa_assert(m->ptr != MAP_FAILED);
321#endif
322
323    /* You're welcome to implement this as NOOP on systems that don't
324     * support it */
325
326    /* Align the pointer up to multiples of the page size */
327    ptr = (uint8_t*) m->ptr + offset;
328    o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
329
330    if (o > 0) {
331        size_t delta = page_size - o;
332        ptr = (uint8_t*) ptr + delta;
333        size -= delta;
334    }
335
336    /* Align the size down to multiples of page size */
337    size = (size / page_size) * page_size;
338
339#ifdef MADV_REMOVE
340    if (madvise(ptr, size, MADV_REMOVE) >= 0)
341        return;
342#endif
343
344#ifdef MADV_FREE
345    if (madvise(ptr, size, MADV_FREE) >= 0)
346        return;
347#endif
348
349#ifdef MADV_DONTNEED
350    madvise(ptr, size, MADV_DONTNEED);
351#elif defined(POSIX_MADV_DONTNEED)
352    posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
353#endif
354}
355
356static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) {
357#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
358    char fn[32];
359    int fd = -1;
360    int prot;
361    struct stat st;
362
363    pa_assert(m);
364
365    switch (type) {
366#ifdef HAVE_SHM_OPEN
367    case PA_MEM_TYPE_SHARED_POSIX:
368        pa_assert(memfd_fd == -1);
369        segment_name(fn, sizeof(fn), id);
370        if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) {
371            if ((errno != EACCES && errno != ENOENT) || !for_cleanup)
372                pa_log_error("shm_open() failed: %s", pa_cstrerror(errno));
373            goto fail;
374        }
375        break;
376#endif
377#ifdef HAVE_MEMFD
378    case PA_MEM_TYPE_SHARED_MEMFD:
379        pa_assert(memfd_fd != -1);
380        fd = memfd_fd;
381        break;
382#endif
383    default:
384        goto fail;
385    }
386
387    if (fstat(fd, &st) < 0) {
388        pa_log_error("fstat() failed: %s", pa_cstrerror(errno));
389        goto fail;
390    }
391
392    if (st.st_size <= 0 ||
393        st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(type) ||
394        PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
395        pa_log_error("Invalid shared memory segment size");
396        goto fail;
397    }
398
399    prot = writable ? PROT_READ | PROT_WRITE : PROT_READ;
400    if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
401        pa_log_error("mmap() failed: %s", pa_cstrerror(errno));
402        goto fail;
403    }
404
405    /* In case of attaching to memfd areas, _the caller_ maintains
406     * ownership of the passed fd and has the sole responsibility
407     * of closing it down.. For other types, we're the code path
408     * which created the fd in the first place and we're thus the
409     * ones responsible for closing it down */
410    if (type != PA_MEM_TYPE_SHARED_MEMFD)
411        pa_assert_se(pa_close(fd) == 0);
412
413    pa_log_info("shm_attach set mem type %d", type);
414    m->type = type;
415    m->id = id;
416    m->size = (size_t) st.st_size;
417    m->do_unlink = false;
418    m->fd = -1;
419
420    return 0;
421
422fail:
423    /* In case of memfds, caller maintains fd ownership */
424    if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD)
425        pa_close(fd);
426
427#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
428
429    return -1;
430}
431
432/* Caller owns passed @memfd_fd and must close it down when appropriate. */
433int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) {
434    return shm_attach(m, type, id, memfd_fd, writable, false);
435}
436
437int pa_shm_cleanup(void) {
438    pa_log_info("start pa_shm_cleanup");
439#ifdef HAVE_SHM_OPEN
440#ifdef SHM_PATH
441    DIR *d;
442    struct dirent *de;
443
444    if (!(d = opendir(SHM_PATH))) {
445        pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
446        return -1;
447    }
448
449    while ((de = readdir(d))) {
450        pa_shm seg;
451        unsigned id;
452        pid_t pid;
453        char fn[128];
454        struct shm_marker *m;
455
456#if defined(__sun)
457        if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN))
458#else
459        if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN))
460#endif
461            continue;
462
463        if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0)
464            continue;
465
466        if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0)
467            continue;
468
469        if (seg.size < shm_marker_size(seg.type)) {
470            pa_shm_free(&seg);
471            continue;
472        }
473
474        m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(seg.type));
475
476        if (pa_atomic_load(&m->marker) != SHM_MARKER) {
477            pa_shm_free(&seg);
478            continue;
479        }
480
481        if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
482            pa_shm_free(&seg);
483            continue;
484        }
485
486        if (kill(pid, 0) == 0 || errno != ESRCH) {
487            pa_shm_free(&seg);
488            continue;
489        }
490
491        pa_shm_free(&seg);
492
493        /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
494        segment_name(fn, sizeof(fn), id);
495
496        if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
497            pa_log_warn("Failed to remove SHM segment %s: %s", fn, pa_cstrerror(errno));
498    }
499
500    closedir(d);
501#endif /* SHM_PATH */
502#endif /* HAVE_SHM_OPEN */
503
504    return 0;
505}
506