Branch data Line data Source code
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, write to the Free Software
19 : : Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 : : USA.
21 : : ***/
22 : :
23 : : #ifdef HAVE_CONFIG_H
24 : : #include <config.h>
25 : : #endif
26 : :
27 : : #include <stdlib.h>
28 : : #include <unistd.h>
29 : : #include <fcntl.h>
30 : : #include <stdio.h>
31 : : #include <errno.h>
32 : : #include <string.h>
33 : : #include <sys/stat.h>
34 : : #include <sys/types.h>
35 : : #include <dirent.h>
36 : : #include <signal.h>
37 : :
38 : : #ifdef HAVE_SYS_MMAN_H
39 : : #include <sys/mman.h>
40 : : #endif
41 : :
42 : : /* This is deprecated on glibc but is still used by FreeBSD */
43 : : #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
44 : : # define MAP_ANONYMOUS MAP_ANON
45 : : #endif
46 : :
47 : : #include <pulse/xmalloc.h>
48 : : #include <pulse/gccmacro.h>
49 : :
50 : : #include <pulsecore/core-error.h>
51 : : #include <pulsecore/log.h>
52 : : #include <pulsecore/random.h>
53 : : #include <pulsecore/core-util.h>
54 : : #include <pulsecore/macro.h>
55 : : #include <pulsecore/atomic.h>
56 : :
57 : : #include "shm.h"
58 : :
59 : : #if defined(__linux__) && !defined(MADV_REMOVE)
60 : : #define MADV_REMOVE 9
61 : : #endif
62 : :
63 : : /* 1 GiB at max */
64 : : #define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024))
65 : :
66 : : #ifdef __linux__
67 : : /* On Linux we know that the shared memory blocks are files in
68 : : * /dev/shm. We can use that information to list all blocks and
69 : : * cleanup unused ones */
70 : : #define SHM_PATH "/dev/shm/"
71 : : #else
72 : : #undef SHM_PATH
73 : : #endif
74 : :
75 : : #define SHM_MARKER ((int) 0xbeefcafe)
76 : :
77 : : /* We now put this SHM marker at the end of each segment. It's
78 : : * optional, to not require a reboot when upgrading, though. Note that
79 : : * on multiarch systems 32bit and 64bit processes might access this
80 : : * region simultaneously. The header fields need to be independent
81 : : * from the process' word with */
82 : : struct shm_marker {
83 : : pa_atomic_t marker; /* 0xbeefcafe */
84 : : pa_atomic_t pid;
85 : : uint64_t _reserved1;
86 : : uint64_t _reserved2;
87 : : uint64_t _reserved3;
88 : : uint64_t _reserved4;
89 : : } PA_GCC_PACKED;
90 : :
91 : : #define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker))
92 : :
93 : : #ifdef HAVE_SHM_OPEN
94 : : static char *segment_name(char *fn, size_t l, unsigned id) {
95 : 80 : pa_snprintf(fn, l, "/pulse-shm-%u", id);
96 : : return fn;
97 : : }
98 : : #endif
99 : :
100 : 7 : int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) {
101 : : #ifdef HAVE_SHM_OPEN
102 : : char fn[32];
103 : 7 : int fd = -1;
104 : : #endif
105 : :
106 [ - + ]: 7 : pa_assert(m);
107 [ - + ]: 7 : pa_assert(size > 0);
108 [ - + ]: 7 : pa_assert(size <= MAX_SHM_SIZE);
109 [ - + ]: 7 : pa_assert(mode >= 0600);
110 : :
111 : : /* Each time we create a new SHM area, let's first drop all stale
112 : : * ones */
113 : 7 : pa_shm_cleanup();
114 : :
115 : : /* Round up to make it page aligned */
116 : 7 : size = PA_PAGE_ALIGN(size);
117 : :
118 [ + + ]: 7 : if (!shared) {
119 : 4 : m->id = 0;
120 : 4 : m->size = size;
121 : :
122 : : #ifdef MAP_ANONYMOUS
123 [ - + ]: 4 : if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) {
124 : 0 : pa_log("mmap() failed: %s", pa_cstrerror(errno));
125 : 0 : goto fail;
126 : : }
127 : : #elif defined(HAVE_POSIX_MEMALIGN)
128 : : {
129 : : int r;
130 : :
131 : : if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) {
132 : : pa_log("posix_memalign() failed: %s", pa_cstrerror(r));
133 : : goto fail;
134 : : }
135 : : }
136 : : #else
137 : : m->ptr = pa_xmalloc(m->size);
138 : : #endif
139 : :
140 : 4 : m->do_unlink = FALSE;
141 : :
142 : : } else {
143 : : #ifdef HAVE_SHM_OPEN
144 : : struct shm_marker *marker;
145 : :
146 : 3 : pa_random(&m->id, sizeof(m->id));
147 : 3 : segment_name(fn, sizeof(fn), m->id);
148 : :
149 [ - + ]: 3 : if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode & 0444)) < 0) {
150 : 0 : pa_log("shm_open() failed: %s", pa_cstrerror(errno));
151 : 0 : goto fail;
152 : : }
153 : :
154 : 3 : m->size = size + SHM_MARKER_SIZE;
155 : :
156 [ - + ]: 3 : if (ftruncate(fd, (off_t) m->size) < 0) {
157 : 0 : pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
158 : 0 : goto fail;
159 : : }
160 : :
161 : : #ifndef MAP_NORESERVE
162 : : #define MAP_NORESERVE 0
163 : : #endif
164 : :
165 [ - + ]: 3 : if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
166 : 0 : pa_log("mmap() failed: %s", pa_cstrerror(errno));
167 : 0 : goto fail;
168 : : }
169 : :
170 : : /* We store our PID at the end of the shm block, so that we
171 : : * can check for dead shm segments later */
172 : 3 : marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE);
173 : 3 : pa_atomic_store(&marker->pid, (int) getpid());
174 : 3 : pa_atomic_store(&marker->marker, SHM_MARKER);
175 : :
176 [ - + ]: 3 : pa_assert_se(pa_close(fd) == 0);
177 : 3 : m->do_unlink = TRUE;
178 : : #else
179 : : goto fail;
180 : : #endif
181 : : }
182 : :
183 : 7 : m->shared = shared;
184 : :
185 : 7 : return 0;
186 : :
187 : : fail:
188 : :
189 : : #ifdef HAVE_SHM_OPEN
190 [ # # ]: 0 : if (fd >= 0) {
191 : 0 : shm_unlink(fn);
192 : 7 : pa_close(fd);
193 : : }
194 : : #endif
195 : :
196 : : return -1;
197 : : }
198 : :
199 : 73 : void pa_shm_free(pa_shm *m) {
200 [ - + ]: 73 : pa_assert(m);
201 [ - + ]: 73 : pa_assert(m->ptr);
202 [ - + ]: 73 : pa_assert(m->size > 0);
203 : :
204 : : #ifdef MAP_FAILED
205 [ - + ]: 73 : pa_assert(m->ptr != MAP_FAILED);
206 : : #endif
207 : :
208 [ + + ]: 73 : if (!m->shared) {
209 : : #ifdef MAP_ANONYMOUS
210 [ - + ]: 3 : if (munmap(m->ptr, m->size) < 0)
211 : 0 : pa_log("munmap() failed: %s", pa_cstrerror(errno));
212 : : #elif defined(HAVE_POSIX_MEMALIGN)
213 : : free(m->ptr);
214 : : #else
215 : : pa_xfree(m->ptr);
216 : : #endif
217 : : } else {
218 : : #ifdef HAVE_SHM_OPEN
219 [ - + ]: 70 : if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
220 : 0 : pa_log("munmap() failed: %s", pa_cstrerror(errno));
221 : :
222 [ + + ]: 70 : if (m->do_unlink) {
223 : : char fn[32];
224 : :
225 : 3 : segment_name(fn, sizeof(fn), m->id);
226 : :
227 [ - + ]: 3 : if (shm_unlink(fn) < 0)
228 : 0 : pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
229 : : }
230 : : #else
231 : : /* We shouldn't be here without shm support */
232 : : pa_assert_not_reached();
233 : : #endif
234 : : }
235 : :
236 : : pa_zero(*m);
237 : 73 : }
238 : :
239 : 3 : void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
240 : : void *ptr;
241 : : size_t o;
242 : :
243 [ - + ]: 3 : pa_assert(m);
244 [ - + ]: 3 : pa_assert(m->ptr);
245 [ - + ]: 3 : pa_assert(m->size > 0);
246 [ - + ]: 3 : pa_assert(offset+size <= m->size);
247 : :
248 : : #ifdef MAP_FAILED
249 [ - + ]: 3 : pa_assert(m->ptr != MAP_FAILED);
250 : : #endif
251 : :
252 : : /* You're welcome to implement this as NOOP on systems that don't
253 : : * support it */
254 : :
255 : : /* Align the pointer up to multiples of the page size */
256 : 3 : ptr = (uint8_t*) m->ptr + offset;
257 : 6 : o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
258 : :
259 [ - + ]: 3 : if (o > 0) {
260 : 0 : size_t delta = PA_PAGE_SIZE - o;
261 : 0 : ptr = (uint8_t*) ptr + delta;
262 : 0 : size -= delta;
263 : : }
264 : :
265 : : /* Align the size down to multiples of page size */
266 : 3 : size = (size / PA_PAGE_SIZE) * PA_PAGE_SIZE;
267 : :
268 : : #ifdef MADV_REMOVE
269 [ - + ]: 3 : if (madvise(ptr, size, MADV_REMOVE) >= 0)
270 : 3 : return;
271 : : #endif
272 : :
273 : : #ifdef MADV_FREE
274 : : if (madvise(ptr, size, MADV_FREE) >= 0)
275 : : return;
276 : : #endif
277 : :
278 : : #ifdef MADV_DONTNEED
279 : 0 : madvise(ptr, size, MADV_DONTNEED);
280 : : #elif defined(POSIX_MADV_DONTNEED)
281 : : posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
282 : : #endif
283 : : }
284 : :
285 : : #ifdef HAVE_SHM_OPEN
286 : :
287 : 74 : int pa_shm_attach_ro(pa_shm *m, unsigned id) {
288 : : char fn[32];
289 : 74 : int fd = -1;
290 : : struct stat st;
291 : :
292 [ - + ]: 74 : pa_assert(m);
293 : :
294 : 74 : segment_name(fn, sizeof(fn), m->id = id);
295 : :
296 [ + + ]: 74 : if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) {
297 [ - + ]: 7 : if (errno != EACCES && errno != ENOENT)
298 : 0 : pa_log("shm_open() failed: %s", pa_cstrerror(errno));
299 : : goto fail;
300 : : }
301 : :
302 [ - + ]: 67 : if (fstat(fd, &st) < 0) {
303 : 0 : pa_log("fstat() failed: %s", pa_cstrerror(errno));
304 : 0 : goto fail;
305 : : }
306 : :
307 [ + - ][ + - ]: 67 : if (st.st_size <= 0 ||
308 [ - + ]: 67 : st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) ||
309 : 134 : PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
310 : 0 : pa_log("Invalid shared memory segment size");
311 : 0 : goto fail;
312 : : }
313 : :
314 : 67 : m->size = (size_t) st.st_size;
315 : :
316 [ - + ]: 67 : if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
317 : 0 : pa_log("mmap() failed: %s", pa_cstrerror(errno));
318 : 0 : goto fail;
319 : : }
320 : :
321 : 67 : m->do_unlink = FALSE;
322 : 67 : m->shared = TRUE;
323 : :
324 [ - + ]: 67 : pa_assert_se(pa_close(fd) == 0);
325 : :
326 : : return 0;
327 : :
328 : : fail:
329 [ - + ]: 7 : if (fd >= 0)
330 : 74 : pa_close(fd);
331 : :
332 : : return -1;
333 : : }
334 : :
335 : : #else /* HAVE_SHM_OPEN */
336 : :
337 : : int pa_shm_attach_ro(pa_shm *m, unsigned id) {
338 : : return -1;
339 : : }
340 : :
341 : : #endif /* HAVE_SHM_OPEN */
342 : :
343 : 7 : int pa_shm_cleanup(void) {
344 : :
345 : : #ifdef HAVE_SHM_OPEN
346 : : #ifdef SHM_PATH
347 : : DIR *d;
348 : : struct dirent *de;
349 : :
350 [ + - ]: 7 : if (!(d = opendir(SHM_PATH))) {
351 : 0 : pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
352 : 0 : return -1;
353 : : }
354 : :
355 [ + + ]: 87 : while ((de = readdir(d))) {
356 : : pa_shm seg;
357 : : unsigned id;
358 : : pid_t pid;
359 : : char fn[128];
360 : : struct shm_marker *m;
361 : :
362 [ + + ]: 80 : if (strncmp(de->d_name, "pulse-shm-", 10))
363 : 14 : continue;
364 : :
365 [ - + ]: 66 : if (pa_atou(de->d_name + 10, &id) < 0)
366 : 0 : continue;
367 : :
368 [ + + ]: 66 : if (pa_shm_attach_ro(&seg, id) < 0)
369 : 7 : continue;
370 : :
371 [ - + ]: 59 : if (seg.size < SHM_MARKER_SIZE) {
372 : 0 : pa_shm_free(&seg);
373 : 0 : continue;
374 : : }
375 : :
376 : 59 : m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE);
377 : :
378 [ - + ]: 59 : if (pa_atomic_load(&m->marker) != SHM_MARKER) {
379 : 0 : pa_shm_free(&seg);
380 : 0 : continue;
381 : : }
382 : :
383 [ - + ]: 59 : if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
384 : 0 : pa_shm_free(&seg);
385 : 0 : continue;
386 : : }
387 : :
388 [ - + ][ # # ]: 59 : if (kill(pid, 0) == 0 || errno != ESRCH) {
389 : 59 : pa_shm_free(&seg);
390 : 59 : continue;
391 : : }
392 : :
393 : 0 : pa_shm_free(&seg);
394 : :
395 : : /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
396 : 0 : segment_name(fn, sizeof(fn), id);
397 : :
398 [ # # ][ # # ]: 0 : if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
[ # # ]
399 : 80 : pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno));
400 : : }
401 : :
402 : 7 : closedir(d);
403 : : #endif /* SHM_PATH */
404 : : #endif /* HAVE_SHM_OPEN */
405 : :
406 : 7 : return 0;
407 : : }
|