/*** This file is part of PulseAudio. Copyright 2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #ifdef HAVE_PTHREAD #include #endif #include #include #include #include #include #include #include #include "lock-autospawn.h" /* So, why do we have this complex code here with threads and pipes * and stuff? For two reasons: POSIX file locks are per-process, not * per-file descriptor. That means that two contexts within the same * process that try to create the autospawn lock might end up assuming * they both managed to lock the file. And then, POSIX locking * operations are synchronous. If two contexts run from the same event * loop it must be made sure that they do not block each other, but * that the locking operation can happen asynchronously. */ #define AUTOSPAWN_LOCK "autospawn.lock" static pa_mutex *mutex; static unsigned n_ref = 0; static int lock_fd = -1; static pa_mutex *lock_fd_mutex = NULL; static pa_thread *thread = NULL; static int pipe_fd[2] = { -1, -1 }; static enum { STATE_IDLE, STATE_OWNING, STATE_TAKEN, STATE_FAILED } state = STATE_IDLE; static void destroy_mutex(void) PA_GCC_DESTRUCTOR; static int ref(void) { if (n_ref > 0) { pa_assert(pipe_fd[0] >= 0); pa_assert(pipe_fd[1] >= 0); pa_assert(lock_fd_mutex); n_ref++; return 0; } pa_assert(!lock_fd_mutex); pa_assert(state == STATE_IDLE); pa_assert(lock_fd < 0); pa_assert(!thread); pa_assert(pipe_fd[0] < 0); pa_assert(pipe_fd[1] < 0); if (pa_pipe_cloexec(pipe_fd) < 0) return -1; pa_make_fd_nonblock(pipe_fd[1]); pa_make_fd_nonblock(pipe_fd[0]); lock_fd_mutex = pa_mutex_new(false, false); n_ref = 1; return 0; } static void unref(bool after_fork) { pa_assert(n_ref > 0); pa_assert(pipe_fd[0] >= 0); pa_assert(pipe_fd[1] >= 0); pa_assert(lock_fd_mutex); n_ref--; if (n_ref > 0) return; /* Join threads only in the process the new thread was created in * to avoid undefined behaviour. * POSIX.1-2008 XSH 2.9.2 Thread IDs: "applications should only assume * that thread IDs are usable and unique within a single process." */ if (thread) { if (after_fork) pa_thread_free_nojoin(thread); else pa_thread_free(thread); thread = NULL; } pa_mutex_lock(lock_fd_mutex); pa_assert(state != STATE_TAKEN); if (state == STATE_OWNING) { pa_assert(lock_fd >= 0); if (after_fork) pa_close(lock_fd); else { char *lf; if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) pa_log_warn(_("Cannot access autospawn lock.")); pa_unlock_lockfile(lf, lock_fd); pa_xfree(lf); } } lock_fd = -1; state = STATE_IDLE; pa_mutex_unlock(lock_fd_mutex); pa_mutex_free(lock_fd_mutex); lock_fd_mutex = NULL; pa_close(pipe_fd[0]); pa_close(pipe_fd[1]); pipe_fd[0] = pipe_fd[1] = -1; } static void ping(void) { ssize_t s; pa_assert(pipe_fd[1] >= 0); for (;;) { char x = 'x'; if ((s = pa_write(pipe_fd[1], &x, 1, NULL)) == 1) break; pa_assert(s < 0); if (errno == EAGAIN) break; pa_assert(errno == EINTR); } } static void wait_for_ping(void) { ssize_t s; char x; struct pollfd pfd; int k; pa_assert(pipe_fd[0] >= 0); memset(&pfd, 0, sizeof(pfd)); pfd.fd = pipe_fd[0]; pfd.events = POLLIN; if ((k = pa_poll(&pfd, 1, -1)) != 1) { pa_assert(k < 0); pa_assert(errno == EINTR); } else if ((s = pa_read(pipe_fd[0], &x, 1, NULL)) != 1) { pa_assert(s < 0); pa_assert(errno == EAGAIN); } } static void empty_pipe(void) { char x[16]; ssize_t s; pa_assert(pipe_fd[0] >= 0); if ((s = pa_read(pipe_fd[0], &x, sizeof(x), NULL)) < 1) { pa_assert(s < 0); pa_assert(errno == EAGAIN); } } static void thread_func(void *u) { int fd; char *lf; #ifdef HAVE_PTHREAD sigset_t fullset; /* No signals in this thread please */ sigfillset(&fullset); pthread_sigmask(SIG_BLOCK, &fullset, NULL); #endif if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) { pa_log_warn(_("Cannot access autospawn lock.")); goto fail; } if ((fd = pa_lock_lockfile(lf)) < 0) goto fail; pa_mutex_lock(lock_fd_mutex); pa_assert(state == STATE_IDLE); lock_fd = fd; state = STATE_OWNING; pa_mutex_unlock(lock_fd_mutex); goto finish; fail: pa_mutex_lock(lock_fd_mutex); pa_assert(state == STATE_IDLE); state = STATE_FAILED; pa_mutex_unlock(lock_fd_mutex); finish: pa_xfree(lf); ping(); } static int start_thread(void) { if (!thread) if (!(thread = pa_thread_new("autospawn", thread_func, NULL))) return -1; return 0; } static void create_mutex(void) { PA_ONCE_BEGIN { mutex = pa_mutex_new(false, false); } PA_ONCE_END; } static void destroy_mutex(void) { if (mutex) pa_mutex_free(mutex); } int pa_autospawn_lock_init(void) { int ret = -1; create_mutex(); pa_mutex_lock(mutex); if (ref() < 0) ret = -1; else ret = pipe_fd[0]; pa_mutex_unlock(mutex); return ret; } int pa_autospawn_lock_acquire(bool block) { int ret = -1; create_mutex(); pa_mutex_lock(mutex); pa_assert(n_ref >= 1); pa_mutex_lock(lock_fd_mutex); for (;;) { empty_pipe(); if (state == STATE_OWNING) { state = STATE_TAKEN; ret = 1; break; } if (state == STATE_FAILED) { ret = -1; break; } if (state == STATE_IDLE) if (start_thread() < 0) break; if (!block) { ret = 0; break; } pa_mutex_unlock(lock_fd_mutex); pa_mutex_unlock(mutex); wait_for_ping(); pa_mutex_lock(mutex); pa_mutex_lock(lock_fd_mutex); } pa_mutex_unlock(lock_fd_mutex); pa_mutex_unlock(mutex); return ret; } void pa_autospawn_lock_release(void) { create_mutex(); pa_mutex_lock(mutex); pa_assert(n_ref >= 1); pa_assert(state == STATE_TAKEN); state = STATE_OWNING; ping(); pa_mutex_unlock(mutex); } void pa_autospawn_lock_done(bool after_fork) { create_mutex(); pa_mutex_lock(mutex); pa_assert(n_ref >= 1); unref(after_fork); pa_mutex_unlock(mutex); }