summaryrefslogtreecommitdiffstats
path: root/src/pulsecore/shm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pulsecore/shm.c')
-rw-r--r--src/pulsecore/shm.c247
1 files changed, 176 insertions, 71 deletions
diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c
index 444d4010..6882e7f8 100644
--- a/src/pulsecore/shm.c
+++ b/src/pulsecore/shm.c
@@ -1,32 +1,31 @@
/* $Id$ */
/***
- This file is part of PulseAudio.
+ This file is part of PulseAudio.
- Copyright 2006 Lennart Poettering
- Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
- PulseAudio is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as
- published by the Free Software Foundation; either version 2.1 of the
- License, or (at your option) any later version.
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
- PulseAudio is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with PulseAudio; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- USA.
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
-#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
@@ -34,15 +33,22 @@
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <signal.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
+#include <pulse/xmalloc.h>
+
#include <pulsecore/core-error.h>
#include <pulsecore/log.h>
#include <pulsecore/random.h>
-#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
#include "shm.h"
@@ -50,10 +56,31 @@
#define MADV_REMOVE 9
#endif
-#define MAX_SHM_SIZE (1024*1024*20)
+#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20))
+
+#ifdef __linux__
+/* On Linux we know that the shared memory blocks are files in
+ * /dev/shm. We can use that information to list all blocks and
+ * cleanup unused ones */
+#define SHM_PATH "/dev/shm/"
+#else
+#undef SHM_PATH
+#endif
+
+#define SHM_MARKER ((int) 0xbeefcafe)
+
+/* We now put this SHM marker at the end of each segment. It's optional to not require a reboot when upgrading, though */
+struct shm_marker {
+ pa_atomic_t marker; /* 0xbeefcafe */
+ pa_atomic_t pid;
+ void *_reserverd1;
+ void *_reserverd2;
+ void *_reserverd3;
+ void *_reserverd4;
+};
static char *segment_name(char *fn, size_t l, unsigned id) {
- snprintf(fn, l, "/pulse-shm-%u", id);
+ pa_snprintf(fn, l, "/pulse-shm-%u", id);
return fn;
}
@@ -61,10 +88,17 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
char fn[32];
int fd = -1;
- assert(m);
- assert(size > 0);
- assert(size < MAX_SHM_SIZE);
- assert(mode >= 0600);
+ pa_assert(m);
+ pa_assert(size > 0);
+ pa_assert(size < MAX_SHM_SIZE);
+ pa_assert(mode >= 0600);
+
+ /* Each time we create a new SHM area, let's first drop all stale
+ * ones */
+ pa_shm_cleanup();
+
+ /* Round up to make it aligned */
+ size = PA_ALIGN(size);
if (!shared) {
m->id = 0;
@@ -79,7 +113,7 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
{
int r;
- if ((r = posix_memalign(&m->ptr, sysconf(_SC_PAGESIZE), size)) < 0) {
+ if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) {
pa_log("posix_memalign() failed: %s", pa_cstrerror(r));
goto fail;
}
@@ -92,6 +126,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
} else {
#ifdef HAVE_SHM_OPEN
+ struct shm_marker *marker;
+
pa_random(&m->id, sizeof(m->id));
segment_name(fn, sizeof(fn), m->id);
@@ -100,7 +136,9 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
goto fail;
}
- if (ftruncate(fd, m->size = size) < 0) {
+ m->size = size + PA_ALIGN(sizeof(struct shm_marker));
+
+ if (ftruncate(fd, m->size) < 0) {
pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
goto fail;
}
@@ -110,10 +148,16 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
goto fail;
}
- close(fd);
+ /* We store our PID at the end of the shm block, so that we
+ * can check for dead shm segments later */
+ marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker)));
+ pa_atomic_store(&marker->pid, (int) getpid());
+ pa_atomic_store(&marker->marker, SHM_MARKER);
+
+ pa_assert_se(close(fd) == 0);
m->do_unlink = 1;
#else
- return -1;
+ return -1;
#endif
}
@@ -126,7 +170,7 @@ fail:
#ifdef HAVE_SHM_OPEN
if (fd >= 0) {
shm_unlink(fn);
- close(fd);
+ pa_close(fd);
}
#endif
@@ -134,76 +178,70 @@ fail:
}
void pa_shm_free(pa_shm *m) {
- assert(m);
- assert(m->ptr);
- assert(m->size > 0);
+ pa_assert(m);
+ pa_assert(m->ptr);
+ pa_assert(m->size > 0);
#ifdef MAP_FAILED
- assert(m->ptr != MAP_FAILED);
+ pa_assert(m->ptr != MAP_FAILED);
#endif
- if (!m->shared) {
+ if (!m->shared) {
#ifdef MAP_ANONYMOUS
- if (munmap(m->ptr, m->size) < 0)
- pa_log("munmap() failed: %s", pa_cstrerror(errno));
+ if (munmap(m->ptr, m->size) < 0)
+ pa_log("munmap() failed: %s", pa_cstrerror(errno));
#elif defined(HAVE_POSIX_MEMALIGN)
free(m->ptr);
#else
pa_xfree(m->ptr);
#endif
- } else {
+ } else {
#ifdef HAVE_SHM_OPEN
- if (munmap(m->ptr, m->size) < 0)
- pa_log("munmap() failed: %s", pa_cstrerror(errno));
+ if (munmap(m->ptr, m->size) < 0)
+ pa_log("munmap() failed: %s", pa_cstrerror(errno));
- if (m->do_unlink) {
- char fn[32];
+ if (m->do_unlink) {
+ char fn[32];
- segment_name(fn, sizeof(fn), m->id);
+ segment_name(fn, sizeof(fn), m->id);
- if (shm_unlink(fn) < 0)
- pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
- }
+ if (shm_unlink(fn) < 0)
+ pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
+ }
#else
- /* We shouldn't be here without shm support */
- assert(0);
+ /* We shouldn't be here without shm support */
+ pa_assert_not_reached();
#endif
- }
+ }
memset(m, 0, sizeof(*m));
}
void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
void *ptr;
+ size_t o, ps;
- assert(m);
- assert(m->ptr);
- assert(m->size > 0);
- assert(offset+size <= m->size);
+ pa_assert(m);
+ pa_assert(m->ptr);
+ pa_assert(m->size > 0);
+ pa_assert(offset+size <= m->size);
#ifdef MAP_FAILED
- assert(m->ptr != MAP_FAILED);
+ pa_assert(m->ptr != MAP_FAILED);
#endif
/* You're welcome to implement this as NOOP on systems that don't
* support it */
+ /* Align this to multiples of the page size */
ptr = (uint8_t*) m->ptr + offset;
-
-#ifdef __linux__
-{
- /* On Linux ptr must be page aligned */
- long psz = sysconf(_SC_PAGESIZE);
- unsigned o;
-
- o = ((unsigned long) ptr) - ((((unsigned long) ptr)/psz) * psz);
+ o = (uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr);
if (o > 0) {
- ptr = (uint8_t*) ptr + (psz - o);
- size -= psz - o;
+ ps = PA_PAGE_SIZE;
+ ptr = (uint8_t*) ptr + (ps - o);
+ size -= ps - o;
}
-}
-#endif
#ifdef MADV_REMOVE
if (madvise(ptr, size, MADV_REMOVE) >= 0)
@@ -216,7 +254,9 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
#endif
#ifdef MADV_DONTNEED
- madvise(ptr, size, MADV_DONTNEED);
+ pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0);
+#elif defined(POSIX_MADV_DONTNEED)
+ pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0);
#endif
}
@@ -227,12 +267,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {
int fd = -1;
struct stat st;
- assert(m);
+ pa_assert(m);
segment_name(fn, sizeof(fn), m->id = id);
if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) {
- pa_log("shm_open() failed: %s", pa_cstrerror(errno));
+ if (errno != EACCES)
+ pa_log("shm_open() failed: %s", pa_cstrerror(errno));
goto fail;
}
@@ -241,7 +282,7 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {
goto fail;
}
- if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE) {
+ if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker)) || PA_ALIGN(st.st_size) != st.st_size) {
pa_log("Invalid shared memory segment size");
goto fail;
}
@@ -256,13 +297,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {
m->do_unlink = 0;
m->shared = 1;
- close(fd);
+ pa_assert_se(pa_close(fd) == 0);
return 0;
fail:
if (fd >= 0)
- close(fd);
+ pa_close(fd);
return -1;
}
@@ -270,7 +311,71 @@ fail:
#else /* HAVE_SHM_OPEN */
int pa_shm_attach_ro(pa_shm *m, unsigned id) {
- return -1;
+ return -1;
}
#endif /* HAVE_SHM_OPEN */
+
+int pa_shm_cleanup(void) {
+
+#ifdef SHM_PATH
+ DIR *d;
+ struct dirent *de;
+
+ if (!(d = opendir(SHM_PATH))) {
+ pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ while ((de = readdir(d))) {
+ pa_shm seg;
+ unsigned id;
+ pid_t pid;
+ char fn[128];
+ struct shm_marker *m;
+
+ if (strncmp(de->d_name, "pulse-shm-", 10))
+ continue;
+
+ if (pa_atou(de->d_name + 10, &id) < 0)
+ continue;
+
+ if (pa_shm_attach_ro(&seg, id) < 0)
+ continue;
+
+ if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker)));
+
+ if (pa_atomic_load(&m->marker) != SHM_MARKER) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ if (kill(pid, 0) == 0 || errno != ESRCH) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ pa_shm_free(&seg);
+
+ /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
+ segment_name(fn, sizeof(fn), id);
+
+ if (shm_unlink(fn) < 0 && errno != EACCES)
+ pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno));
+ }
+
+ closedir(d);
+#endif
+
+ return 0;
+}