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