pulseaudio/src/pulsecore/lock-autospawn.c
Ondrej Holecek 5effc83479 update FSF addresses to FSF web page
FSF addresses used in PA sources are no longer valid and rpmlint
generates numerous warnings during packaging because of this.
This patch changes all FSF addresses to FSF web page according to
the GPL how-to: https://www.gnu.org/licenses/gpl-howto.en.html

Done automatically by sed-ing through sources.
2015-01-14 22:20:40 +02:00

362 lines
7.3 KiB
C

/***
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 <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <string.h>
#include <signal.h>
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#include <pulse/gccmacro.h>
#include <pulse/xmalloc.h>
#include <pulsecore/i18n.h>
#include <pulsecore/poll.h>
#include <pulsecore/mutex.h>
#include <pulsecore/thread.h>
#include <pulsecore/core-util.h>
#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);
}