]> code.delx.au - pulseaudio/blob - src/pulsecore/shm.c
remap: Change remapping function argument type from void to int16_t / float as approp...
[pulseaudio] / src / pulsecore / shm.c
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 #define SHM_ID_LEN 10
72 #elif defined(__sun)
73 #define SHM_PATH "/tmp"
74 #define SHM_ID_LEN 15
75 #else
76 #undef SHM_PATH
77 #undef SHM_ID_LEN
78 #endif
79
80 #define SHM_MARKER ((int) 0xbeefcafe)
81
82 /* We now put this SHM marker at the end of each segment. It's
83 * optional, to not require a reboot when upgrading, though. Note that
84 * on multiarch systems 32bit and 64bit processes might access this
85 * region simultaneously. The header fields need to be independent
86 * from the process' word with */
87 struct shm_marker {
88 pa_atomic_t marker; /* 0xbeefcafe */
89 pa_atomic_t pid;
90 uint64_t _reserved1;
91 uint64_t _reserved2;
92 uint64_t _reserved3;
93 uint64_t _reserved4;
94 } PA_GCC_PACKED;
95
96 #define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker))
97
98 #ifdef HAVE_SHM_OPEN
99 static char *segment_name(char *fn, size_t l, unsigned id) {
100 pa_snprintf(fn, l, "/pulse-shm-%u", id);
101 return fn;
102 }
103 #endif
104
105 int pa_shm_create_rw(pa_shm *m, size_t size, bool shared, mode_t mode) {
106 #ifdef HAVE_SHM_OPEN
107 char fn[32];
108 int fd = -1;
109 #endif
110
111 pa_assert(m);
112 pa_assert(size > 0);
113 pa_assert(size <= MAX_SHM_SIZE);
114 pa_assert(!(mode & ~0777));
115 pa_assert(mode >= 0600);
116
117 /* Each time we create a new SHM area, let's first drop all stale
118 * ones */
119 pa_shm_cleanup();
120
121 /* Round up to make it page aligned */
122 size = PA_PAGE_ALIGN(size);
123
124 if (!shared) {
125 m->id = 0;
126 m->size = size;
127
128 #ifdef MAP_ANONYMOUS
129 if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) {
130 pa_log("mmap() failed: %s", pa_cstrerror(errno));
131 goto fail;
132 }
133 #elif defined(HAVE_POSIX_MEMALIGN)
134 {
135 int r;
136
137 if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) {
138 pa_log("posix_memalign() failed: %s", pa_cstrerror(r));
139 goto fail;
140 }
141 }
142 #else
143 m->ptr = pa_xmalloc(m->size);
144 #endif
145
146 m->do_unlink = false;
147
148 } else {
149 #ifdef HAVE_SHM_OPEN
150 struct shm_marker *marker;
151
152 pa_random(&m->id, sizeof(m->id));
153 segment_name(fn, sizeof(fn), m->id);
154
155 if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) {
156 pa_log("shm_open() failed: %s", pa_cstrerror(errno));
157 goto fail;
158 }
159
160 m->size = size + SHM_MARKER_SIZE;
161
162 if (ftruncate(fd, (off_t) m->size) < 0) {
163 pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
164 goto fail;
165 }
166
167 #ifndef MAP_NORESERVE
168 #define MAP_NORESERVE 0
169 #endif
170
171 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
172 pa_log("mmap() failed: %s", pa_cstrerror(errno));
173 goto fail;
174 }
175
176 /* We store our PID at the end of the shm block, so that we
177 * can check for dead shm segments later */
178 marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE);
179 pa_atomic_store(&marker->pid, (int) getpid());
180 pa_atomic_store(&marker->marker, SHM_MARKER);
181
182 pa_assert_se(pa_close(fd) == 0);
183 m->do_unlink = true;
184 #else
185 goto fail;
186 #endif
187 }
188
189 m->shared = shared;
190
191 return 0;
192
193 fail:
194
195 #ifdef HAVE_SHM_OPEN
196 if (fd >= 0) {
197 shm_unlink(fn);
198 pa_close(fd);
199 }
200 #endif
201
202 return -1;
203 }
204
205 void pa_shm_free(pa_shm *m) {
206 pa_assert(m);
207 pa_assert(m->ptr);
208 pa_assert(m->size > 0);
209
210 #ifdef MAP_FAILED
211 pa_assert(m->ptr != MAP_FAILED);
212 #endif
213
214 if (!m->shared) {
215 #ifdef MAP_ANONYMOUS
216 if (munmap(m->ptr, m->size) < 0)
217 pa_log("munmap() failed: %s", pa_cstrerror(errno));
218 #elif defined(HAVE_POSIX_MEMALIGN)
219 free(m->ptr);
220 #else
221 pa_xfree(m->ptr);
222 #endif
223 } else {
224 #ifdef HAVE_SHM_OPEN
225 if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
226 pa_log("munmap() failed: %s", pa_cstrerror(errno));
227
228 if (m->do_unlink) {
229 char fn[32];
230
231 segment_name(fn, sizeof(fn), m->id);
232
233 if (shm_unlink(fn) < 0)
234 pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
235 }
236 #else
237 /* We shouldn't be here without shm support */
238 pa_assert_not_reached();
239 #endif
240 }
241
242 pa_zero(*m);
243 }
244
245 void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
246 void *ptr;
247 size_t o;
248
249 pa_assert(m);
250 pa_assert(m->ptr);
251 pa_assert(m->size > 0);
252 pa_assert(offset+size <= m->size);
253
254 #ifdef MAP_FAILED
255 pa_assert(m->ptr != MAP_FAILED);
256 #endif
257
258 /* You're welcome to implement this as NOOP on systems that don't
259 * support it */
260
261 /* Align the pointer up to multiples of the page size */
262 ptr = (uint8_t*) m->ptr + offset;
263 o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
264
265 if (o > 0) {
266 size_t delta = PA_PAGE_SIZE - o;
267 ptr = (uint8_t*) ptr + delta;
268 size -= delta;
269 }
270
271 /* Align the size down to multiples of page size */
272 size = (size / PA_PAGE_SIZE) * PA_PAGE_SIZE;
273
274 #ifdef MADV_REMOVE
275 if (madvise(ptr, size, MADV_REMOVE) >= 0)
276 return;
277 #endif
278
279 #ifdef MADV_FREE
280 if (madvise(ptr, size, MADV_FREE) >= 0)
281 return;
282 #endif
283
284 #ifdef MADV_DONTNEED
285 madvise(ptr, size, MADV_DONTNEED);
286 #elif defined(POSIX_MADV_DONTNEED)
287 posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
288 #endif
289 }
290
291 #ifdef HAVE_SHM_OPEN
292
293 int pa_shm_attach_ro(pa_shm *m, unsigned id) {
294 char fn[32];
295 int fd = -1;
296 struct stat st;
297
298 pa_assert(m);
299
300 segment_name(fn, sizeof(fn), m->id = id);
301
302 if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) {
303 if (errno != EACCES && errno != ENOENT)
304 pa_log("shm_open() failed: %s", pa_cstrerror(errno));
305 goto fail;
306 }
307
308 if (fstat(fd, &st) < 0) {
309 pa_log("fstat() failed: %s", pa_cstrerror(errno));
310 goto fail;
311 }
312
313 if (st.st_size <= 0 ||
314 st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) ||
315 PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
316 pa_log("Invalid shared memory segment size");
317 goto fail;
318 }
319
320 m->size = (size_t) st.st_size;
321
322 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
323 pa_log("mmap() failed: %s", pa_cstrerror(errno));
324 goto fail;
325 }
326
327 m->do_unlink = false;
328 m->shared = true;
329
330 pa_assert_se(pa_close(fd) == 0);
331
332 return 0;
333
334 fail:
335 if (fd >= 0)
336 pa_close(fd);
337
338 return -1;
339 }
340
341 #else /* HAVE_SHM_OPEN */
342
343 int pa_shm_attach_ro(pa_shm *m, unsigned id) {
344 return -1;
345 }
346
347 #endif /* HAVE_SHM_OPEN */
348
349 int pa_shm_cleanup(void) {
350
351 #ifdef HAVE_SHM_OPEN
352 #ifdef SHM_PATH
353 DIR *d;
354 struct dirent *de;
355
356 if (!(d = opendir(SHM_PATH))) {
357 pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
358 return -1;
359 }
360
361 while ((de = readdir(d))) {
362 pa_shm seg;
363 unsigned id;
364 pid_t pid;
365 char fn[128];
366 struct shm_marker *m;
367
368 #if defined(__sun)
369 if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN))
370 #else
371 if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN))
372 #endif
373 continue;
374
375 if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0)
376 continue;
377
378 if (pa_shm_attach_ro(&seg, id) < 0)
379 continue;
380
381 if (seg.size < SHM_MARKER_SIZE) {
382 pa_shm_free(&seg);
383 continue;
384 }
385
386 m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE);
387
388 if (pa_atomic_load(&m->marker) != SHM_MARKER) {
389 pa_shm_free(&seg);
390 continue;
391 }
392
393 if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
394 pa_shm_free(&seg);
395 continue;
396 }
397
398 if (kill(pid, 0) == 0 || errno != ESRCH) {
399 pa_shm_free(&seg);
400 continue;
401 }
402
403 pa_shm_free(&seg);
404
405 /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
406 segment_name(fn, sizeof(fn), id);
407
408 if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
409 pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno));
410 }
411
412 closedir(d);
413 #endif /* SHM_PATH */
414 #endif /* HAVE_SHM_OPEN */
415
416 return 0;
417 }