]> code.delx.au - pulseaudio/blob - src/pulsecore/shm.c
merge 'lennart' branch back into trunk.
[pulseaudio] / src / pulsecore / shm.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 Copyright 2006 Lennart Poettering
7 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
8
9 PulseAudio is free software; you can redistribute it and/or modify
10 it under the terms of the GNU Lesser General Public License as
11 published by the Free Software Foundation; either version 2.1 of the
12 License, or (at your option) any later version.
13
14 PulseAudio is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with PulseAudio; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 USA.
23 ***/
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
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 #include <pulse/xmalloc.h>
45
46 #include <pulsecore/core-error.h>
47 #include <pulsecore/log.h>
48 #include <pulsecore/random.h>
49 #include <pulsecore/core-util.h>
50 #include <pulsecore/macro.h>
51 #include <pulsecore/atomic.h>
52
53 #include "shm.h"
54
55 #if defined(__linux__) && !defined(MADV_REMOVE)
56 #define MADV_REMOVE 9
57 #endif
58
59 #define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20))
60
61 #ifdef __linux__
62 /* On Linux we know that the shared memory blocks are files in
63 * /dev/shm. We can use that information to list all blocks and
64 * cleanup unused ones */
65 #define SHM_PATH "/dev/shm/"
66 #else
67 #undef SHM_PATH
68 #endif
69
70 #define SHM_MARKER ((int) 0xbeefcafe)
71
72 /* We now put this SHM marker at the end of each segment. It's optional to not require a reboot when upgrading, though */
73 struct shm_marker {
74 pa_atomic_t marker; /* 0xbeefcafe */
75 pa_atomic_t pid;
76 void *_reserverd1;
77 void *_reserverd2;
78 void *_reserverd3;
79 void *_reserverd4;
80 };
81
82 static char *segment_name(char *fn, size_t l, unsigned id) {
83 pa_snprintf(fn, l, "/pulse-shm-%u", id);
84 return fn;
85 }
86
87 int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
88 char fn[32];
89 int fd = -1;
90
91 pa_assert(m);
92 pa_assert(size > 0);
93 pa_assert(size < MAX_SHM_SIZE);
94 pa_assert(mode >= 0600);
95
96 /* Each time we create a new SHM area, let's first drop all stale
97 * ones */
98 pa_shm_cleanup();
99
100 /* Round up to make it aligned */
101 size = PA_ALIGN(size);
102
103 if (!shared) {
104 m->id = 0;
105 m->size = size;
106
107 #ifdef MAP_ANONYMOUS
108 if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
109 pa_log("mmap() failed: %s", pa_cstrerror(errno));
110 goto fail;
111 }
112 #elif defined(HAVE_POSIX_MEMALIGN)
113 {
114 int r;
115
116 if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) {
117 pa_log("posix_memalign() failed: %s", pa_cstrerror(r));
118 goto fail;
119 }
120 }
121 #else
122 m->ptr = pa_xmalloc(m->size);
123 #endif
124
125 m->do_unlink = 0;
126
127 } else {
128 #ifdef HAVE_SHM_OPEN
129 struct shm_marker *marker;
130
131 pa_random(&m->id, sizeof(m->id));
132 segment_name(fn, sizeof(fn), m->id);
133
134 if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode & 0444)) < 0) {
135 pa_log("shm_open() failed: %s", pa_cstrerror(errno));
136 goto fail;
137 }
138
139 m->size = size + PA_ALIGN(sizeof(struct shm_marker));
140
141 if (ftruncate(fd, m->size) < 0) {
142 pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
143 goto fail;
144 }
145
146 if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
147 pa_log("mmap() failed: %s", pa_cstrerror(errno));
148 goto fail;
149 }
150
151 /* We store our PID at the end of the shm block, so that we
152 * can check for dead shm segments later */
153 marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker)));
154 pa_atomic_store(&marker->pid, (int) getpid());
155 pa_atomic_store(&marker->marker, SHM_MARKER);
156
157 pa_assert_se(close(fd) == 0);
158 m->do_unlink = 1;
159 #else
160 return -1;
161 #endif
162 }
163
164 m->shared = shared;
165
166 return 0;
167
168 fail:
169
170 #ifdef HAVE_SHM_OPEN
171 if (fd >= 0) {
172 shm_unlink(fn);
173 pa_close(fd);
174 }
175 #endif
176
177 return -1;
178 }
179
180 void pa_shm_free(pa_shm *m) {
181 pa_assert(m);
182 pa_assert(m->ptr);
183 pa_assert(m->size > 0);
184
185 #ifdef MAP_FAILED
186 pa_assert(m->ptr != MAP_FAILED);
187 #endif
188
189 if (!m->shared) {
190 #ifdef MAP_ANONYMOUS
191 if (munmap(m->ptr, m->size) < 0)
192 pa_log("munmap() failed: %s", pa_cstrerror(errno));
193 #elif defined(HAVE_POSIX_MEMALIGN)
194 free(m->ptr);
195 #else
196 pa_xfree(m->ptr);
197 #endif
198 } else {
199 #ifdef HAVE_SHM_OPEN
200 if (munmap(m->ptr, m->size) < 0)
201 pa_log("munmap() failed: %s", pa_cstrerror(errno));
202
203 if (m->do_unlink) {
204 char fn[32];
205
206 segment_name(fn, sizeof(fn), m->id);
207
208 if (shm_unlink(fn) < 0)
209 pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
210 }
211 #else
212 /* We shouldn't be here without shm support */
213 pa_assert_not_reached();
214 #endif
215 }
216
217 memset(m, 0, sizeof(*m));
218 }
219
220 void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
221 void *ptr;
222 size_t o, ps;
223
224 pa_assert(m);
225 pa_assert(m->ptr);
226 pa_assert(m->size > 0);
227 pa_assert(offset+size <= m->size);
228
229 #ifdef MAP_FAILED
230 pa_assert(m->ptr != MAP_FAILED);
231 #endif
232
233 /* You're welcome to implement this as NOOP on systems that don't
234 * support it */
235
236 /* Align this to multiples of the page size */
237 ptr = (uint8_t*) m->ptr + offset;
238 o = (uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr);
239
240 if (o > 0) {
241 ps = PA_PAGE_SIZE;
242 ptr = (uint8_t*) ptr + (ps - o);
243 size -= ps - o;
244 }
245
246 #ifdef MADV_REMOVE
247 if (madvise(ptr, size, MADV_REMOVE) >= 0)
248 return;
249 #endif
250
251 #ifdef MADV_FREE
252 if (madvise(ptr, size, MADV_FREE) >= 0)
253 return;
254 #endif
255
256 #ifdef MADV_DONTNEED
257 pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0);
258 #elif defined(POSIX_MADV_DONTNEED)
259 pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0);
260 #endif
261 }
262
263 #ifdef HAVE_SHM_OPEN
264
265 int pa_shm_attach_ro(pa_shm *m, unsigned id) {
266 char fn[32];
267 int fd = -1;
268 struct stat st;
269
270 pa_assert(m);
271
272 segment_name(fn, sizeof(fn), m->id = id);
273
274 if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) {
275 if (errno != EACCES)
276 pa_log("shm_open() failed: %s", pa_cstrerror(errno));
277 goto fail;
278 }
279
280 if (fstat(fd, &st) < 0) {
281 pa_log("fstat() failed: %s", pa_cstrerror(errno));
282 goto fail;
283 }
284
285 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) {
286 pa_log("Invalid shared memory segment size");
287 goto fail;
288 }
289
290 m->size = st.st_size;
291
292 if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
293 pa_log("mmap() failed: %s", pa_cstrerror(errno));
294 goto fail;
295 }
296
297 m->do_unlink = 0;
298 m->shared = 1;
299
300 pa_assert_se(pa_close(fd) == 0);
301
302 return 0;
303
304 fail:
305 if (fd >= 0)
306 pa_close(fd);
307
308 return -1;
309 }
310
311 #else /* HAVE_SHM_OPEN */
312
313 int pa_shm_attach_ro(pa_shm *m, unsigned id) {
314 return -1;
315 }
316
317 #endif /* HAVE_SHM_OPEN */
318
319 int pa_shm_cleanup(void) {
320
321 #ifdef SHM_PATH
322 DIR *d;
323 struct dirent *de;
324
325 if (!(d = opendir(SHM_PATH))) {
326 pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
327 return -1;
328 }
329
330 while ((de = readdir(d))) {
331 pa_shm seg;
332 unsigned id;
333 pid_t pid;
334 char fn[128];
335 struct shm_marker *m;
336
337 if (strncmp(de->d_name, "pulse-shm-", 10))
338 continue;
339
340 if (pa_atou(de->d_name + 10, &id) < 0)
341 continue;
342
343 if (pa_shm_attach_ro(&seg, id) < 0)
344 continue;
345
346 if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) {
347 pa_shm_free(&seg);
348 continue;
349 }
350
351 m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker)));
352
353 if (pa_atomic_load(&m->marker) != SHM_MARKER) {
354 pa_shm_free(&seg);
355 continue;
356 }
357
358 if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
359 pa_shm_free(&seg);
360 continue;
361 }
362
363 if (kill(pid, 0) == 0 || errno != ESRCH) {
364 pa_shm_free(&seg);
365 continue;
366 }
367
368 pa_shm_free(&seg);
369
370 /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
371 segment_name(fn, sizeof(fn), id);
372
373 if (shm_unlink(fn) < 0 && errno != EACCES)
374 pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno));
375 }
376
377 closedir(d);
378 #endif
379
380 return 0;
381 }