1/***
2  This file is part of PulseAudio.
3
4  Copyright 2008 Lennart Poettering
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation; either version 2.1 of the License,
9  or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public License
17  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <errno.h>
25#include <string.h>
26#include <signal.h>
27
28#ifdef HAVE_PTHREAD
29#include <pthread.h>
30#endif
31
32#include <pulse/gccmacro.h>
33#include <pulse/xmalloc.h>
34
35#include <pulsecore/i18n.h>
36#include <pulsecore/poll.h>
37#include <pulsecore/mutex.h>
38#include <pulsecore/thread.h>
39#include <pulsecore/core-util.h>
40
41#include "lock-autospawn.h"
42
43/* So, why do we have this complex code here with threads and pipes
44 * and stuff? For two reasons: POSIX file locks are per-process, not
45 * per-file descriptor. That means that two contexts within the same
46 * process that try to create the autospawn lock might end up assuming
47 * they both managed to lock the file. And then, POSIX locking
48 * operations are synchronous. If two contexts run from the same event
49 * loop it must be made sure that they do not block each other, but
50 * that the locking operation can happen asynchronously. */
51
52#define AUTOSPAWN_LOCK "autospawn.lock"
53
54static pa_mutex *mutex;
55
56static unsigned n_ref = 0;
57static int lock_fd = -1;
58static pa_mutex *lock_fd_mutex = NULL;
59static pa_thread *thread = NULL;
60static int pipe_fd[2] = { -1, -1 };
61
62static enum {
63    STATE_IDLE,
64    STATE_OWNING,
65    STATE_TAKEN,
66    STATE_FAILED
67} state = STATE_IDLE;
68
69static void destroy_mutex(void) PA_GCC_DESTRUCTOR;
70
71static int ref(void) {
72
73    if (n_ref > 0) {
74
75        pa_assert(pipe_fd[0] >= 0);
76        pa_assert(pipe_fd[1] >= 0);
77        pa_assert(lock_fd_mutex);
78
79        n_ref++;
80
81        return 0;
82    }
83
84    pa_assert(!lock_fd_mutex);
85    pa_assert(state == STATE_IDLE);
86    pa_assert(lock_fd < 0);
87    pa_assert(!thread);
88    pa_assert(pipe_fd[0] < 0);
89    pa_assert(pipe_fd[1] < 0);
90
91    if (pa_pipe_cloexec(pipe_fd) < 0)
92        return -1;
93
94    pa_make_fd_nonblock(pipe_fd[1]);
95    pa_make_fd_nonblock(pipe_fd[0]);
96
97    lock_fd_mutex = pa_mutex_new(false, false);
98
99    n_ref = 1;
100    return 0;
101}
102
103static void unref(bool after_fork) {
104
105    pa_assert(n_ref > 0);
106    pa_assert(pipe_fd[0] >= 0);
107    pa_assert(pipe_fd[1] >= 0);
108    pa_assert(lock_fd_mutex);
109
110    n_ref--;
111
112    if (n_ref > 0)
113        return;
114
115    /* Join threads only in the process the new thread was created in
116     * to avoid undefined behaviour.
117     * POSIX.1-2008 XSH 2.9.2 Thread IDs: "applications should only assume
118     * that thread IDs are usable and unique within a single process." */
119    if (thread) {
120        if (after_fork)
121            pa_thread_free_nojoin(thread);
122	else
123            pa_thread_free(thread);
124        thread = NULL;
125    }
126
127    pa_mutex_lock(lock_fd_mutex);
128
129    pa_assert(state != STATE_TAKEN);
130
131    if (state == STATE_OWNING) {
132
133        pa_assert(lock_fd >= 0);
134
135        if (after_fork)
136            pa_close(lock_fd);
137        else {
138            char *lf;
139
140            if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK)))
141                pa_log_warn(_("Cannot access autospawn lock."));
142
143            pa_unlock_lockfile(lf, lock_fd);
144            pa_xfree(lf);
145        }
146    }
147
148    lock_fd = -1;
149    state = STATE_IDLE;
150
151    pa_mutex_unlock(lock_fd_mutex);
152
153    pa_mutex_free(lock_fd_mutex);
154    lock_fd_mutex = NULL;
155
156    pa_close(pipe_fd[0]);
157    pa_close(pipe_fd[1]);
158    pipe_fd[0] = pipe_fd[1] = -1;
159}
160
161static void ping(void) {
162    ssize_t s;
163
164    pa_assert(pipe_fd[1] >= 0);
165
166    for (;;) {
167        char x = 'x';
168
169        if ((s = pa_write(pipe_fd[1], &x, 1, NULL)) == 1)
170            break;
171
172        pa_assert(s < 0);
173
174        if (errno == EAGAIN)
175            break;
176
177        pa_assert(errno == EINTR);
178    }
179}
180
181static void wait_for_ping(void) {
182    ssize_t s;
183    char x;
184    struct pollfd pfd;
185    int k;
186
187    pa_assert(pipe_fd[0] >= 0);
188
189    memset(&pfd, 0, sizeof(pfd));
190    pfd.fd = pipe_fd[0];
191    pfd.events = POLLIN;
192
193    if ((k = pa_poll(&pfd, 1, -1)) != 1) {
194        pa_assert(k < 0);
195        pa_assert(errno == EINTR);
196    } else if ((s = pa_read(pipe_fd[0], &x, 1, NULL)) != 1) {
197        pa_assert(s < 0);
198        pa_assert(errno == EAGAIN);
199    }
200}
201
202static void empty_pipe(void) {
203    char x[16];
204    ssize_t s;
205
206    pa_assert(pipe_fd[0] >= 0);
207
208    if ((s = pa_read(pipe_fd[0], &x, sizeof(x), NULL)) < 1) {
209        pa_assert(s < 0);
210        pa_assert(errno == EAGAIN);
211    }
212}
213
214static void thread_func(void *u) {
215    int fd;
216    char *lf;
217
218#ifdef HAVE_PTHREAD
219    sigset_t fullset;
220
221    /* No signals in this thread please */
222    sigfillset(&fullset);
223    pthread_sigmask(SIG_BLOCK, &fullset, NULL);
224#endif
225
226    if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) {
227        pa_log_warn(_("Cannot access autospawn lock."));
228        goto fail;
229    }
230
231    if ((fd = pa_lock_lockfile(lf)) < 0)
232        goto fail;
233
234    pa_mutex_lock(lock_fd_mutex);
235    pa_assert(state == STATE_IDLE);
236    lock_fd = fd;
237    state = STATE_OWNING;
238    pa_mutex_unlock(lock_fd_mutex);
239
240    goto finish;
241
242fail:
243    pa_mutex_lock(lock_fd_mutex);
244    pa_assert(state == STATE_IDLE);
245    state = STATE_FAILED;
246    pa_mutex_unlock(lock_fd_mutex);
247
248finish:
249    pa_xfree(lf);
250
251    ping();
252}
253
254static int start_thread(void) {
255
256    if (!thread)
257        if (!(thread = pa_thread_new("autospawn", thread_func, NULL)))
258            return -1;
259
260    return 0;
261}
262
263static void create_mutex(void) {
264    PA_ONCE_BEGIN {
265        mutex = pa_mutex_new(false, false);
266    } PA_ONCE_END;
267}
268
269static void destroy_mutex(void) {
270    if (mutex)
271        pa_mutex_free(mutex);
272}
273
274int pa_autospawn_lock_init(void) {
275    int ret = -1;
276
277    create_mutex();
278    pa_mutex_lock(mutex);
279
280    if (ref() < 0)
281        ret = -1;
282    else
283        ret = pipe_fd[0];
284
285    pa_mutex_unlock(mutex);
286
287    return ret;
288}
289
290int pa_autospawn_lock_acquire(bool block) {
291    int ret = -1;
292
293    create_mutex();
294    pa_mutex_lock(mutex);
295    pa_assert(n_ref >= 1);
296
297    pa_mutex_lock(lock_fd_mutex);
298
299    for (;;) {
300
301        empty_pipe();
302
303        if (state == STATE_OWNING) {
304            state = STATE_TAKEN;
305            ret = 1;
306            break;
307        }
308
309        if (state == STATE_FAILED) {
310            ret = -1;
311            break;
312        }
313
314        if (state == STATE_IDLE)
315            if (start_thread() < 0)
316                break;
317
318        if (!block) {
319            ret = 0;
320            break;
321        }
322
323        pa_mutex_unlock(lock_fd_mutex);
324        pa_mutex_unlock(mutex);
325
326        wait_for_ping();
327
328        pa_mutex_lock(mutex);
329        pa_mutex_lock(lock_fd_mutex);
330    }
331
332    pa_mutex_unlock(lock_fd_mutex);
333
334    pa_mutex_unlock(mutex);
335
336    return ret;
337}
338
339void pa_autospawn_lock_release(void) {
340
341    create_mutex();
342    pa_mutex_lock(mutex);
343    pa_assert(n_ref >= 1);
344
345    pa_assert(state == STATE_TAKEN);
346    state = STATE_OWNING;
347
348    ping();
349
350    pa_mutex_unlock(mutex);
351}
352
353void pa_autospawn_lock_done(bool after_fork) {
354
355    create_mutex();
356    pa_mutex_lock(mutex);
357    pa_assert(n_ref >= 1);
358
359    unref(after_fork);
360
361    pa_mutex_unlock(mutex);
362}
363