pulseaudio/src/pulsecore/core-util.c

3213 lines
67 KiB
C
Raw Normal View History

/***
This file is part of PulseAudio.
Copyright 2004-2006 Lennart Poettering
Copyright 2004 Joe Marcus Clarke
Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif
#if defined(HAVE_REGEX_H)
#include <regex.h>
#elif defined(HAVE_PCREPOSIX_H)
#include <pcreposix.h>
#endif
#ifdef HAVE_STRTOF_L
#include <locale.h>
#endif
#ifdef HAVE_SCHED_H
#include <sched.h>
#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK)
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#ifndef ENOTSUP
#define ENOTSUP 135
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_LIBSAMPLERATE
#include <samplerate.h>
#endif
#ifdef __APPLE__
#include <xlocale.h>
#endif
#ifdef HAVE_DBUS
#include "rtkit.h"
#endif
#ifdef __linux__
#include <sys/personality.h>
#endif
#include <pulse/xmalloc.h>
#include <pulse/util.h>
#include <pulse/utf8.h>
#include <pulsecore/core-error.h>
#include <pulsecore/socket.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/thread.h>
#include <pulsecore/strbuf.h>
#include <pulsecore/usergroup.h>
#include <pulsecore/strlist.h>
#include <pulsecore/cpu-x86.h>
#include <pulsecore/pipe.h>
#include "core-util.h"
/* Not all platforms have this */
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#define NEWLINE "\r\n"
#define WHITESPACE "\n\r \t"
static pa_strlist *recorded_env = NULL;
#ifdef OS_IS_WIN32
/* Returns the directory of the current DLL, with '/bin/' removed if it is the last component */
char *pa_win32_get_toplevel(HANDLE handle) {
static char *toplevel = NULL;
if (!toplevel) {
char library_path[MAX_PATH];
char *p;
if (!GetModuleFileName(handle, library_path, MAX_PATH))
return NULL;
toplevel = pa_xstrdup(library_path);
p = strrchr(toplevel, PA_PATH_SEP_CHAR);
if (p)
*p = '\0';
p = strrchr(toplevel, PA_PATH_SEP_CHAR);
if (p && (strcmp(p + 1, "bin") == 0))
*p = '\0';
}
return toplevel;
}
#endif
/** Make a file descriptor nonblock. Doesn't do any error checking */
void pa_make_fd_nonblock(int fd) {
#ifdef O_NONBLOCK
int v;
pa_assert(fd >= 0);
pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0);
if (!(v & O_NONBLOCK))
pa_assert_se(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0);
#elif defined(OS_IS_WIN32)
u_long arg = 1;
if (ioctlsocket(fd, FIONBIO, &arg) < 0) {
pa_assert_se(WSAGetLastError() == WSAENOTSOCK);
pa_log_warn("Only sockets can be made non-blocking!");
}
#else
pa_log_warn("Non-blocking I/O not supported.!");
#endif
}
/* Set the FD_CLOEXEC flag for a fd */
void pa_make_fd_cloexec(int fd) {
#ifdef FD_CLOEXEC
int v;
pa_assert(fd >= 0);
pa_assert_se((v = fcntl(fd, F_GETFD, 0)) >= 0);
if (!(v & FD_CLOEXEC))
pa_assert_se(fcntl(fd, F_SETFD, v|FD_CLOEXEC) >= 0);
#endif
}
/** Creates a directory securely */
int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) {
struct stat st;
int r, saved_errno, fd;
pa_assert(dir);
#ifdef OS_IS_WIN32
r = mkdir(dir);
#else
{
mode_t u;
u = umask((~m) & 0777);
r = mkdir(dir, m);
umask(u);
}
#endif
if (r < 0 && errno != EEXIST)
return -1;
#if defined(HAVE_FSTAT) && !defined(OS_IS_WIN32)
if ((fd = open(dir,
#ifdef O_CLOEXEC
O_CLOEXEC|
#endif
#ifdef O_NOCTTY
O_NOCTTY|
#endif
#ifdef O_NOFOLLOW
O_NOFOLLOW|
#endif
O_RDONLY)) < 0)
goto fail;
if (fstat(fd, &st) < 0) {
pa_assert_se(pa_close(fd) >= 0);
goto fail;
}
if (!S_ISDIR(st.st_mode)) {
pa_assert_se(pa_close(fd) >= 0);
errno = EEXIST;
goto fail;
}
#ifdef HAVE_FCHOWN
if (uid == (uid_t) -1)
uid = getuid();
if (gid == (gid_t) -1)
gid = getgid();
if (fchown(fd, uid, gid) < 0)
goto fail;
#endif
#ifdef HAVE_FCHMOD
(void) fchmod(fd, m);
#endif
pa_assert_se(pa_close(fd) >= 0);
#endif
#ifdef HAVE_LSTAT
if (lstat(dir, &st) < 0)
#else
if (stat(dir, &st) < 0)
#endif
goto fail;
#ifndef OS_IS_WIN32
if (!S_ISDIR(st.st_mode) ||
(st.st_uid != uid) ||
(st.st_gid != gid) ||
((st.st_mode & 0777) != m)) {
errno = EACCES;
goto fail;
}
#else
pa_log_warn("Secure directory creation not supported on Win32.");
#endif
return 0;
fail:
saved_errno = errno;
rmdir(dir);
errno = saved_errno;
return -1;
}
/* Return a newly allocated sting containing the parent directory of the specified file */
char *pa_parent_dir(const char *fn) {
char *slash, *dir = pa_xstrdup(fn);
if ((slash = (char*) pa_path_get_filename(dir)) == dir) {
pa_xfree(dir);
errno = ENOENT;
return NULL;
}
*(slash-1) = 0;
return dir;
}
/* Creates a the parent directory of the specified path securely */
int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid) {
int ret = -1;
char *dir;
if (!(dir = pa_parent_dir(fn)))
goto finish;
if (pa_make_secure_dir(dir, m, uid, gid) < 0)
goto finish;
ret = 0;
finish:
pa_xfree(dir);
return ret;
}
/** Platform independent read function. Necessary since not all
* systems treat all file descriptors equal. If type is
* non-NULL it is used to cache the type of the fd. This is
* useful for making sure that only a single syscall is executed per
* function call. The variable pointed to should be initialized to 0
* by the caller. */
ssize_t pa_read(int fd, void *buf, size_t count, int *type) {
#ifdef OS_IS_WIN32
if (!type || *type == 0) {
ssize_t r;
if ((r = recv(fd, buf, count, 0)) >= 0)
return r;
if (WSAGetLastError() != WSAENOTSOCK) {
errno = WSAGetLastError();
return r;
}
if (type)
*type = 1;
}
#endif
for (;;) {
ssize_t r;
if ((r = read(fd, buf, count)) < 0)
if (errno == EINTR)
continue;
return r;
}
}
/** Similar to pa_read(), but handles writes */
ssize_t pa_write(int fd, const void *buf, size_t count, int *type) {
if (!type || *type == 0) {
ssize_t r;
for (;;) {
if ((r = send(fd, buf, count, MSG_NOSIGNAL)) < 0) {
if (errno == EINTR)
continue;
break;
}
return r;
}
#ifdef OS_IS_WIN32
if (WSAGetLastError() != WSAENOTSOCK) {
errno = WSAGetLastError();
return r;
}
#else
if (errno != ENOTSOCK)
return r;
#endif
if (type)
*type = 1;
}
for (;;) {
ssize_t r;
if ((r = write(fd, buf, count)) < 0)
if (errno == EINTR)
continue;
return r;
}
}
/** Calls read() in a loop. Makes sure that as much as 'size' bytes,
2009-04-01 12:35:44 +02:00
* unless EOF is reached or an error occurred */
ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) {
ssize_t ret = 0;
int _type;
pa_assert(fd >= 0);
pa_assert(data);
pa_assert(size);
if (!type) {
_type = 0;
type = &_type;
}
while (size > 0) {
ssize_t r;
if ((r = pa_read(fd, data, size, type)) < 0)
return r;
if (r == 0)
break;
ret += r;
data = (uint8_t*) data + r;
size -= (size_t) r;
}
return ret;
}
/** Similar to pa_loop_read(), but wraps write() */
ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) {
ssize_t ret = 0;
int _type;
pa_assert(fd >= 0);
pa_assert(data);
pa_assert(size);
if (!type) {
_type = 0;
type = &_type;
}
while (size > 0) {
ssize_t r;
if ((r = pa_write(fd, data, size, type)) < 0)
return r;
if (r == 0)
break;
ret += r;
data = (const uint8_t*) data + r;
size -= (size_t) r;
}
return ret;
}
/** Platform independent read function. Necessary since not all
* systems treat all file descriptors equal. */
int pa_close(int fd) {
#ifdef OS_IS_WIN32
int ret;
if ((ret = closesocket(fd)) == 0)
return 0;
if (WSAGetLastError() != WSAENOTSOCK) {
errno = WSAGetLastError();
return ret;
}
#endif
2008-08-26 15:44:55 +02:00
for (;;) {
int r;
if ((r = close(fd)) < 0)
if (errno == EINTR)
continue;
2008-08-26 15:44:55 +02:00
return r;
2008-08-26 15:44:55 +02:00
}
}
/* Print a warning messages in case that the given signal is not
* blocked or trapped */
void pa_check_signal_is_blocked(int sig) {
#ifdef HAVE_SIGACTION
struct sigaction sa;
sigset_t set;
/* If POSIX threads are supported use thread-aware
* pthread_sigmask() function, to check if the signal is
* blocked. Otherwise fall back to sigprocmask() */
#ifdef HAVE_PTHREAD
if (pthread_sigmask(SIG_SETMASK, NULL, &set) < 0) {
#endif
if (sigprocmask(SIG_SETMASK, NULL, &set) < 0) {
pa_log("sigprocmask(): %s", pa_cstrerror(errno));
return;
}
#ifdef HAVE_PTHREAD
}
#endif
if (sigismember(&set, sig))
return;
/* Check whether the signal is trapped */
if (sigaction(sig, NULL, &sa) < 0) {
pa_log("sigaction(): %s", pa_cstrerror(errno));
return;
}
if (sa.sa_handler != SIG_DFL)
return;
pa_log_warn("%s is not trapped. This might cause malfunction!", pa_sig2str(sig));
#else /* HAVE_SIGACTION */
pa_log_warn("%s might not be trapped. This might cause malfunction!", pa_sig2str(sig));
#endif
}
/* The following function is based on an example from the GNU libc
* documentation. This function is similar to GNU's asprintf(). */
char *pa_sprintf_malloc(const char *format, ...) {
2011-03-12 19:45:02 +01:00
size_t size = 100;
char *c = NULL;
pa_assert(format);
for(;;) {
int r;
va_list ap;
c = pa_xrealloc(c, size);
va_start(ap, format);
r = vsnprintf(c, size, format, ap);
va_end(ap);
c[size-1] = 0;
if (r > -1 && (size_t) r < size)
return c;
if (r > -1) /* glibc 2.1 */
size = (size_t) r+1;
else /* glibc 2.0 */
size *= 2;
}
}
/* Same as the previous function, but use a va_list instead of an
* ellipsis */
char *pa_vsprintf_malloc(const char *format, va_list ap) {
2011-03-12 19:45:02 +01:00
size_t size = 100;
char *c = NULL;
pa_assert(format);
for(;;) {
int r;
va_list aq;
c = pa_xrealloc(c, size);
va_copy(aq, ap);
r = vsnprintf(c, size, format, aq);
va_end(aq);
c[size-1] = 0;
if (r > -1 && (size_t) r < size)
return c;
if (r > -1) /* glibc 2.1 */
size = (size_t) r+1;
else /* glibc 2.0 */
size *= 2;
}
}
/* Similar to OpenBSD's strlcpy() function */
char *pa_strlcpy(char *b, const char *s, size_t l) {
size_t k;
pa_assert(b);
pa_assert(s);
pa_assert(l > 0);
k = strlen(s);
if (k > l-1)
k = l-1;
memcpy(b, s, k);
b[k] = 0;
return b;
}
#ifdef _POSIX_PRIORITY_SCHEDULING
static int set_scheduler(int rtprio) {
#ifdef HAVE_SCHED_H
struct sched_param sp;
#ifdef HAVE_DBUS
int r;
DBusError error;
DBusConnection *bus;
dbus_error_init(&error);
#endif
pa_zero(sp);
sp.sched_priority = rtprio;
#ifdef SCHED_RESET_ON_FORK
if (pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp) == 0) {
pa_log_debug("SCHED_RR|SCHED_RESET_ON_FORK worked.");
return 0;
}
#endif
if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0) {
pa_log_debug("SCHED_RR worked.");
return 0;
}
#endif /* HAVE_SCHED_H */
#ifdef HAVE_DBUS
/* Try to talk to RealtimeKit */
if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
pa_log("Failed to connect to system bus: %s\n", error.message);
dbus_error_free(&error);
errno = -EIO;
return -1;
}
/* We need to disable exit on disconnect because otherwise
* dbus_shutdown will kill us. See
* https://bugs.freedesktop.org/show_bug.cgi?id=16924 */
dbus_connection_set_exit_on_disconnect(bus, FALSE);
r = rtkit_make_realtime(bus, 0, rtprio);
dbus_connection_unref(bus);
if (r >= 0) {
pa_log_debug("RealtimeKit worked.");
return 0;
}
errno = -r;
#else
errno = 0;
#endif
return -1;
}
#endif
/* Make the current thread a realtime thread, and acquire the highest
* rtprio we can get that is less or equal the specified parameter. If
* the thread is already realtime, don't do anything. */
int pa_make_realtime(int rtprio) {
#ifdef _POSIX_PRIORITY_SCHEDULING
int p;
if (set_scheduler(rtprio) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i.", rtprio);
return 0;
}
for (p = rtprio-1; p >= 1; p--)
if (set_scheduler(p) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio);
return 0;
}
#elif defined(OS_IS_WIN32)
/* Windows only allows realtime scheduling to be set on a per process basis.
* Therefore, instead of making the thread realtime, just give it the highest non-realtime priority. */
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) {
pa_log_info("Successfully enabled THREAD_PRIORITY_TIME_CRITICAL scheduling for thread.");
return 0;
}
pa_log_warn("SetThreadPriority() failed: 0x%08X", GetLastError());
errno = EPERM;
#else
errno = ENOTSUP;
#endif
pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno));
return -1;
}
#ifdef HAVE_SYS_RESOURCE_H
static int set_nice(int nice_level) {
#ifdef HAVE_DBUS
DBusError error;
DBusConnection *bus;
int r;
dbus_error_init(&error);
#endif
#ifdef HAVE_SYS_RESOURCE_H
if (setpriority(PRIO_PROCESS, 0, nice_level) >= 0) {
pa_log_debug("setpriority() worked.");
return 0;
}
#endif
#ifdef HAVE_DBUS
/* Try to talk to RealtimeKit */
if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
pa_log("Failed to connect to system bus: %s\n", error.message);
dbus_error_free(&error);
errno = -EIO;
return -1;
}
/* We need to disable exit on disconnect because otherwise
* dbus_shutdown will kill us. See
* https://bugs.freedesktop.org/show_bug.cgi?id=16924 */
dbus_connection_set_exit_on_disconnect(bus, FALSE);
r = rtkit_make_high_priority(bus, 0, nice_level);
dbus_connection_unref(bus);
if (r >= 0) {
pa_log_debug("RealtimeKit worked.");
return 0;
}
errno = -r;
#endif
return -1;
}
#endif
/* Raise the priority of the current process as much as possible that
* is <= the specified nice level..*/
int pa_raise_priority(int nice_level) {
#ifdef HAVE_SYS_RESOURCE_H
int n;
if (set_nice(nice_level) >= 0) {
pa_log_info("Successfully gained nice level %i.", nice_level);
return 0;
}
for (n = nice_level+1; n < 0; n++)
if (set_nice(n) >= 0) {
pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level);
return 0;
}
pa_log_info("Failed to acquire high-priority scheduling: %s", pa_cstrerror(errno));
return -1;
#endif
#ifdef OS_IS_WIN32
if (nice_level < 0) {
if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError());
errno = EPERM;
return -1;
}
pa_log_info("Successfully gained high priority class.");
}
#endif
return 0;
}
/* Reset the priority to normal, inverting the changes made by
* pa_raise_priority() and pa_make_realtime()*/
void pa_reset_priority(void) {
#ifdef HAVE_SYS_RESOURCE_H
struct sched_param sp;
setpriority(PRIO_PROCESS, 0, 0);
pa_zero(sp);
pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp);
#endif
#ifdef OS_IS_WIN32
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
#endif
}
2009-02-18 21:57:16 +01:00
int pa_match(const char *expr, const char *v) {
int k;
regex_t re;
2008-08-09 16:12:50 +02:00
int r;
if (regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) {
errno = EINVAL;
return -1;
}
if ((k = regexec(&re, v, 0, NULL, 0)) == 0)
2008-08-09 16:12:50 +02:00
r = 1;
else if (k == REG_NOMATCH)
2008-08-09 16:12:50 +02:00
r = 0;
else
r = -1;
2008-08-09 16:12:50 +02:00
regfree(&re);
if (r < 0)
errno = EINVAL;
return r;
}
/* Try to parse a boolean string value.*/
int pa_parse_boolean(const char *v) {
const char *expr;
pa_assert(v);
/* First we check language independant */
if (!strcmp(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on"))
return 1;
else if (!strcmp(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off"))
return 0;
#ifdef HAVE_LANGINFO_H
/* And then we check language dependant */
if ((expr = nl_langinfo(YESEXPR)))
if (expr[0])
if (pa_match(expr, v) > 0)
return 1;
if ((expr = nl_langinfo(NOEXPR)))
if (expr[0])
if (pa_match(expr, v) > 0)
return 0;
#endif
errno = EINVAL;
return -1;
}
/* Split the specified string wherever one of the strings in delimiter
* occurs. Each time it is called returns a newly allocated string
* with pa_xmalloc(). The variable state points to, should be
* initiallized to NULL before the first call. */
char *pa_split(const char *c, const char *delimiter, const char**state) {
const char *current = *state ? *state : c;
size_t l;
if (!*current)
return NULL;
l = strcspn(current, delimiter);
*state = current+l;
if (**state)
(*state)++;
return pa_xstrndup(current, l);
}
/* Split a string into words. Otherwise similar to pa_split(). */
char *pa_split_spaces(const char *c, const char **state) {
const char *current = *state ? *state : c;
size_t l;
if (!*current || *c == 0)
return NULL;
current += strspn(current, WHITESPACE);
l = strcspn(current, WHITESPACE);
*state = current+l;
return pa_xstrndup(current, l);
}
PA_STATIC_TLS_DECLARE(signame, pa_xfree);
/* Return the name of an UNIX signal. Similar to Solaris sig2str() */
const char *pa_sig2str(int sig) {
char *t;
if (sig <= 0)
goto fail;
#ifdef NSIG
if (sig >= NSIG)
goto fail;
#endif
#ifdef HAVE_SIG2STR
{
char buf[SIG2STR_MAX];
if (sig2str(sig, buf) == 0) {
pa_xfree(PA_STATIC_TLS_GET(signame));
t = pa_sprintf_malloc("SIG%s", buf);
PA_STATIC_TLS_SET(signame, t);
return t;
}
}
#else
switch (sig) {
#ifdef SIGHUP
case SIGHUP: return "SIGHUP";
#endif
case SIGINT: return "SIGINT";
#ifdef SIGQUIT
case SIGQUIT: return "SIGQUIT";
#endif
case SIGILL: return "SIGULL";
#ifdef SIGTRAP
case SIGTRAP: return "SIGTRAP";
#endif
case SIGABRT: return "SIGABRT";
#ifdef SIGBUS
case SIGBUS: return "SIGBUS";
#endif
case SIGFPE: return "SIGFPE";
#ifdef SIGKILL
case SIGKILL: return "SIGKILL";
#endif
#ifdef SIGUSR1
case SIGUSR1: return "SIGUSR1";
#endif
case SIGSEGV: return "SIGSEGV";
#ifdef SIGUSR2
case SIGUSR2: return "SIGUSR2";
#endif
#ifdef SIGPIPE
case SIGPIPE: return "SIGPIPE";
#endif
#ifdef SIGALRM
case SIGALRM: return "SIGALRM";
#endif
case SIGTERM: return "SIGTERM";
#ifdef SIGSTKFLT
case SIGSTKFLT: return "SIGSTKFLT";
#endif
#ifdef SIGCHLD
case SIGCHLD: return "SIGCHLD";
#endif
#ifdef SIGCONT
case SIGCONT: return "SIGCONT";
#endif
#ifdef SIGSTOP
case SIGSTOP: return "SIGSTOP";
#endif
#ifdef SIGTSTP
case SIGTSTP: return "SIGTSTP";
#endif
#ifdef SIGTTIN
case SIGTTIN: return "SIGTTIN";
#endif
#ifdef SIGTTOU
case SIGTTOU: return "SIGTTOU";
#endif
#ifdef SIGURG
case SIGURG: return "SIGURG";
#endif
#ifdef SIGXCPU
case SIGXCPU: return "SIGXCPU";
#endif
#ifdef SIGXFSZ
case SIGXFSZ: return "SIGXFSZ";
#endif
#ifdef SIGVTALRM
case SIGVTALRM: return "SIGVTALRM";
#endif
#ifdef SIGPROF
case SIGPROF: return "SIGPROF";
#endif
#ifdef SIGWINCH
case SIGWINCH: return "SIGWINCH";
#endif
#ifdef SIGIO
case SIGIO: return "SIGIO";
#endif
#ifdef SIGPWR
case SIGPWR: return "SIGPWR";
#endif
#ifdef SIGSYS
case SIGSYS: return "SIGSYS";
#endif
}
#ifdef SIGRTMIN
if (sig >= SIGRTMIN && sig <= SIGRTMAX) {
pa_xfree(PA_STATIC_TLS_GET(signame));
t = pa_sprintf_malloc("SIGRTMIN+%i", sig - SIGRTMIN);
PA_STATIC_TLS_SET(signame, t);
return t;
}
#endif
#endif
fail:
pa_xfree(PA_STATIC_TLS_GET(signame));
t = pa_sprintf_malloc("SIG%i", sig);
PA_STATIC_TLS_SET(signame, t);
return t;
}
#ifdef HAVE_GRP_H
/* Check whether the specified GID and the group name match */
static int is_group(gid_t gid, const char *name) {
struct group *group = NULL;
int r = -1;
errno = 0;
if (!(group = pa_getgrgid_malloc(gid))) {
if (!errno)
errno = ENOENT;
pa_log("pa_getgrgid_malloc(%u): %s", gid, pa_cstrerror(errno));
goto finish;
}
r = strcmp(name, group->gr_name) == 0;
finish:
pa_getgrgid_free(group);
return r;
}
/* Check the current user is member of the specified group */
int pa_own_uid_in_group(const char *name, gid_t *gid) {
GETGROUPS_T *gids, tgid;
long n = sysconf(_SC_NGROUPS_MAX);
int r = -1, i, k;
pa_assert(n > 0);
gids = pa_xmalloc(sizeof(GETGROUPS_T) * (size_t) n);
if ((n = getgroups((int) n, gids)) < 0) {
pa_log("getgroups(): %s", pa_cstrerror(errno));
goto finish;
}
for (i = 0; i < n; i++) {
if ((k = is_group(gids[i], name)) < 0)
goto finish;
else if (k > 0) {
*gid = gids[i];
r = 1;
goto finish;
}
}
if ((k = is_group(tgid = getgid(), name)) < 0)
goto finish;
else if (k > 0) {
*gid = tgid;
r = 1;
goto finish;
}
r = 0;
finish:
pa_xfree(gids);
return r;
}
/* Check whether the specifc user id is a member of the specified group */
int pa_uid_in_group(uid_t uid, const char *name) {
struct group *group = NULL;
char **i;
int r = -1;
errno = 0;
if (!(group = pa_getgrnam_malloc(name))) {
if (!errno)
errno = ENOENT;
goto finish;
}
r = 0;
for (i = group->gr_mem; *i; i++) {
struct passwd *pw = NULL;
errno = 0;
if (!(pw = pa_getpwnam_malloc(*i)))
continue;
if (pw->pw_uid == uid)
r = 1;
pa_getpwnam_free(pw);
if (r == 1)
break;
}
finish:
pa_getgrnam_free(group);
return r;
}
/* Get the GID of a gfiven group, return (gid_t) -1 on failure. */
gid_t pa_get_gid_of_group(const char *name) {
gid_t ret = (gid_t) -1;
struct group *gr = NULL;
errno = 0;
if (!(gr = pa_getgrnam_malloc(name))) {
if (!errno)
errno = ENOENT;
goto finish;
}
ret = gr->gr_gid;
finish:
pa_getgrnam_free(gr);
return ret;
}
int pa_check_in_group(gid_t g) {
gid_t gids[NGROUPS_MAX];
int r;
if ((r = getgroups(NGROUPS_MAX, gids)) < 0)
return -1;
for (; r > 0; r--)
if (gids[r-1] == g)
return 1;
return 0;
}
#else /* HAVE_GRP_H */
int pa_own_uid_in_group(const char *name, gid_t *gid) {
2011-01-04 17:12:09 +01:00
errno = ENOTSUP;
return -1;
}
int pa_uid_in_group(uid_t uid, const char *name) {
2011-01-04 17:12:09 +01:00
errno = ENOTSUP;
return -1;
}
gid_t pa_get_gid_of_group(const char *name) {
2011-01-04 17:12:09 +01:00
errno = ENOTSUP;
return (gid_t) -1;
}
int pa_check_in_group(gid_t g) {
2011-01-04 17:12:09 +01:00
errno = ENOTSUP;
return -1;
}
#endif
/* Lock or unlock a file entirely.
(advisory on UNIX, mandatory on Windows) */
int pa_lock_fd(int fd, int b) {
#ifdef F_SETLKW
struct flock f_lock;
/* Try a R/W lock first */
f_lock.l_type = (short) (b ? F_WRLCK : F_UNLCK);
f_lock.l_whence = SEEK_SET;
f_lock.l_start = 0;
f_lock.l_len = 0;
if (fcntl(fd, F_SETLKW, &f_lock) >= 0)
return 0;
/* Perhaps the file descriptor qas opened for read only, than try again with a read lock. */
if (b && errno == EBADF) {
f_lock.l_type = F_RDLCK;
if (fcntl(fd, F_SETLKW, &f_lock) >= 0)
return 0;
}
pa_log("%slock: %s", !b ? "un" : "", pa_cstrerror(errno));
#endif
#ifdef OS_IS_WIN32
HANDLE h = (HANDLE) _get_osfhandle(fd);
if (b && LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF))
return 0;
if (!b && UnlockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF))
return 0;
pa_log("%slock failed: 0x%08X", !b ? "un" : "", GetLastError());
/* FIXME: Needs to set errno! */
#endif
return -1;
}
/* Remove trailing newlines from a string */
char* pa_strip_nl(char *s) {
pa_assert(s);
s[strcspn(s, NEWLINE)] = 0;
return s;
}
char *pa_strip(char *s) {
char *e, *l = NULL;
/* Drops trailing whitespace. Modifies the string in
* place. Returns pointer to first non-space character */
s += strspn(s, WHITESPACE);
for (e = s; *e; e++)
if (!strchr(WHITESPACE, *e))
l = e;
if (l)
*(l+1) = 0;
else
*s = 0;
return s;
}
/* Create a temporary lock file and lock it. */
int pa_lock_lockfile(const char *fn) {
int fd;
pa_assert(fn);
for (;;) {
struct stat st;
if ((fd = pa_open_cloexec(fn, O_CREAT|O_RDWR
#ifdef O_NOFOLLOW
|O_NOFOLLOW
#endif
, S_IRUSR|S_IWUSR)) < 0) {
pa_log_warn("Failed to create lock file '%s': %s", fn, pa_cstrerror(errno));
goto fail;
}
if (pa_lock_fd(fd, 1) < 0) {
pa_log_warn("Failed to lock file '%s'.", fn);
goto fail;
}
if (fstat(fd, &st) < 0) {
pa_log_warn("Failed to fstat() file '%s': %s", fn, pa_cstrerror(errno));
goto fail;
}
2009-04-01 12:35:44 +02:00
/* Check whether the file has been removed meanwhile. When yes,
* restart this loop, otherwise, we're done */
if (st.st_nlink >= 1)
break;
if (pa_lock_fd(fd, 0) < 0) {
pa_log_warn("Failed to unlock file '%s'.", fn);
goto fail;
}
if (pa_close(fd) < 0) {
pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno));
fd = -1;
goto fail;
}
}
return fd;
fail:
if (fd >= 0) {
int saved_errno = errno;
pa_close(fd);
errno = saved_errno;
}
return -1;
}
/* Unlock a temporary lcok file */
int pa_unlock_lockfile(const char *fn, int fd) {
int r = 0;
pa_assert(fd >= 0);
if (fn) {
if (unlink(fn) < 0) {
pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno));
r = -1;
}
}
if (pa_lock_fd(fd, 0) < 0) {
pa_log_warn("Failed to unlock file '%s'.", fn);
r = -1;
}
if (pa_close(fd) < 0) {
pa_log_warn("Failed to close '%s': %s", fn, pa_cstrerror(errno));
r = -1;
}
return r;
}
static char *get_pulse_home(void) {
char *h;
struct stat st;
char *ret = NULL;
if (!(h = pa_get_home_dir_malloc())) {
pa_log_error("Failed to get home directory.");
return NULL;
}
if (stat(h, &st) < 0) {
pa_log_error("Failed to stat home directory %s: %s", h, pa_cstrerror(errno));
goto finish;
}
#ifdef HAVE_GETUID
if (st.st_uid != getuid()) {
pa_log_error("Home directory %s not ours.", h);
errno = EACCES;
goto finish;
}
#endif
ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h);
finish:
pa_xfree(h);
return ret;
}
char *pa_get_state_dir(void) {
char *d;
/* The state directory shall contain dynamic data that should be
* kept across reboots, and is private to this user */
if (!(d = pa_xstrdup(getenv("PULSE_STATE_PATH"))))
if (!(d = get_pulse_home()))
return NULL;
2008-06-21 13:55:52 +02:00
/* If PULSE_STATE_PATH and PULSE_RUNTIME_PATH point to the same
* dir then this will break. */
2011-03-12 19:45:02 +01:00
if (pa_make_secure_dir(d, 0700U, (uid_t) -1, (gid_t) -1) < 0) {
pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno));
pa_xfree(d);
return NULL;
}
return d;
}
char *pa_get_home_dir_malloc(void) {
char *homedir;
size_t allocated = 128;
for (;;) {
homedir = pa_xmalloc(allocated);
if (!pa_get_home_dir(homedir, allocated)) {
pa_xfree(homedir);
return NULL;
}
if (strlen(homedir) < allocated - 1)
break;
pa_xfree(homedir);
allocated *= 2;
}
return homedir;
}
char *pa_get_binary_name_malloc(void) {
char *t;
size_t allocated = 128;
for (;;) {
t = pa_xmalloc(allocated);
if (!pa_get_binary_name(t, allocated)) {
pa_xfree(t);
return NULL;
}
if (strlen(t) < allocated - 1)
break;
pa_xfree(t);
allocated *= 2;
}
return t;
}
static char* make_random_dir(mode_t m) {
static const char table[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
char *fn;
size_t pathlen;
fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir());
pathlen = strlen(fn);
for (;;) {
size_t i;
int r;
mode_t u;
int saved_errno;
for (i = pathlen - 12; i < pathlen; i++)
fn[i] = table[rand() % (sizeof(table)-1)];
u = umask((~m) & 0777);
#ifndef OS_IS_WIN32
r = mkdir(fn, m);
#else
r = mkdir(fn);
#endif
saved_errno = errno;
umask(u);
errno = saved_errno;
if (r >= 0)
return fn;
if (errno != EEXIST) {
pa_log_error("Failed to create random directory %s: %s", fn, pa_cstrerror(errno));
pa_xfree(fn);
return NULL;
}
}
}
static int make_random_dir_and_link(mode_t m, const char *k) {
char *p;
if (!(p = make_random_dir(m)))
return -1;
#ifdef HAVE_SYMLINK
if (symlink(p, k) < 0) {
int saved_errno = errno;
if (errno != EEXIST)
pa_log_error("Failed to symlink %s to %s: %s", k, p, pa_cstrerror(errno));
rmdir(p);
pa_xfree(p);
errno = saved_errno;
return -1;
}
#else
pa_xfree(p);
return -1;
#endif
pa_xfree(p);
return 0;
}
char *pa_get_runtime_dir(void) {
char *d, *k = NULL, *p = NULL, *t = NULL, *mid;
struct stat st;
mode_t m;
/* The runtime directory shall contain dynamic data that needs NOT
* to be kept accross reboots and is usuallly private to the user,
* except in system mode, where it might be accessible by other
* users, too. Since we need POSIX locking and UNIX sockets in
* this directory, we link it to a random subdir in /tmp, if it
* was not explicitly configured. */
m = pa_in_system_mode() ? 0755U : 0700U;
if ((d = getenv("PULSE_RUNTIME_PATH"))) {
2011-03-12 19:45:02 +01:00
if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1) < 0) {
pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno));
goto fail;
}
return pa_xstrdup(d);
}
if (!(d = get_pulse_home()))
goto fail;
2011-03-12 19:45:02 +01:00
if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1) < 0) {
pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno));
pa_xfree(d);
goto fail;
}
if (!(mid = pa_machine_id())) {
pa_xfree(d);
goto fail;
}
k = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-runtime", d, mid);
pa_xfree(d);
pa_xfree(mid);
for (;;) {
/* OK, first let's check if the "runtime" symlink is already
* existant */
if (!(p = pa_readlink(k))) {
if (errno != ENOENT) {
pa_log_error("Failed to stat runtime directory %s: %s", k, pa_cstrerror(errno));
goto fail;
}
#ifdef HAVE_SYMLINK
/* Hmm, so the runtime directory didn't exist yet, so let's
* create one in /tmp and symlink that to it */
if (make_random_dir_and_link(0700, k) < 0) {
/* Mhmm, maybe another process was quicker than us,
* let's check if that was valid */
if (errno == EEXIST)
continue;
goto fail;
}
#else
/* No symlink possible, so let's just create the runtime directly */
if (!mkdir(k))
goto fail;
#endif
return k;
}
/* Make sure that this actually makes sense */
if (!pa_is_path_absolute(p)) {
pa_log_error("Path %s in link %s is not absolute.", p, k);
errno = ENOENT;
goto fail;
}
/* Hmm, so this symlink is still around, make sure nobody fools
* us */
#ifdef HAVE_LSTAT
if (lstat(p, &st) < 0) {
if (errno != ENOENT) {
pa_log_error("Failed to stat runtime directory %s: %s", p, pa_cstrerror(errno));
goto fail;
}
} else {
if (S_ISDIR(st.st_mode) &&
(st.st_uid == getuid()) &&
((st.st_mode & 0777) == 0700)) {
pa_xfree(p);
return k;
}
pa_log_info("Hmm, runtime path exists, but points to an invalid directory. Changing runtime directory.");
}
#endif
pa_xfree(p);
p = NULL;
/* Hmm, so the link points to some nonexisting or invalid
* dir. Let's replace it by a new link. We first create a
* temporary link and then rename that to allow concurrent
* execution of this function. */
t = pa_sprintf_malloc("%s.tmp", k);
if (make_random_dir_and_link(0700, t) < 0) {
if (errno != EEXIST) {
pa_log_error("Failed to symlink %s: %s", t, pa_cstrerror(errno));
goto fail;
}
pa_xfree(t);
t = NULL;
/* Hmm, someone lese was quicker then us. Let's give
* him some time to finish, and retry. */
pa_msleep(10);
continue;
}
/* OK, we succeeded in creating the temporary symlink, so
* let's rename it */
if (rename(t, k) < 0) {
pa_log_error("Failed to rename %s to %s: %s", t, k, pa_cstrerror(errno));
goto fail;
}
pa_xfree(t);
return k;
}
fail:
pa_xfree(p);
pa_xfree(k);
pa_xfree(t);
return NULL;
}
/* Try to open a configuration file. If "env" is specified, open the
* value of the specified environment variable. Otherwise look for a
* file "local" in the home directory or a file "global" in global
* file system. If "result" is non-NULL, a pointer to a newly
* allocated buffer containing the used configuration file is
* stored there.*/
FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) {
const char *fn;
FILE *f;
if (env && (fn = getenv(env))) {
if ((f = pa_fopen_cloexec(fn, "r"))) {
if (result)
*result = pa_xstrdup(fn);
return f;
}
pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
return NULL;
}
if (local) {
const char *e;
char *lfn;
char *h;
if ((e = getenv("PULSE_CONFIG_PATH")))
fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
else if ((h = pa_get_home_dir_malloc())) {
fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
pa_xfree(h);
} else
return NULL;
if ((f = pa_fopen_cloexec(fn, "r"))) {
if (result)
*result = pa_xstrdup(fn);
pa_xfree(lfn);
return f;
}
if (errno != ENOENT) {
pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
pa_xfree(lfn);
return NULL;
}
pa_xfree(lfn);
}
if (global) {
char *gfn;
#ifdef OS_IS_WIN32
if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0)
gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s",
pa_win32_get_toplevel(NULL),
global + strlen(PA_DEFAULT_CONFIG_DIR));
else
#endif
gfn = pa_xstrdup(global);
if ((f = pa_fopen_cloexec(gfn, "r"))) {
if (result)
*result = gfn;
else
pa_xfree(gfn);
return f;
}
pa_xfree(gfn);
}
errno = ENOENT;
return NULL;
}
char *pa_find_config_file(const char *global, const char *local, const char *env) {
const char *fn;
if (env && (fn = getenv(env))) {
if (access(fn, R_OK) == 0)
return pa_xstrdup(fn);
pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
return NULL;
}
if (local) {
const char *e;
char *lfn;
char *h;
if ((e = getenv("PULSE_CONFIG_PATH")))
fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
else if ((h = pa_get_home_dir_malloc())) {
fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
pa_xfree(h);
} else
return NULL;
if (access(fn, R_OK) == 0) {
char *r = pa_xstrdup(fn);
pa_xfree(lfn);
return r;
}
if (errno != ENOENT) {
pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
pa_xfree(lfn);
return NULL;
}
pa_xfree(lfn);
}
if (global) {
char *gfn;
#ifdef OS_IS_WIN32
if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0)
gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s",
pa_win32_get_toplevel(NULL),
global + strlen(PA_DEFAULT_CONFIG_DIR));
else
#endif
gfn = pa_xstrdup(global);
if (access(gfn, R_OK) == 0)
return gfn;
pa_xfree(gfn);
}
errno = ENOENT;
return NULL;
}
/* Format the specified data as a hexademical string */
char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) {
size_t i = 0, j = 0;
const char hex[] = "0123456789abcdef";
pa_assert(d);
pa_assert(s);
pa_assert(slength > 0);
while (i < dlength && j+3 <= slength) {
s[j++] = hex[*d >> 4];
s[j++] = hex[*d & 0xF];
d++;
i++;
}
s[j < slength ? j : slength] = 0;
return s;
}
/* Convert a hexadecimal digit to a number or -1 if invalid */
static int hexc(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
errno = EINVAL;
return -1;
}
/* Parse a hexadecimal string as created by pa_hexstr() to a BLOB */
size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) {
size_t j = 0;
pa_assert(p);
pa_assert(d);
while (j < dlength && *p) {
int b;
if ((b = hexc(*(p++))) < 0)
return (size_t) -1;
d[j] = (uint8_t) (b << 4);
if (!*p)
return (size_t) -1;
if ((b = hexc(*(p++))) < 0)
return (size_t) -1;
d[j] |= (uint8_t) b;
j++;
}
return j;
}
/* Returns nonzero when *s starts with *pfx */
pa_bool_t pa_startswith(const char *s, const char *pfx) {
size_t l;
pa_assert(s);
pa_assert(pfx);
l = strlen(pfx);
return strlen(s) >= l && strncmp(s, pfx, l) == 0;
}
/* Returns nonzero when *s ends with *sfx */
pa_bool_t pa_endswith(const char *s, const char *sfx) {
size_t l1, l2;
pa_assert(s);
pa_assert(sfx);
l1 = strlen(s);
l2 = strlen(sfx);
return l1 >= l2 && strcmp(s+l1-l2, sfx) == 0;
}
pa_bool_t pa_is_path_absolute(const char *fn) {
pa_assert(fn);
#ifndef OS_IS_WIN32
return *fn == '/';
#else
return strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\';
#endif
}
char *pa_make_path_absolute(const char *p) {
char *r;
char *cwd;
pa_assert(p);
if (pa_is_path_absolute(p))
return pa_xstrdup(p);
if (!(cwd = pa_getcwd()))
return pa_xstrdup(p);
r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", cwd, p);
pa_xfree(cwd);
return r;
}
/* if fn is null return the PulseAudio run time path in s (~/.pulse)
* if fn is non-null and starts with / return fn
* otherwise append fn to the run time path and return it */
static char *get_path(const char *fn, pa_bool_t prependmid, pa_bool_t rt) {
char *rtp;
rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir();
if (fn) {
char *r;
if (pa_is_path_absolute(fn))
return pa_xstrdup(fn);
if (!rtp)
return NULL;
if (prependmid) {
char *mid;
if (!(mid = pa_machine_id())) {
pa_xfree(rtp);
return NULL;
}
r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-%s", rtp, mid, fn);
pa_xfree(mid);
} else
r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", rtp, fn);
pa_xfree(rtp);
return r;
} else
return rtp;
}
char *pa_runtime_path(const char *fn) {
return get_path(fn, FALSE, TRUE);
}
char *pa_state_path(const char *fn, pa_bool_t appendmid) {
return get_path(fn, appendmid, FALSE);
}
/* Convert the string s to a signed integer in *ret_i */
int pa_atoi(const char *s, int32_t *ret_i) {
char *x = NULL;
long l;
pa_assert(s);
pa_assert(ret_i);
errno = 0;
l = strtol(s, &x, 0);
if (!x || *x || errno) {
if (!errno)
errno = EINVAL;
return -1;
}
if ((int32_t) l != l) {
errno = ERANGE;
return -1;
}
*ret_i = (int32_t) l;
return 0;
}
/* Convert the string s to an unsigned integer in *ret_u */
int pa_atou(const char *s, uint32_t *ret_u) {
char *x = NULL;
unsigned long l;
pa_assert(s);
pa_assert(ret_u);
errno = 0;
l = strtoul(s, &x, 0);
if (!x || *x || errno) {
if (!errno)
errno = EINVAL;
return -1;
}
if ((uint32_t) l != l) {
errno = ERANGE;
return -1;
}
*ret_u = (uint32_t) l;
return 0;
}
#ifdef HAVE_STRTOF_L
static locale_t c_locale = NULL;
static void c_locale_destroy(void) {
freelocale(c_locale);
}
#endif
int pa_atod(const char *s, double *ret_d) {
char *x = NULL;
double f;
pa_assert(s);
pa_assert(ret_d);
/* This should be locale independent */
#ifdef HAVE_STRTOF_L
PA_ONCE_BEGIN {
if ((c_locale = newlocale(LC_ALL_MASK, "C", NULL)))
atexit(c_locale_destroy);
} PA_ONCE_END;
if (c_locale) {
errno = 0;
f = strtod_l(s, &x, c_locale);
} else
#endif
{
errno = 0;
f = strtod(s, &x);
}
if (!x || *x || errno) {
if (!errno)
errno = EINVAL;
return -1;
}
*ret_d = f;
return 0;
}
/* Same as snprintf, but guarantees NUL-termination on every platform */
size_t pa_snprintf(char *str, size_t size, const char *format, ...) {
size_t ret;
va_list ap;
pa_assert(str);
pa_assert(size > 0);
pa_assert(format);
va_start(ap, format);
ret = pa_vsnprintf(str, size, format, ap);
va_end(ap);
return ret;
}
/* Same as vsnprintf, but guarantees NUL-termination on every platform */
size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
int ret;
pa_assert(str);
pa_assert(size > 0);
pa_assert(format);
ret = vsnprintf(str, size, format, ap);
str[size-1] = 0;
if (ret < 0)
return strlen(str);
if ((size_t) ret > size-1)
return size-1;
return (size_t) ret;
}
/* Truncate the specified string, but guarantee that the string
* returned still validates as UTF8 */
char *pa_truncate_utf8(char *c, size_t l) {
pa_assert(c);
pa_assert(pa_utf8_valid(c));
if (strlen(c) <= l)
return c;
c[l] = 0;
while (l > 0 && !pa_utf8_valid(c))
c[--l] = 0;
return c;
}
char *pa_getcwd(void) {
size_t l = 128;
for (;;) {
char *p = pa_xmalloc(l);
if (getcwd(p, l))
return p;
if (errno != ERANGE)
return NULL;
pa_xfree(p);
l *= 2;
}
}
void *pa_will_need(const void *p, size_t l) {
#ifdef RLIMIT_MEMLOCK
struct rlimit rlim;
#endif
const void *a;
size_t size;
int r;
size_t bs;
pa_assert(p);
pa_assert(l > 0);
a = PA_PAGE_ALIGN_PTR(p);
size = (size_t) ((const uint8_t*) p + l - (const uint8_t*) a);
#ifdef HAVE_POSIX_MADVISE
if ((r = posix_madvise((void*) a, size, POSIX_MADV_WILLNEED)) == 0) {
pa_log_debug("posix_madvise() worked fine!");
return (void*) p;
}
#endif
/* Most likely the memory was not mmap()ed from a file and thus
* madvise() didn't work, so let's misuse mlock() do page this
* stuff back into RAM. Yeah, let's fuck with the MM! It's so
* inviting, the man page of mlock() tells us: "All pages that
* contain a part of the specified address range are guaranteed to
* be resident in RAM when the call returns successfully." */
#ifdef RLIMIT_MEMLOCK
pa_assert_se(getrlimit(RLIMIT_MEMLOCK, &rlim) == 0);
if (rlim.rlim_cur < PA_PAGE_SIZE) {
pa_log_debug("posix_madvise() failed (or doesn't exist), resource limits don't allow mlock(), can't page in data: %s", pa_cstrerror(r));
errno = EPERM;
return (void*) p;
}
2008-08-20 03:33:06 +03:00
bs = PA_PAGE_ALIGN((size_t) rlim.rlim_cur);
#else
bs = PA_PAGE_SIZE*4;
#endif
pa_log_debug("posix_madvise() failed (or doesn't exist), trying mlock(): %s", pa_cstrerror(r));
#ifdef HAVE_MLOCK
while (size > 0 && bs > 0) {
if (bs > size)
bs = size;
if (mlock(a, bs) < 0) {
bs = PA_PAGE_ALIGN(bs / 2);
continue;
}
pa_assert_se(munlock(a, bs) == 0);
a = (const uint8_t*) a + bs;
size -= bs;
}
#endif
if (bs <= 0)
pa_log_debug("mlock() failed too (or doesn't exist), giving up: %s", pa_cstrerror(errno));
else
pa_log_debug("mlock() worked fine!");
return (void*) p;
}
void pa_close_pipe(int fds[2]) {
pa_assert(fds);
if (fds[0] >= 0)
pa_assert_se(pa_close(fds[0]) == 0);
if (fds[1] >= 0)
pa_assert_se(pa_close(fds[1]) == 0);
fds[0] = fds[1] = -1;
}
char *pa_readlink(const char *p) {
#ifdef HAVE_READLINK
size_t l = 100;
for (;;) {
char *c;
ssize_t n;
c = pa_xmalloc(l);
if ((n = readlink(p, c, l-1)) < 0) {
pa_xfree(c);
return NULL;
}
if ((size_t) n < l-1) {
c[n] = 0;
return c;
}
pa_xfree(c);
l *= 2;
}
#else
return NULL;
#endif
}
int pa_close_all(int except_fd, ...) {
va_list ap;
unsigned n = 0, i;
int r, *p;
va_start(ap, except_fd);
if (except_fd >= 0)
for (n = 1; va_arg(ap, int) >= 0; n++)
;
va_end(ap);
p = pa_xnew(int, n+1);
va_start(ap, except_fd);
i = 0;
if (except_fd >= 0) {
int fd;
p[i++] = except_fd;
while ((fd = va_arg(ap, int)) >= 0)
p[i++] = fd;
}
p[i] = -1;
va_end(ap);
r = pa_close_allv(p);
pa_xfree(p);
return r;
}
int pa_close_allv(const int except_fds[]) {
#ifndef OS_IS_WIN32
struct rlimit rl;
int maxfd, fd;
#ifdef __linux__
int saved_errno;
DIR *d;
if ((d = opendir("/proc/self/fd"))) {
struct dirent *de;
while ((de = readdir(d))) {
pa_bool_t found;
long l;
char *e = NULL;
int i;
if (de->d_name[0] == '.')
continue;
errno = 0;
l = strtol(de->d_name, &e, 10);
if (errno != 0 || !e || *e) {
closedir(d);
errno = EINVAL;
return -1;
}
fd = (int) l;
if ((long) fd != l) {
closedir(d);
errno = EINVAL;
return -1;
}
if (fd < 3)
continue;
if (fd == dirfd(d))
continue;
found = FALSE;
for (i = 0; except_fds[i] >= 0; i++)
if (except_fds[i] == fd) {
found = TRUE;
break;
}
if (found)
continue;
if (pa_close(fd) < 0) {
saved_errno = errno;
closedir(d);
errno = saved_errno;
return -1;
}
}
closedir(d);
return 0;
}
#endif
if (getrlimit(RLIMIT_NOFILE, &rl) >= 0)
maxfd = (int) rl.rlim_max;
else
maxfd = sysconf(_SC_OPEN_MAX);
for (fd = 3; fd < maxfd; fd++) {
int i;
pa_bool_t found;
found = FALSE;
for (i = 0; except_fds[i] >= 0; i++)
if (except_fds[i] == fd) {
found = TRUE;
break;
}
if (found)
continue;
if (pa_close(fd) < 0 && errno != EBADF)
return -1;
}
#endif /* !OS_IS_WIN32 */
return 0;
}
int pa_unblock_sigs(int except, ...) {
va_list ap;
unsigned n = 0, i;
int r, *p;
va_start(ap, except);
if (except >= 1)
for (n = 1; va_arg(ap, int) >= 0; n++)
;
va_end(ap);
p = pa_xnew(int, n+1);
va_start(ap, except);
i = 0;
if (except >= 1) {
int sig;
p[i++] = except;
while ((sig = va_arg(ap, int)) >= 0)
p[i++] = sig;
}
p[i] = -1;
va_end(ap);
r = pa_unblock_sigsv(p);
pa_xfree(p);
return r;
}
int pa_unblock_sigsv(const int except[]) {
#ifndef OS_IS_WIN32
int i;
sigset_t ss;
if (sigemptyset(&ss) < 0)
return -1;
for (i = 0; except[i] > 0; i++)
if (sigaddset(&ss, except[i]) < 0)
return -1;
return sigprocmask(SIG_SETMASK, &ss, NULL);
#else
return 0;
#endif
}
int pa_reset_sigs(int except, ...) {
va_list ap;
unsigned n = 0, i;
int *p, r;
va_start(ap, except);
if (except >= 1)
for (n = 1; va_arg(ap, int) >= 0; n++)
;
va_end(ap);
p = pa_xnew(int, n+1);
va_start(ap, except);
i = 0;
if (except >= 1) {
int sig;
p[i++] = except;
while ((sig = va_arg(ap, int)) >= 0)
p[i++] = sig;
}
p[i] = -1;
va_end(ap);
r = pa_reset_sigsv(p);
pa_xfree(p);
return r;
}
int pa_reset_sigsv(const int except[]) {
#ifndef OS_IS_WIN32
int sig;
for (sig = 1; sig < NSIG; sig++) {
pa_bool_t reset = TRUE;
switch (sig) {
case SIGKILL:
case SIGSTOP:
reset = FALSE;
break;
default: {
int i;
for (i = 0; except[i] > 0; i++) {
if (sig == except[i]) {
reset = FALSE;
break;
}
}
}
}
if (reset) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
/* On Linux the first two RT signals are reserved by
* glibc, and sigaction() will return EINVAL for them. */
if ((sigaction(sig, &sa, NULL) < 0))
if (errno != EINVAL)
return -1;
}
}
#endif
return 0;
}
void pa_set_env(const char *key, const char *value) {
pa_assert(key);
pa_assert(value);
/* This is not thread-safe */
#ifdef OS_IS_WIN32
SetEnvironmentVariable(key, value);
#else
setenv(key, value, 1);
#endif
}
void pa_set_env_and_record(const char *key, const char *value) {
pa_assert(key);
pa_assert(value);
/* This is not thread-safe */
pa_set_env(key, value);
recorded_env = pa_strlist_prepend(recorded_env, key);
}
void pa_unset_env_recorded(void) {
/* This is not thread-safe */
for (;;) {
char *s;
recorded_env = pa_strlist_pop(recorded_env, &s);
if (!s)
break;
#ifdef OS_IS_WIN32
SetEnvironmentVariable(s, NULL);
#else
unsetenv(s);
#endif
pa_xfree(s);
}
}
pa_bool_t pa_in_system_mode(void) {
const char *e;
if (!(e = getenv("PULSE_SYSTEM")))
return FALSE;
return !!atoi(e);
}
2008-08-07 02:22:57 +02:00
char *pa_get_user_name_malloc(void) {
ssize_t k;
char *u;
#ifdef _SC_LOGIN_NAME_MAX
k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX);
2008-08-07 02:22:57 +02:00
if (k <= 0)
#endif
k = 32;
2008-08-07 02:22:57 +02:00
u = pa_xnew(char, k+1);
if (!(pa_get_user_name(u, k))) {
pa_xfree(u);
return NULL;
2008-08-07 02:22:57 +02:00
}
return u;
}
char *pa_get_host_name_malloc(void) {
size_t l;
2008-08-07 02:22:57 +02:00
l = 100;
2008-08-07 02:22:57 +02:00
for (;;) {
char *c;
c = pa_xmalloc(l);
2008-08-07 02:22:57 +02:00
if (!pa_get_host_name(c, l)) {
if (errno != EINVAL && errno != ENAMETOOLONG)
break;
} else if (strlen(c) < l-1) {
char *u;
if (*c == 0) {
2008-08-07 02:22:57 +02:00
pa_xfree(c);
break;
2008-08-07 02:22:57 +02:00
}
u = pa_utf8_filter(c);
pa_xfree(c);
return u;
}
2008-08-07 02:22:57 +02:00
/* Hmm, the hostname is as long the space we offered the
* function, we cannot know if it fully fit in, so let's play
* safe and retry. */
pa_xfree(c);
l *= 2;
}
return NULL;
}
char *pa_machine_id(void) {
FILE *f;
char *h;
/* The returned value is supposed be some kind of ascii identifier
* that is unique and stable across reboots. */
/* First we try the D-Bus UUID, which is the best option we have,
* since it fits perfectly our needs and is not as volatile as the
* hostname which might be set from dhcp. */
if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r"))) {
char ln[34] = "", *r;
r = fgets(ln, sizeof(ln)-1, f);
fclose(f);
pa_strip_nl(ln);
if (r && ln[0])
return pa_utf8_filter(ln);
}
if ((h = pa_get_host_name_malloc()))
return h;
#ifndef OS_IS_WIN32
/* If no hostname was set we use the POSIX hostid. It's usually
* the IPv4 address. Might not be that stable. */
return pa_sprintf_malloc("%08lx", (unsigned long) gethostid);
#else
return NULL;
#endif
}
2009-04-13 22:20:48 +02:00
char *pa_session_id(void) {
const char *e;
if (!(e = getenv("XDG_SESSION_COOKIE")))
return NULL;
return pa_utf8_filter(e);
}
char *pa_uname_string(void) {
#ifdef HAVE_UNAME
struct utsname u;
pa_assert_se(uname(&u) >= 0);
return pa_sprintf_malloc("%s %s %s %s", u.sysname, u.machine, u.release, u.version);
#endif
#ifdef OS_IS_WIN32
OSVERSIONINFO i;
pa_zero(i);
i.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
pa_assert_se(GetVersionEx(&i));
return pa_sprintf_malloc("Windows %d.%d (%d) %s", i.dwMajorVersion, i.dwMinorVersion, i.dwBuildNumber, i.szCSDVersion);
#endif
2008-08-07 02:22:57 +02:00
}
#ifdef HAVE_VALGRIND_MEMCHECK_H
pa_bool_t pa_in_valgrind(void) {
static int b = 0;
/* To make heisenbugs a bit simpler to find we check for $VALGRIND
* here instead of really checking whether we run in valgrind or
* not. */
if (b < 1)
b = getenv("VALGRIND") ? 2 : 1;
return b > 1;
}
#endif
unsigned pa_gcd(unsigned a, unsigned b) {
while (b > 0) {
unsigned t = b;
b = a % b;
a = t;
}
return a;
}
void pa_reduce(unsigned *num, unsigned *den) {
unsigned gcd = pa_gcd(*num, *den);
if (gcd <= 0)
return;
*num /= gcd;
*den /= gcd;
pa_assert(pa_gcd(*num, *den) == 1);
}
2009-01-22 02:16:53 +01:00
unsigned pa_ncpus(void) {
long ncpus;
#ifdef _SC_NPROCESSORS_CONF
ncpus = sysconf(_SC_NPROCESSORS_CONF);
#else
ncpus = 1;
#endif
return ncpus <= 0 ? 1 : (unsigned) ncpus;
}
char *pa_replace(const char*s, const char*a, const char *b) {
pa_strbuf *sb;
size_t an;
pa_assert(s);
pa_assert(a);
pa_assert(b);
an = strlen(a);
sb = pa_strbuf_new();
for (;;) {
const char *p;
if (!(p = strstr(s, a)))
break;
pa_strbuf_putsn(sb, s, p-s);
pa_strbuf_puts(sb, b);
s = p + an;
}
pa_strbuf_puts(sb, s);
return pa_strbuf_tostring_free(sb);
}
2009-07-21 00:02:27 +03:00
char *pa_escape(const char *p, const char *chars) {
const char *s;
const char *c;
pa_strbuf *buf = pa_strbuf_new();
for (s = p; *s; ++s) {
if (*s == '\\')
pa_strbuf_putc(buf, '\\');
else if (chars) {
for (c = chars; *c; ++c) {
if (*s == *c) {
pa_strbuf_putc(buf, '\\');
break;
}
}
}
pa_strbuf_putc(buf, *s);
}
return pa_strbuf_tostring_free(buf);
}
char *pa_unescape(char *p) {
char *s, *d;
pa_bool_t escaped = FALSE;
for (s = p, d = p; *s; s++) {
if (!escaped && *s == '\\') {
escaped = TRUE;
continue;
}
*(d++) = *s;
escaped = FALSE;
}
*d = 0;
return p;
}
2009-02-18 21:57:57 +01:00
char *pa_realpath(const char *path) {
revive solaris module On Wed, 4 Mar 2009, Lennart Poettering wrote: [snip] > > This patch disables link map/library versioning unless ld is GNU ld. > > Another approach for solaris would be to use that linker's -M option, > > but I couldn't make that work (due to undefined mainloop, browse and > > simple symbols when linking pacat. I can post the errors if anyone is > > intested.) > > The linking in PA is a bit weird since we have a cyclic dependency > between libpulse and libpulsecommon which however is not explicit. Could that affect the pacat link somehow? What are the implications for client apps that link with the non-versioned libraries I've been building on solaris? [snip] > > struct userdata { > > pa_core *core; > > @@ -87,15 +92,24 @@ struct userdata { > > > > pa_memchunk memchunk; > > > > - unsigned int page_size; > > - > > uint32_t frame_size; > > - uint32_t buffer_size; > > - unsigned int written_bytes, read_bytes; > > + int32_t buffer_size; > > + volatile uint64_t written_bytes, read_bytes; > > + pa_mutex *written_bytes_lock; > > Hmm, we generally try do do things without locking in PA. This smells as > if it was solvable using atomic ints as well. > > Actually, looking at this again I get the impression these mutex are > completely unnecessary here. All functions that lock these mutexes are > called from the IO thread so no locking should be nessary. > > Please don't use volatile here. I am pretty sure it is a misuse. Also > see http://kernel.org/doc/Documentation/volatile-considered-harmful.txt > which applies here too I think. OK, I've removed the locks. For some reason I thought that the get_latency function was called from two different threads. > > +static void sink_set_volume(pa_sink *s) { > > + struct userdata *u; > > + audio_info_t info; > > + > > + pa_assert_se(u = s->userdata); > > + > > + if (u->fd >= 0) { > > + AUDIO_INITINFO(&info); > > + > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > I'd prefer if you'd use pa_cvolume_max here instead of pa_cvolume_avg() > because this makes the volume independant of the balance. > > > - info.play.error = 0; > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > Same here. (i.e. for the source) Done and done. > > + if (u->sink->thread_info.rewind_requested) > > + pa_sink_process_rewind(u->sink, 0); > > This is correct. > > > > > err = ioctl(u->fd, AUDIO_GETINFO, &info); > > pa_assert(err >= 0); > > Hmm, if at all this should be pa_assert_se(), not pa_assert() (so that > it is not defined away by -DNDEBUG). However I'd prefer if the error > would be could correctly. (I see that this code is not yours, but > still...) Done. > > + case EINTR: > > + break; > > I think you should simply try again in this case... Done. > > + case EAGAIN: > > + u->buffer_size = u->buffer_size * 18 / 25; > > + u->buffer_size -= u->buffer_size % u->frame_size; > > + u->buffer_size = PA_MAX(u->buffer_size, (int32_t)MIN_BUFFER_SIZE); > > + pa_sink_set_max_request(u->sink, u->buffer_size); > > + pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); > > + break; > > Hmm, care to explain this? EAGAIN happens when the user requests a buffer size that is too large for the STREAMS layer to accept. We end up looping with EAGAIN every time we try to write out the rest of the buffer, which burns enough CPU time to trip the CPU limit. So, I reduce the buffer size with each EAGAIN. This gets us reasonably close to the largest usable buffer size. (Perhaps there's a better way to determine what that limit is, but I don't know how.) > > + > > + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); > > + } else { > > + pa_rtpoll_set_timer_disabled(u->rtpoll); > > } > > Hmm, you schedule audio via timers? Is that a good idea? Perhaps not. I won't know until I test on more hardware. But, given that we have rt priority and high resolution timers on solaris, I think it is OK in theory... The reason I used a timer was to minimise CPU usage and avoid the CPU limit. Recall that getting woken up by poll is not an option for playback unfortunately. We can arrange for a signal when the FD becomes writable, but that throws out the whole buffer size concept, which acts to reduce latency. > That really only makes sense if you have to deal with large buffers and > support rewinding. I've implemented rewind support, but I'm still not sure that I have understood the concept; I take it that we "rewind" (from the point-of-view of the renderer, not the sink) so that some rendered but as yet unplayed portion of the memblock/buffers can then be rendered again? > Please keep in mind that the system clock and the sound card clock > deviate. If you use the system timers to do PCM scheduling ou might need > a pa_smoother object that is able to estimate the deviation for you. Actually, in an earlier version I did use a smoother (after reading about that in the wiki). But because of the non-monotonic sample counter (bug?) I decided that it probably wasn't worth the added complexity so I removed it. I'll put the smoother back if I can figure out the problem with the sample counter. > > > + u->frame_size = pa_frame_size(&ss); > > > > - if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) > > + u->buffer_size = 16384; > > It would appear more appropriate to me if the buffer size is adjusted by > the sample spec used. Done. > One last thing: it would probably be a good idea to allocate a pa_card > object and attach the sink and the source to it. It is possible to open /dev/audio twice by loading the solaris module twice -- once for the sink (passing record=0) and once for source (passing playback=0), thus giving seperate threads/LWPs for source and sink. It might be misleading to allocate two cards in that situation? > Right now pa_cards are mostly useful for switching profiles but even if > you do not allow switching profiles on-the-fly it is of some value to > find out via the cards object which source belongs to which sink. > > Otherwise I am happy! > > Thanks for your patch! I'd be thankful if you could fix the issues > pointed out and prepare another patch on top of current git! No problem. Patch follows. It also includes a portability fix for pa_realpath and a fix for a bug in the pa_signal_new() error path that causes signal data be freed if you attempt to register the same signal twice. > I hope I answered all your questions, Your answers were very helpful, thanks. Finn > > Lennart > >
2009-03-07 16:48:10 +11:00
char *t;
2009-02-18 21:57:57 +01:00
pa_assert(path);
/* We want only abolsute paths */
if (path[0] != '/') {
errno = EINVAL;
return NULL;
}
revive solaris module On Wed, 4 Mar 2009, Lennart Poettering wrote: [snip] > > This patch disables link map/library versioning unless ld is GNU ld. > > Another approach for solaris would be to use that linker's -M option, > > but I couldn't make that work (due to undefined mainloop, browse and > > simple symbols when linking pacat. I can post the errors if anyone is > > intested.) > > The linking in PA is a bit weird since we have a cyclic dependency > between libpulse and libpulsecommon which however is not explicit. Could that affect the pacat link somehow? What are the implications for client apps that link with the non-versioned libraries I've been building on solaris? [snip] > > struct userdata { > > pa_core *core; > > @@ -87,15 +92,24 @@ struct userdata { > > > > pa_memchunk memchunk; > > > > - unsigned int page_size; > > - > > uint32_t frame_size; > > - uint32_t buffer_size; > > - unsigned int written_bytes, read_bytes; > > + int32_t buffer_size; > > + volatile uint64_t written_bytes, read_bytes; > > + pa_mutex *written_bytes_lock; > > Hmm, we generally try do do things without locking in PA. This smells as > if it was solvable using atomic ints as well. > > Actually, looking at this again I get the impression these mutex are > completely unnecessary here. All functions that lock these mutexes are > called from the IO thread so no locking should be nessary. > > Please don't use volatile here. I am pretty sure it is a misuse. Also > see http://kernel.org/doc/Documentation/volatile-considered-harmful.txt > which applies here too I think. OK, I've removed the locks. For some reason I thought that the get_latency function was called from two different threads. > > +static void sink_set_volume(pa_sink *s) { > > + struct userdata *u; > > + audio_info_t info; > > + > > + pa_assert_se(u = s->userdata); > > + > > + if (u->fd >= 0) { > > + AUDIO_INITINFO(&info); > > + > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > I'd prefer if you'd use pa_cvolume_max here instead of pa_cvolume_avg() > because this makes the volume independant of the balance. > > > - info.play.error = 0; > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > Same here. (i.e. for the source) Done and done. > > + if (u->sink->thread_info.rewind_requested) > > + pa_sink_process_rewind(u->sink, 0); > > This is correct. > > > > > err = ioctl(u->fd, AUDIO_GETINFO, &info); > > pa_assert(err >= 0); > > Hmm, if at all this should be pa_assert_se(), not pa_assert() (so that > it is not defined away by -DNDEBUG). However I'd prefer if the error > would be could correctly. (I see that this code is not yours, but > still...) Done. > > + case EINTR: > > + break; > > I think you should simply try again in this case... Done. > > + case EAGAIN: > > + u->buffer_size = u->buffer_size * 18 / 25; > > + u->buffer_size -= u->buffer_size % u->frame_size; > > + u->buffer_size = PA_MAX(u->buffer_size, (int32_t)MIN_BUFFER_SIZE); > > + pa_sink_set_max_request(u->sink, u->buffer_size); > > + pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); > > + break; > > Hmm, care to explain this? EAGAIN happens when the user requests a buffer size that is too large for the STREAMS layer to accept. We end up looping with EAGAIN every time we try to write out the rest of the buffer, which burns enough CPU time to trip the CPU limit. So, I reduce the buffer size with each EAGAIN. This gets us reasonably close to the largest usable buffer size. (Perhaps there's a better way to determine what that limit is, but I don't know how.) > > + > > + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); > > + } else { > > + pa_rtpoll_set_timer_disabled(u->rtpoll); > > } > > Hmm, you schedule audio via timers? Is that a good idea? Perhaps not. I won't know until I test on more hardware. But, given that we have rt priority and high resolution timers on solaris, I think it is OK in theory... The reason I used a timer was to minimise CPU usage and avoid the CPU limit. Recall that getting woken up by poll is not an option for playback unfortunately. We can arrange for a signal when the FD becomes writable, but that throws out the whole buffer size concept, which acts to reduce latency. > That really only makes sense if you have to deal with large buffers and > support rewinding. I've implemented rewind support, but I'm still not sure that I have understood the concept; I take it that we "rewind" (from the point-of-view of the renderer, not the sink) so that some rendered but as yet unplayed portion of the memblock/buffers can then be rendered again? > Please keep in mind that the system clock and the sound card clock > deviate. If you use the system timers to do PCM scheduling ou might need > a pa_smoother object that is able to estimate the deviation for you. Actually, in an earlier version I did use a smoother (after reading about that in the wiki). But because of the non-monotonic sample counter (bug?) I decided that it probably wasn't worth the added complexity so I removed it. I'll put the smoother back if I can figure out the problem with the sample counter. > > > + u->frame_size = pa_frame_size(&ss); > > > > - if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) > > + u->buffer_size = 16384; > > It would appear more appropriate to me if the buffer size is adjusted by > the sample spec used. Done. > One last thing: it would probably be a good idea to allocate a pa_card > object and attach the sink and the source to it. It is possible to open /dev/audio twice by loading the solaris module twice -- once for the sink (passing record=0) and once for source (passing playback=0), thus giving seperate threads/LWPs for source and sink. It might be misleading to allocate two cards in that situation? > Right now pa_cards are mostly useful for switching profiles but even if > you do not allow switching profiles on-the-fly it is of some value to > find out via the cards object which source belongs to which sink. > > Otherwise I am happy! > > Thanks for your patch! I'd be thankful if you could fix the issues > pointed out and prepare another patch on top of current git! No problem. Patch follows. It also includes a portability fix for pa_realpath and a fix for a bug in the pa_signal_new() error path that causes signal data be freed if you attempt to register the same signal twice. > I hope I answered all your questions, Your answers were very helpful, thanks. Finn > > Lennart > >
2009-03-07 16:48:10 +11:00
#if defined(__GLIBC__) || defined(__APPLE__)
{
char *r;
2009-02-18 21:57:57 +01:00
revive solaris module On Wed, 4 Mar 2009, Lennart Poettering wrote: [snip] > > This patch disables link map/library versioning unless ld is GNU ld. > > Another approach for solaris would be to use that linker's -M option, > > but I couldn't make that work (due to undefined mainloop, browse and > > simple symbols when linking pacat. I can post the errors if anyone is > > intested.) > > The linking in PA is a bit weird since we have a cyclic dependency > between libpulse and libpulsecommon which however is not explicit. Could that affect the pacat link somehow? What are the implications for client apps that link with the non-versioned libraries I've been building on solaris? [snip] > > struct userdata { > > pa_core *core; > > @@ -87,15 +92,24 @@ struct userdata { > > > > pa_memchunk memchunk; > > > > - unsigned int page_size; > > - > > uint32_t frame_size; > > - uint32_t buffer_size; > > - unsigned int written_bytes, read_bytes; > > + int32_t buffer_size; > > + volatile uint64_t written_bytes, read_bytes; > > + pa_mutex *written_bytes_lock; > > Hmm, we generally try do do things without locking in PA. This smells as > if it was solvable using atomic ints as well. > > Actually, looking at this again I get the impression these mutex are > completely unnecessary here. All functions that lock these mutexes are > called from the IO thread so no locking should be nessary. > > Please don't use volatile here. I am pretty sure it is a misuse. Also > see http://kernel.org/doc/Documentation/volatile-considered-harmful.txt > which applies here too I think. OK, I've removed the locks. For some reason I thought that the get_latency function was called from two different threads. > > +static void sink_set_volume(pa_sink *s) { > > + struct userdata *u; > > + audio_info_t info; > > + > > + pa_assert_se(u = s->userdata); > > + > > + if (u->fd >= 0) { > > + AUDIO_INITINFO(&info); > > + > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > I'd prefer if you'd use pa_cvolume_max here instead of pa_cvolume_avg() > because this makes the volume independant of the balance. > > > - info.play.error = 0; > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > Same here. (i.e. for the source) Done and done. > > + if (u->sink->thread_info.rewind_requested) > > + pa_sink_process_rewind(u->sink, 0); > > This is correct. > > > > > err = ioctl(u->fd, AUDIO_GETINFO, &info); > > pa_assert(err >= 0); > > Hmm, if at all this should be pa_assert_se(), not pa_assert() (so that > it is not defined away by -DNDEBUG). However I'd prefer if the error > would be could correctly. (I see that this code is not yours, but > still...) Done. > > + case EINTR: > > + break; > > I think you should simply try again in this case... Done. > > + case EAGAIN: > > + u->buffer_size = u->buffer_size * 18 / 25; > > + u->buffer_size -= u->buffer_size % u->frame_size; > > + u->buffer_size = PA_MAX(u->buffer_size, (int32_t)MIN_BUFFER_SIZE); > > + pa_sink_set_max_request(u->sink, u->buffer_size); > > + pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); > > + break; > > Hmm, care to explain this? EAGAIN happens when the user requests a buffer size that is too large for the STREAMS layer to accept. We end up looping with EAGAIN every time we try to write out the rest of the buffer, which burns enough CPU time to trip the CPU limit. So, I reduce the buffer size with each EAGAIN. This gets us reasonably close to the largest usable buffer size. (Perhaps there's a better way to determine what that limit is, but I don't know how.) > > + > > + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); > > + } else { > > + pa_rtpoll_set_timer_disabled(u->rtpoll); > > } > > Hmm, you schedule audio via timers? Is that a good idea? Perhaps not. I won't know until I test on more hardware. But, given that we have rt priority and high resolution timers on solaris, I think it is OK in theory... The reason I used a timer was to minimise CPU usage and avoid the CPU limit. Recall that getting woken up by poll is not an option for playback unfortunately. We can arrange for a signal when the FD becomes writable, but that throws out the whole buffer size concept, which acts to reduce latency. > That really only makes sense if you have to deal with large buffers and > support rewinding. I've implemented rewind support, but I'm still not sure that I have understood the concept; I take it that we "rewind" (from the point-of-view of the renderer, not the sink) so that some rendered but as yet unplayed portion of the memblock/buffers can then be rendered again? > Please keep in mind that the system clock and the sound card clock > deviate. If you use the system timers to do PCM scheduling ou might need > a pa_smoother object that is able to estimate the deviation for you. Actually, in an earlier version I did use a smoother (after reading about that in the wiki). But because of the non-monotonic sample counter (bug?) I decided that it probably wasn't worth the added complexity so I removed it. I'll put the smoother back if I can figure out the problem with the sample counter. > > > + u->frame_size = pa_frame_size(&ss); > > > > - if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) > > + u->buffer_size = 16384; > > It would appear more appropriate to me if the buffer size is adjusted by > the sample spec used. Done. > One last thing: it would probably be a good idea to allocate a pa_card > object and attach the sink and the source to it. It is possible to open /dev/audio twice by loading the solaris module twice -- once for the sink (passing record=0) and once for source (passing playback=0), thus giving seperate threads/LWPs for source and sink. It might be misleading to allocate two cards in that situation? > Right now pa_cards are mostly useful for switching profiles but even if > you do not allow switching profiles on-the-fly it is of some value to > find out via the cards object which source belongs to which sink. > > Otherwise I am happy! > > Thanks for your patch! I'd be thankful if you could fix the issues > pointed out and prepare another patch on top of current git! No problem. Patch follows. It also includes a portability fix for pa_realpath and a fix for a bug in the pa_signal_new() error path that causes signal data be freed if you attempt to register the same signal twice. > I hope I answered all your questions, Your answers were very helpful, thanks. Finn > > Lennart > >
2009-03-07 16:48:10 +11:00
if (!(r = realpath(path, NULL)))
return NULL;
/* We copy this here in case our pa_xmalloc() is not
* implemented on top of libc malloc() */
t = pa_xstrdup(r);
pa_xfree(r);
}
#elif defined(PATH_MAX)
{
char *path_buf;
path_buf = pa_xmalloc(PATH_MAX);
2009-02-18 21:57:57 +01:00
#if defined(OS_IS_WIN32)
if (!(t = _fullpath(path_buf, path, _MAX_PATH))) {
pa_xfree(path_buf);
return NULL;
}
#else
revive solaris module On Wed, 4 Mar 2009, Lennart Poettering wrote: [snip] > > This patch disables link map/library versioning unless ld is GNU ld. > > Another approach for solaris would be to use that linker's -M option, > > but I couldn't make that work (due to undefined mainloop, browse and > > simple symbols when linking pacat. I can post the errors if anyone is > > intested.) > > The linking in PA is a bit weird since we have a cyclic dependency > between libpulse and libpulsecommon which however is not explicit. Could that affect the pacat link somehow? What are the implications for client apps that link with the non-versioned libraries I've been building on solaris? [snip] > > struct userdata { > > pa_core *core; > > @@ -87,15 +92,24 @@ struct userdata { > > > > pa_memchunk memchunk; > > > > - unsigned int page_size; > > - > > uint32_t frame_size; > > - uint32_t buffer_size; > > - unsigned int written_bytes, read_bytes; > > + int32_t buffer_size; > > + volatile uint64_t written_bytes, read_bytes; > > + pa_mutex *written_bytes_lock; > > Hmm, we generally try do do things without locking in PA. This smells as > if it was solvable using atomic ints as well. > > Actually, looking at this again I get the impression these mutex are > completely unnecessary here. All functions that lock these mutexes are > called from the IO thread so no locking should be nessary. > > Please don't use volatile here. I am pretty sure it is a misuse. Also > see http://kernel.org/doc/Documentation/volatile-considered-harmful.txt > which applies here too I think. OK, I've removed the locks. For some reason I thought that the get_latency function was called from two different threads. > > +static void sink_set_volume(pa_sink *s) { > > + struct userdata *u; > > + audio_info_t info; > > + > > + pa_assert_se(u = s->userdata); > > + > > + if (u->fd >= 0) { > > + AUDIO_INITINFO(&info); > > + > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > I'd prefer if you'd use pa_cvolume_max here instead of pa_cvolume_avg() > because this makes the volume independant of the balance. > > > - info.play.error = 0; > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > Same here. (i.e. for the source) Done and done. > > + if (u->sink->thread_info.rewind_requested) > > + pa_sink_process_rewind(u->sink, 0); > > This is correct. > > > > > err = ioctl(u->fd, AUDIO_GETINFO, &info); > > pa_assert(err >= 0); > > Hmm, if at all this should be pa_assert_se(), not pa_assert() (so that > it is not defined away by -DNDEBUG). However I'd prefer if the error > would be could correctly. (I see that this code is not yours, but > still...) Done. > > + case EINTR: > > + break; > > I think you should simply try again in this case... Done. > > + case EAGAIN: > > + u->buffer_size = u->buffer_size * 18 / 25; > > + u->buffer_size -= u->buffer_size % u->frame_size; > > + u->buffer_size = PA_MAX(u->buffer_size, (int32_t)MIN_BUFFER_SIZE); > > + pa_sink_set_max_request(u->sink, u->buffer_size); > > + pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); > > + break; > > Hmm, care to explain this? EAGAIN happens when the user requests a buffer size that is too large for the STREAMS layer to accept. We end up looping with EAGAIN every time we try to write out the rest of the buffer, which burns enough CPU time to trip the CPU limit. So, I reduce the buffer size with each EAGAIN. This gets us reasonably close to the largest usable buffer size. (Perhaps there's a better way to determine what that limit is, but I don't know how.) > > + > > + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); > > + } else { > > + pa_rtpoll_set_timer_disabled(u->rtpoll); > > } > > Hmm, you schedule audio via timers? Is that a good idea? Perhaps not. I won't know until I test on more hardware. But, given that we have rt priority and high resolution timers on solaris, I think it is OK in theory... The reason I used a timer was to minimise CPU usage and avoid the CPU limit. Recall that getting woken up by poll is not an option for playback unfortunately. We can arrange for a signal when the FD becomes writable, but that throws out the whole buffer size concept, which acts to reduce latency. > That really only makes sense if you have to deal with large buffers and > support rewinding. I've implemented rewind support, but I'm still not sure that I have understood the concept; I take it that we "rewind" (from the point-of-view of the renderer, not the sink) so that some rendered but as yet unplayed portion of the memblock/buffers can then be rendered again? > Please keep in mind that the system clock and the sound card clock > deviate. If you use the system timers to do PCM scheduling ou might need > a pa_smoother object that is able to estimate the deviation for you. Actually, in an earlier version I did use a smoother (after reading about that in the wiki). But because of the non-monotonic sample counter (bug?) I decided that it probably wasn't worth the added complexity so I removed it. I'll put the smoother back if I can figure out the problem with the sample counter. > > > + u->frame_size = pa_frame_size(&ss); > > > > - if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) > > + u->buffer_size = 16384; > > It would appear more appropriate to me if the buffer size is adjusted by > the sample spec used. Done. > One last thing: it would probably be a good idea to allocate a pa_card > object and attach the sink and the source to it. It is possible to open /dev/audio twice by loading the solaris module twice -- once for the sink (passing record=0) and once for source (passing playback=0), thus giving seperate threads/LWPs for source and sink. It might be misleading to allocate two cards in that situation? > Right now pa_cards are mostly useful for switching profiles but even if > you do not allow switching profiles on-the-fly it is of some value to > find out via the cards object which source belongs to which sink. > > Otherwise I am happy! > > Thanks for your patch! I'd be thankful if you could fix the issues > pointed out and prepare another patch on top of current git! No problem. Patch follows. It also includes a portability fix for pa_realpath and a fix for a bug in the pa_signal_new() error path that causes signal data be freed if you attempt to register the same signal twice. > I hope I answered all your questions, Your answers were very helpful, thanks. Finn > > Lennart > >
2009-03-07 16:48:10 +11:00
if (!(t = realpath(path, path_buf))) {
pa_xfree(path_buf);
return NULL;
}
#endif
revive solaris module On Wed, 4 Mar 2009, Lennart Poettering wrote: [snip] > > This patch disables link map/library versioning unless ld is GNU ld. > > Another approach for solaris would be to use that linker's -M option, > > but I couldn't make that work (due to undefined mainloop, browse and > > simple symbols when linking pacat. I can post the errors if anyone is > > intested.) > > The linking in PA is a bit weird since we have a cyclic dependency > between libpulse and libpulsecommon which however is not explicit. Could that affect the pacat link somehow? What are the implications for client apps that link with the non-versioned libraries I've been building on solaris? [snip] > > struct userdata { > > pa_core *core; > > @@ -87,15 +92,24 @@ struct userdata { > > > > pa_memchunk memchunk; > > > > - unsigned int page_size; > > - > > uint32_t frame_size; > > - uint32_t buffer_size; > > - unsigned int written_bytes, read_bytes; > > + int32_t buffer_size; > > + volatile uint64_t written_bytes, read_bytes; > > + pa_mutex *written_bytes_lock; > > Hmm, we generally try do do things without locking in PA. This smells as > if it was solvable using atomic ints as well. > > Actually, looking at this again I get the impression these mutex are > completely unnecessary here. All functions that lock these mutexes are > called from the IO thread so no locking should be nessary. > > Please don't use volatile here. I am pretty sure it is a misuse. Also > see http://kernel.org/doc/Documentation/volatile-considered-harmful.txt > which applies here too I think. OK, I've removed the locks. For some reason I thought that the get_latency function was called from two different threads. > > +static void sink_set_volume(pa_sink *s) { > > + struct userdata *u; > > + audio_info_t info; > > + > > + pa_assert_se(u = s->userdata); > > + > > + if (u->fd >= 0) { > > + AUDIO_INITINFO(&info); > > + > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > I'd prefer if you'd use pa_cvolume_max here instead of pa_cvolume_avg() > because this makes the volume independant of the balance. > > > - info.play.error = 0; > > + info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; > > + assert(info.play.gain <= AUDIO_MAX_GAIN); > > Same here. (i.e. for the source) Done and done. > > + if (u->sink->thread_info.rewind_requested) > > + pa_sink_process_rewind(u->sink, 0); > > This is correct. > > > > > err = ioctl(u->fd, AUDIO_GETINFO, &info); > > pa_assert(err >= 0); > > Hmm, if at all this should be pa_assert_se(), not pa_assert() (so that > it is not defined away by -DNDEBUG). However I'd prefer if the error > would be could correctly. (I see that this code is not yours, but > still...) Done. > > + case EINTR: > > + break; > > I think you should simply try again in this case... Done. > > + case EAGAIN: > > + u->buffer_size = u->buffer_size * 18 / 25; > > + u->buffer_size -= u->buffer_size % u->frame_size; > > + u->buffer_size = PA_MAX(u->buffer_size, (int32_t)MIN_BUFFER_SIZE); > > + pa_sink_set_max_request(u->sink, u->buffer_size); > > + pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); > > + break; > > Hmm, care to explain this? EAGAIN happens when the user requests a buffer size that is too large for the STREAMS layer to accept. We end up looping with EAGAIN every time we try to write out the rest of the buffer, which burns enough CPU time to trip the CPU limit. So, I reduce the buffer size with each EAGAIN. This gets us reasonably close to the largest usable buffer size. (Perhaps there's a better way to determine what that limit is, but I don't know how.) > > + > > + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); > > + } else { > > + pa_rtpoll_set_timer_disabled(u->rtpoll); > > } > > Hmm, you schedule audio via timers? Is that a good idea? Perhaps not. I won't know until I test on more hardware. But, given that we have rt priority and high resolution timers on solaris, I think it is OK in theory... The reason I used a timer was to minimise CPU usage and avoid the CPU limit. Recall that getting woken up by poll is not an option for playback unfortunately. We can arrange for a signal when the FD becomes writable, but that throws out the whole buffer size concept, which acts to reduce latency. > That really only makes sense if you have to deal with large buffers and > support rewinding. I've implemented rewind support, but I'm still not sure that I have understood the concept; I take it that we "rewind" (from the point-of-view of the renderer, not the sink) so that some rendered but as yet unplayed portion of the memblock/buffers can then be rendered again? > Please keep in mind that the system clock and the sound card clock > deviate. If you use the system timers to do PCM scheduling ou might need > a pa_smoother object that is able to estimate the deviation for you. Actually, in an earlier version I did use a smoother (after reading about that in the wiki). But because of the non-monotonic sample counter (bug?) I decided that it probably wasn't worth the added complexity so I removed it. I'll put the smoother back if I can figure out the problem with the sample counter. > > > + u->frame_size = pa_frame_size(&ss); > > > > - if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) > > + u->buffer_size = 16384; > > It would appear more appropriate to me if the buffer size is adjusted by > the sample spec used. Done. > One last thing: it would probably be a good idea to allocate a pa_card > object and attach the sink and the source to it. It is possible to open /dev/audio twice by loading the solaris module twice -- once for the sink (passing record=0) and once for source (passing playback=0), thus giving seperate threads/LWPs for source and sink. It might be misleading to allocate two cards in that situation? > Right now pa_cards are mostly useful for switching profiles but even if > you do not allow switching profiles on-the-fly it is of some value to > find out via the cards object which source belongs to which sink. > > Otherwise I am happy! > > Thanks for your patch! I'd be thankful if you could fix the issues > pointed out and prepare another patch on top of current git! No problem. Patch follows. It also includes a portability fix for pa_realpath and a fix for a bug in the pa_signal_new() error path that causes signal data be freed if you attempt to register the same signal twice. > I hope I answered all your questions, Your answers were very helpful, thanks. Finn > > Lennart > >
2009-03-07 16:48:10 +11:00
}
#else
#error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here."
#endif
2009-02-18 21:57:57 +01:00
return t;
}
void pa_disable_sigpipe(void) {
#ifdef SIGPIPE
struct sigaction sa;
pa_zero(sa);
if (sigaction(SIGPIPE, NULL, &sa) < 0) {
pa_log("sigaction(): %s", pa_cstrerror(errno));
return;
}
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0) {
pa_log("sigaction(): %s", pa_cstrerror(errno));
return;
}
#endif
}
2009-06-17 03:13:01 +02:00
void pa_xfreev(void**a) {
void **p;
if (!a)
return;
for (p = a; *p; p++)
pa_xfree(*p);
pa_xfree(a);
}
char **pa_split_spaces_strv(const char *s) {
char **t, *e;
unsigned i = 0, n = 8;
const char *state = NULL;
t = pa_xnew(char*, n);
while ((e = pa_split_spaces(s, &state))) {
t[i++] = e;
if (i >= n) {
n *= 2;
t = pa_xrenew(char*, t, n);
}
}
if (i <= 0) {
pa_xfree(t);
return NULL;
}
t[i] = NULL;
return t;
}
char* pa_maybe_prefix_path(const char *path, const char *prefix) {
pa_assert(path);
if (pa_is_path_absolute(path))
return pa_xstrdup(path);
return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path);
}
size_t pa_pipe_buf(int fd) {
#ifdef _PC_PIPE_BUF
long n;
if ((n = fpathconf(fd, _PC_PIPE_BUF)) >= 0)
return (size_t) n;
#endif
#ifdef PIPE_BUF
return PIPE_BUF;
#else
return 4096;
#endif
}
void pa_reset_personality(void) {
#ifdef __linux__
if (personality(PER_LINUX) < 0)
pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno));
#endif
}
#if defined(__linux__) && !defined(__OPTIMIZE__)
pa_bool_t pa_run_from_build_tree(void) {
char *rp;
pa_bool_t b = FALSE;
/* We abuse __OPTIMIZE__ as a check whether we are a debug build
* or not. */
if ((rp = pa_readlink("/proc/self/exe"))) {
b = pa_startswith(rp, PA_BUILDDIR);
pa_xfree(rp);
}
return b;
}
#endif
const char *pa_get_temp_dir(void) {
const char *t;
if ((t = getenv("TMPDIR")) &&
pa_is_path_absolute(t))
return t;
if ((t = getenv("TMP")) &&
pa_is_path_absolute(t))
return t;
if ((t = getenv("TEMP")) &&
pa_is_path_absolute(t))
return t;
if ((t = getenv("TEMPDIR")) &&
pa_is_path_absolute(t))
return t;
return "/tmp";
}
int pa_open_cloexec(const char *fn, int flags, mode_t mode) {
int fd;
#ifdef O_NOCTTY
flags |= O_NOCTTY;
#endif
#ifdef O_CLOEXEC
if ((fd = open(fn, flags|O_CLOEXEC, mode)) >= 0)
goto finish;
if (errno != EINVAL)
return fd;
#endif
if ((fd = open(fn, flags, mode)) < 0)
return fd;
finish:
/* Some implementations might simply ignore O_CLOEXEC if it is not
* understood, make sure FD_CLOEXEC is enabled anyway */
pa_make_fd_cloexec(fd);
return fd;
}
int pa_socket_cloexec(int domain, int type, int protocol) {
int fd;
#ifdef SOCK_CLOEXEC
if ((fd = socket(domain, type | SOCK_CLOEXEC, protocol)) >= 0)
goto finish;
if (errno != EINVAL)
return fd;
#endif
if ((fd = socket(domain, type, protocol)) < 0)
return fd;
finish:
/* Some implementations might simply ignore SOCK_CLOEXEC if it is
* not understood, make sure FD_CLOEXEC is enabled anyway */
pa_make_fd_cloexec(fd);
return fd;
}
int pa_pipe_cloexec(int pipefd[2]) {
int r;
#ifdef HAVE_PIPE2
if ((r = pipe2(pipefd, O_CLOEXEC)) >= 0)
goto finish;
if (errno != EINVAL && errno != ENOSYS)
return r;
#endif
if ((r = pipe(pipefd)) < 0)
return r;
finish:
pa_make_fd_cloexec(pipefd[0]);
pa_make_fd_cloexec(pipefd[1]);
return 0;
}
int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
int fd;
#ifdef HAVE_ACCEPT4
if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0)
goto finish;
if (errno != EINVAL && errno != ENOSYS)
return fd;
#endif
if ((fd = accept(sockfd, addr, addrlen)) < 0)
return fd;
finish:
pa_make_fd_cloexec(fd);
return fd;
}
FILE* pa_fopen_cloexec(const char *path, const char *mode) {
FILE *f;
char *m;
m = pa_sprintf_malloc("%se", mode);
errno = 0;
if ((f = fopen(path, m))) {
pa_xfree(m);
goto finish;
}
pa_xfree(m);
if (errno != EINVAL)
return NULL;
if (!(f = fopen(path, mode)))
return NULL;
finish:
pa_make_fd_cloexec(fileno(f));
return f;
}
void pa_nullify_stdfds(void) {
#ifndef OS_IS_WIN32
pa_close(STDIN_FILENO);
pa_close(STDOUT_FILENO);
pa_close(STDERR_FILENO);
pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO);
pa_assert_se(open("/dev/null", O_WRONLY) == STDOUT_FILENO);
pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO);
#else
FreeConsole();
#endif
}
char *pa_read_line_from_file(const char *fn) {
FILE *f;
char ln[256] = "", *r;
if (!(f = pa_fopen_cloexec(fn, "r")))
return NULL;
r = fgets(ln, sizeof(ln)-1, f);
fclose(f);
if (!r) {
errno = EIO;
return NULL;
}
pa_strip_nl(ln);
return pa_xstrdup(ln);
}
pa_bool_t pa_running_in_vm(void) {
#if defined(__i386__) || defined(__x86_64__)
/* Both CPUID and DMI are x86 specific interfaces... */
uint32_t eax = 0x40000000;
union {
uint32_t sig32[3];
char text[13];
} sig;
#ifdef __linux__
const char *const dmi_vendors[] = {
"/sys/class/dmi/id/sys_vendor",
"/sys/class/dmi/id/board_vendor",
"/sys/class/dmi/id/bios_vendor"
};
unsigned i;
for (i = 0; i < PA_ELEMENTSOF(dmi_vendors); i++) {
char *s;
if ((s = pa_read_line_from_file(dmi_vendors[i]))) {
if (pa_startswith(s, "QEMU") ||
/* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
pa_startswith(s, "VMware") ||
pa_startswith(s, "VMW") ||
pa_startswith(s, "Microsoft Corporation") ||
pa_startswith(s, "innotek GmbH") ||
pa_startswith(s, "Xen")) {
pa_xfree(s);
return TRUE;
}
pa_xfree(s);
}
}
#endif
/* http://lwn.net/Articles/301888/ */
pa_zero(sig);
__asm__ __volatile__ (
/* ebx/rbx is being used for PIC! */
" push %%"PA_REG_b" \n\t"
" cpuid \n\t"
" mov %%ebx, %1 \n\t"
" pop %%"PA_REG_b" \n\t"
: "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
: "0" (eax)
);
if (pa_streq(sig.text, "XenVMMXenVMM") ||
pa_streq(sig.text, "KVMKVMKVM") ||
/* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
pa_streq(sig.text, "VMwareVMware") ||
/* http://msdn.microsoft.com/en-us/library/bb969719.aspx */
pa_streq(sig.text, "Microsoft Hv"))
return TRUE;
#endif
return FALSE;
}