X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/a67c21f093202f142438689d3f7cfbdf4ea82eea..8ad8e393f135f294942347dd66ec5e6c53329764:/src/pulsecore/core-util.c diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index a644b664..8e98e857 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -41,6 +39,11 @@ #include #include #include +#include +#include +#include +#include +#include #ifdef HAVE_STRTOF_L #include @@ -48,6 +51,10 @@ #ifdef HAVE_SCHED_H #include + +#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK) +#define SCHED_RESET_ON_FORK 0x40000000 +#endif #endif #ifdef HAVE_SYS_RESOURCE_H @@ -86,6 +93,18 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + +#ifdef HAVE_DBUS +#include "rtkit.h" +#endif + +#ifdef __linux__ +#include +#endif + #include #include #include @@ -95,6 +114,9 @@ #include #include #include +#include +#include +#include #include "core-util.h" @@ -103,11 +125,7 @@ #define MSG_NOSIGNAL 0 #endif -#ifndef OS_IS_WIN32 -#define PA_USER_RUNTIME_PATH_PREFIX "/tmp/pulse-" -#else -#define PA_USER_RUNTIME_PATH_PREFIX "%TEMP%\\pulse-" -#endif +static pa_strlist *recorded_env = NULL; #ifdef OS_IS_WIN32 @@ -118,6 +136,8 @@ int pa_set_root(HANDLE handle) { strcpy(library_path, PULSE_ROOTENV "="); + /* FIXME: Needs to set errno */ + if (!GetModuleFileName(handle, library_path + sizeof(PULSE_ROOTENV), MAX_PATH)) return 0; @@ -175,7 +195,7 @@ void pa_make_fd_cloexec(int fd) { /** 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; + int r, saved_errno; pa_assert(dir); @@ -184,7 +204,7 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { #else { mode_t u; - u = umask(~m); + u = umask((~m) & 0777); r = mkdir(dir, m); umask(u); } @@ -221,13 +241,16 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { goto fail; } #else - pa_log_warn("secure directory creation not supported on Win32."); + pa_log_warn("Secure directory creation not supported on Win32."); #endif return 0; fail: + saved_errno = errno; rmdir(dir); + errno = saved_errno; + return -1; } @@ -237,6 +260,7 @@ char *pa_parent_dir(const char *fn) { if ((slash = (char*) pa_path_get_filename(dir)) == dir) { pa_xfree(dir); + errno = ENOENT; return NULL; } @@ -289,7 +313,15 @@ ssize_t pa_read(int fd, void *buf, size_t count, int *type) { #endif - return read(fd, buf, count); + for (;;) { + ssize_t r; + + if ((r = read(fd, buf, count)) < 0) + if (errno == EINTR) + continue; + + return r; + } } /** Similar to pa_read(), but handles writes */ @@ -298,8 +330,17 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { if (!type || *type == 0) { ssize_t r; - if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0) + for (;;) { + if ((r = send(fd, buf, count, MSG_NOSIGNAL)) < 0) { + + if (errno == EINTR) + continue; + + break; + } + return r; + } #ifdef OS_IS_WIN32 if (WSAGetLastError() != WSAENOTSOCK) { @@ -315,11 +356,19 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { *type = 1; } - return write(fd, buf, count); + 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, - * unless EOF is reached or an error occured */ + * 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; @@ -344,7 +393,7 @@ ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) { ret += r; data = (uint8_t*) data + r; - size -= r; + size -= (size_t) r; } return ret; @@ -375,7 +424,7 @@ ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { ret += r; data = (const uint8_t*) data + r; - size -= r; + size -= (size_t) r; } return ret; @@ -397,7 +446,15 @@ int pa_close(int fd) { } #endif - return close(fd); + for (;;) { + int r; + + if ((r = close(fd)) < 0) + if (errno == EINTR) + continue; + + return r; + } } /* Print a warning messages in case that the given signal is not @@ -444,7 +501,7 @@ void pa_check_signal_is_blocked(int sig) { /* 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, ...) { - int size = 100; + size_t size = 100; char *c = NULL; pa_assert(format); @@ -461,11 +518,11 @@ char *pa_sprintf_malloc(const char *format, ...) { c[size-1] = 0; - if (r > -1 && r < size) + if (r > -1 && (size_t) r < size) return c; if (r > -1) /* glibc 2.1 */ - size = r+1; + size = (size_t) r+1; else /* glibc 2.0 */ size *= 2; } @@ -474,7 +531,7 @@ char *pa_sprintf_malloc(const char *format, ...) { /* Same as the previous function, but use a va_list instead of an * ellipsis */ char *pa_vsprintf_malloc(const char *format, va_list ap) { - int size = 100; + size_t size = 100; char *c = NULL; pa_assert(format); @@ -491,11 +548,11 @@ char *pa_vsprintf_malloc(const char *format, va_list ap) { c[size-1] = 0; - if (r > -1 && r < size) + if (r > -1 && (size_t) r < size) return c; if (r > -1) /* glibc 2.1 */ - size = r+1; + size = (size_t) r+1; else /* glibc 2.0 */ size *= 2; } @@ -503,82 +560,252 @@ char *pa_vsprintf_malloc(const char *format, va_list ap) { /* 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); - strncpy(b, s, l); - b[l-1] = 0; + k = strlen(s); + + if (k > l-1) + k = l-1; + + memcpy(b, s, k); + b[k] = 0; + return b; } -/* Make the current thread a realtime thread*/ -void pa_make_realtime(void) { - -#ifdef _POSIX_PRIORITY_SCHEDULING +static int set_scheduler(int rtprio) { struct sched_param sp; - int r, policy; + int r; +#ifdef HAVE_DBUS + DBusError error; + DBusConnection *bus; + + dbus_error_init(&error); +#endif - memset(&sp, 0, sizeof(sp)); - policy = 0; + pa_zero(sp); + sp.sched_priority = rtprio; - if ((r = pthread_getschedparam(pthread_self(), &policy, &sp)) != 0) { - pa_log("pthread_getschedgetparam(): %s", pa_cstrerror(r)); - return; +#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; } - sp.sched_priority = 1; - if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) != 0) { - pa_log_warn("pthread_setschedparam(): %s", pa_cstrerror(r)); - return; +#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; } - pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread."); + errno = -r; +#else + errno = r; #endif + return -1; +} + +/* 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)) { + pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio); + return 0; + } + + pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno)); + return -1; +#else + + errno = ENOTSUP; + return -1; +#endif } -#define NICE_LEVEL (-11) +static int set_nice(int nice_level) { +#ifdef HAVE_DBUS + DBusError error; + DBusConnection *bus; + int r; + + dbus_error_init(&error); +#endif + + if (setpriority(PRIO_PROCESS, 0, nice_level) >= 0) { + pa_log_debug("setpriority() worked."); + return 0; + } + +#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; +} -/* Raise the priority of the current process as much as possible and -sensible: set the nice level to -15.*/ -void pa_raise_priority(void) { +/* 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 - if (setpriority(PRIO_PROCESS, 0, NICE_LEVEL) < 0) - pa_log_warn("setpriority(): %s", pa_cstrerror(errno)); - else - pa_log_info("Successfully gained nice level %i.", NICE_LEVEL); + 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 (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) - pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError()); - else + 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() */ + * 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 +} -#ifdef HAVE_SYS_RESOURCE_H - setpriority(PRIO_PROCESS, 0, 0); -#endif +int pa_match(const char *expr, const char *v) { + int k; + regex_t re; + int r; + + if (regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) { + errno = EINVAL; + return -1; + } + + if ((k = regexec(&re, v, 0, NULL, 0)) == 0) + r = 1; + else if (k == REG_NOMATCH) + r = 0; + else + r = -1; + + 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; + /* 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; + + errno = EINVAL; return -1; } @@ -755,41 +982,24 @@ fail: /* Check whether the specified GID and the group name match */ static int is_group(gid_t gid, const char *name) { - struct group group, *result = NULL; - long n; - void *data; + struct group *group = NULL; int r = -1; -#ifdef HAVE_GETGRGID_R -#ifdef _SC_GETGR_R_SIZE_MAX - n = sysconf(_SC_GETGR_R_SIZE_MAX); -#else - n = -1; -#endif - if (n < 0) n = 512; - data = pa_xmalloc(n); - - if (getgrgid_r(gid, &group, data, n, &result) < 0 || !result) { - pa_log("getgrgid_r(%u): %s", (unsigned)gid, pa_cstrerror(errno)); - goto finish; - } + errno = 0; + if (!(group = pa_getgrgid_malloc(gid))) + { + if (!errno) + errno = ENOENT; - r = strcmp(name, result->gr_name) == 0; + pa_log("pa_getgrgid_malloc(%u): %s", gid, pa_cstrerror(errno)); -finish: - pa_xfree(data); -#else - /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) that do not - * support getgrgid_r. */ - if ((result = getgrgid(gid)) == NULL) { - pa_log("getgrgid(%u): %s", gid, pa_cstrerror(errno)); goto finish; } - r = strcmp(name, result->gr_name) == 0; + r = strcmp(name, group->gr_name) == 0; finish: -#endif + pa_getgrgid_free(group); return r; } @@ -797,27 +1007,32 @@ finish: /* 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; - int n = sysconf(_SC_NGROUPS_MAX); - int r = -1, i; + long n = sysconf(_SC_NGROUPS_MAX); + int r = -1, i, k; pa_assert(n > 0); - gids = pa_xmalloc(sizeof(GETGROUPS_T)*n); + gids = pa_xmalloc(sizeof(GETGROUPS_T) * (size_t) n); - if ((n = getgroups(n, gids)) < 0) { + if ((n = getgroups((int) n, gids)) < 0) { pa_log("getgroups(): %s", pa_cstrerror(errno)); goto finish; } for (i = 0; i < n; i++) { - if (is_group(gids[i], name) > 0) { + + if ((k = is_group(gids[i], name)) < 0) + goto finish; + else if (k > 0) { *gid = gids[i]; r = 1; goto finish; } } - if (is_group(tgid = getgid(), name) > 0) { + if ((k = is_group(tgid = getgid(), name)) < 0) + goto finish; + else if (k > 0) { *gid = tgid; r = 1; goto finish; @@ -833,37 +1048,37 @@ finish: /* Check whether the specifc user id is a member of the specified group */ int pa_uid_in_group(uid_t uid, const char *name) { - char *g_buf, *p_buf; - long g_n, p_n; - struct group grbuf, *gr; + struct group *group = NULL; char **i; int r = -1; - g_n = sysconf(_SC_GETGR_R_SIZE_MAX); - g_buf = pa_xmalloc(g_n); - - p_n = sysconf(_SC_GETPW_R_SIZE_MAX); - p_buf = pa_xmalloc(p_n); - - if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) + errno = 0; + if (!(group = pa_getgrnam_malloc(name))) + { + if (!errno) + errno = ENOENT; goto finish; + } r = 0; - for (i = gr->gr_mem; *i; i++) { - struct passwd pwbuf, *pw; + for (i = group->gr_mem; *i; i++) { + struct passwd *pw = NULL; - if (getpwnam_r(*i, &pwbuf, p_buf, (size_t) p_n, &pw) != 0 || !pw) + errno = 0; + if (!(pw = pa_getpwnam_malloc(*i))) continue; - if (pw->pw_uid == uid) { + if (pw->pw_uid == uid) r = 1; + + pa_getpwnam_free(pw); + + if (r == 1) break; - } } finish: - pa_xfree(g_buf); - pa_xfree(p_buf); + pa_getgrnam_free(group); return r; } @@ -871,20 +1086,20 @@ finish: /* 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; - char *g_buf; - long g_n; - struct group grbuf, *gr; + struct group *gr = NULL; - g_n = sysconf(_SC_GETGR_R_SIZE_MAX); - g_buf = pa_xmalloc(g_n); - - if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) + errno = 0; + if (!(gr = pa_getgrnam_malloc(name))) + { + if (!errno) + errno = ENOENT; goto finish; + } ret = gr->gr_gid; finish: - pa_xfree(g_buf); + pa_getgrnam_free(gr); return ret; } @@ -905,19 +1120,23 @@ int pa_check_in_group(gid_t g) { #else /* HAVE_GRP_H */ int pa_own_uid_in_group(const char *name, gid_t *gid) { + errno = ENOSUP; return -1; } int pa_uid_in_group(uid_t uid, const char *name) { + errno = ENOSUP; return -1; } gid_t pa_get_gid_of_group(const char *name) { + errno = ENOSUP; return (gid_t) -1; } int pa_check_in_group(gid_t g) { + errno = ENOSUP; return -1; } @@ -927,22 +1146,22 @@ int pa_check_in_group(gid_t g) { (advisory on UNIX, mandatory on Windows) */ int pa_lock_fd(int fd, int b) { #ifdef F_SETLKW - struct flock flock; + struct flock f_lock; /* Try a R/W lock first */ - flock.l_type = b ? F_WRLCK : F_UNLCK; - flock.l_whence = SEEK_SET; - flock.l_start = 0; - flock.l_len = 0; + 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, &flock) >= 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) { - flock.l_type = F_RDLCK; - if (fcntl(fd, F_SETLKW, &flock) >= 0) + f_lock.l_type = F_RDLCK; + if (fcntl(fd, F_SETLKW, &f_lock) >= 0) return 0; } @@ -958,6 +1177,8 @@ int pa_lock_fd(int fd, int b) { return 0; pa_log("%slock failed: 0x%08X", !b ? "un" : "", GetLastError()); + + /* FIXME: Needs to set errno! */ #endif return -1; @@ -973,7 +1194,7 @@ char* pa_strip_nl(char *s) { /* Create a temporary lock file and lock it. */ int pa_lock_lockfile(const char *fn) { - int fd = -1; + int fd; pa_assert(fn); for (;;) { @@ -1001,7 +1222,7 @@ int pa_lock_lockfile(const char *fn) { goto fail; } - /* Check wheter the file has been removed meanwhile. When yes, + /* Check whether the file has been removed meanwhile. When yes, * restart this loop, otherwise, we're done */ if (st.st_nlink >= 1) break; @@ -1016,16 +1237,17 @@ int pa_lock_lockfile(const char *fn) { fd = -1; goto fail; } - - fd = -1; } return fd; fail: - if (fd >= 0) + if (fd >= 0) { + int saved_errno = errno; pa_close(fd); + errno = saved_errno; + } return -1; } @@ -1033,12 +1255,13 @@ fail: /* Unlock a temporary lcok file */ int pa_unlock_lockfile(const char *fn, int fd) { int r = 0; - pa_assert(fn); pa_assert(fd >= 0); - if (unlink(fn) < 0) { - pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno)); - r = -1; + 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) { @@ -1054,128 +1277,526 @@ int pa_unlock_lockfile(const char *fn, int fd) { return r; } -/* 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 *mode) { - const char *fn; - char h[PATH_MAX]; - -#ifdef OS_IS_WIN32 - char buf[PATH_MAX]; - - if (!getenv(PULSE_ROOTENV)) - pa_set_root(NULL); -#endif +static char *get_pulse_home(void) { + char *h; + struct stat st; + char *ret = NULL; - if (env && (fn = getenv(env))) { -#ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX)) - return NULL; - fn = buf; -#endif + if (!(h = pa_get_home_dir_malloc())) { + pa_log_error("Failed to get home directory."); + return NULL; + } - if (result) - *result = pa_xstrdup(fn); + if (stat(h, &st) < 0) { + pa_log_error("Failed to stat home directory %s: %s", h, pa_cstrerror(errno)); + goto finish; + } - return fopen(fn, mode); + if (st.st_uid != getuid()) { + pa_log_error("Home directory %s not ours.", h); + errno = EACCES; + goto finish; } - if (local) { - const char *e; - char *lfn = NULL; + ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h); - if ((e = getenv("PULSE_CONFIG_PATH"))) - fn = lfn = pa_sprintf_malloc("%s/%s", e, local); - else if (pa_get_home_dir(h, sizeof(h))) - fn = lfn = pa_sprintf_malloc("%s/.pulse/%s", h, local); +finish: + pa_xfree(h); - if (lfn) { - FILE *f; + return ret; +} -#ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) - return NULL; - fn = buf; -#endif +char *pa_get_state_dir(void) { + char *d; - f = fopen(fn, mode); - if (f != NULL) { - if (result) - *result = pa_xstrdup(fn); - pa_xfree(lfn); - return f; - } + /* The state directory shall contain dynamic data that should be + * kept across reboots, and is private to this user */ - if (errno != ENOENT) - pa_log_warn("Failed to open configuration file '%s': %s", lfn, pa_cstrerror(errno)); + if (!(d = pa_xstrdup(getenv("PULSE_STATE_PATH")))) + if (!(d = get_pulse_home())) + return NULL; - pa_xfree(lfn); - } - } + /* If PULSE_STATE_PATH and PULSE_RUNTIME_PATH point to the same + * dir then this will break. */ - if (!global) { - if (result) - *result = NULL; - errno = ENOENT; + 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; } -#ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) - return NULL; - global = buf; -#endif - - if (result) - *result = pa_xstrdup(global); - - return fopen(global, mode); + return d; } -/* 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"; +char *pa_get_home_dir_malloc(void) { + char *homedir; + size_t allocated = 128; - pa_assert(d); - pa_assert(s); - pa_assert(slength > 0); + for (;;) { + homedir = pa_xmalloc(allocated); - while (i < dlength && j+3 <= slength) { - s[j++] = hex[*d >> 4]; - s[j++] = hex[*d & 0xF]; + if (!pa_get_home_dir(homedir, allocated)) { + pa_xfree(homedir); + return NULL; + } - d++; - i++; + if (strlen(homedir) < allocated - 1) + break; + + pa_xfree(homedir); + allocated *= 2; } - s[j < slength ? j : slength] = 0; - return s; + return homedir; } -/* Convert a hexadecimal digit to a number or -1 if invalid */ -static int hexc(char c) { - if (c >= '0' && c <= '9') - return c - '0'; +char *pa_get_binary_name_malloc(void) { + char *t; + size_t allocated = 128; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; + for (;;) { + t = pa_xmalloc(allocated); - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; + if (!pa_get_binary_name(t, allocated)) { + pa_xfree(t); + return NULL; + } - return -1; -} + if (strlen(t) < allocated - 1) + break; -/* 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_xfree(t); + allocated *= 2; + } - pa_assert(p); + 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); + r = mkdir(fn, m); + + 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; + + 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; + } + + 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"))) { + + 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; + + 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; + } + + /* 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; + } + + 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 */ + + 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."); + } + + 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; +#ifdef OS_IS_WIN32 + char buf[PATH_MAX]; + + if (!getenv(PULSE_ROOTENV)) + pa_set_root(NULL); +#endif + + if (env && (fn = getenv(env))) { + FILE *f; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX)) + /* FIXME: Needs to set errno! */ + return NULL; + fn = buf; +#endif + + if ((f = fopen(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; + FILE *f; + + 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; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) { + /* FIXME: Needs to set errno! */ + pa_xfree(lfn); + return NULL; + } + fn = buf; +#endif + + if ((f = fopen(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) { + FILE *f; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) + /* FIXME: Needs to set errno! */ + return NULL; + global = buf; +#endif + + if ((f = fopen(global, "r"))) { + + if (result) + *result = pa_xstrdup(global); + + return f; + } + } + + errno = ENOENT; + return NULL; +} + +char *pa_find_config_file(const char *global, const char *local, const char *env) { + const char *fn; +#ifdef OS_IS_WIN32 + char buf[PATH_MAX]; + + if (!getenv(PULSE_ROOTENV)) + pa_set_root(NULL); +#endif + + if (env && (fn = getenv(env))) { + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX)) + /* FIXME: Needs to set errno! */ + return NULL; + fn = buf; +#endif + + 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; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) { + /* FIXME: Needs to set errno! */ + pa_xfree(lfn); + return NULL; + } + fn = buf; +#endif + + 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) { +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) + /* FIXME: Needs to set errno! */ + return NULL; + global = buf; +#endif + + if (access(global, R_OK) == 0) + return pa_xstrdup(global); + } + + 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) { @@ -1184,326 +1805,1065 @@ size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) { if ((b = hexc(*(p++))) < 0) return (size_t) -1; - d[j] = (uint8_t) (b << 4); + 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; + } + + 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) { + 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; + } +} + +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[]) { + 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; + } + + 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[]) { + 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); +} + +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[]) { + 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; + } + } + + return 0; +} + +void pa_set_env(const char *key, const char *value) { + pa_assert(key); + pa_assert(value); + + /* This is not thread-safe */ + + putenv(pa_sprintf_malloc("%s=%s", key, value)); +} + +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; + + unsetenv(s); + pa_xfree(s); + } +} + +pa_bool_t pa_in_system_mode(void) { + const char *e; + + if (!(e = getenv("PULSE_SYSTEM"))) + return FALSE; + + return !!atoi(e); +} + +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); - if (!*p) - return (size_t) -1; + if (k <= 0) +#endif + k = 32; - if ((b = hexc(*(p++))) < 0) - return (size_t) -1; + u = pa_xnew(char, k+1); - d[j] |= (uint8_t) b; - j++; + if (!(pa_get_user_name(u, k))) { + pa_xfree(u); + return NULL; } - return j; + return u; } -/* Returns nonzero when *s starts with *pfx */ -int pa_startswith(const char *s, const char *pfx) { +char *pa_get_host_name_malloc(void) { size_t l; - pa_assert(s); - pa_assert(pfx); + l = 100; + for (;;) { + char *c; - l = strlen(pfx); + c = pa_xmalloc(l); - return strlen(s) >= l && strncmp(s, pfx, l) == 0; -} + if (!pa_get_host_name(c, l)) { -/* Returns nonzero when *s ends with *sfx */ -int pa_endswith(const char *s, const char *sfx) { - size_t l1, l2; + if (errno != EINVAL && errno != ENAMETOOLONG) + break; - pa_assert(s); - pa_assert(sfx); + } else if (strlen(c) < l-1) { + char *u; - l1 = strlen(s); - l2 = strlen(sfx); + if (*c == 0) { + pa_xfree(c); + break; + } - return l1 >= l2 && strcmp(s+l1-l2, sfx) == 0; + u = pa_utf8_filter(c); + pa_xfree(c); + return u; + } + + /* 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; } -/* if fn is null return the PulseAudio run time path in s (/tmp/pulse) - * if fn is non-null and starts with / return fn in s - * otherwise append fn to the run time path and return it in s */ -char *pa_runtime_path(const char *fn, char *s, size_t l) { - const char *e; +char *pa_machine_id(void) { + FILE *f; + char *h; -#ifndef OS_IS_WIN32 - if (fn && *fn == '/') -#else - if (fn && strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\') -#endif - return pa_strlcpy(s, fn, l); + /* 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 ((e = getenv("PULSE_RUNTIME_PATH"))) { + if ((f = fopen(PA_MACHINE_ID, "r"))) { + char ln[34] = "", *r; - if (fn) - pa_snprintf(s, l, "%s%c%s", e, PA_PATH_SEP_CHAR, fn); - else - pa_snprintf(s, l, "%s", e); + r = fgets(ln, sizeof(ln)-1, f); + fclose(f); - } else { - char u[256]; + pa_strip_nl(ln); - if (fn) - pa_snprintf(s, l, "%s%s%c%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)), PA_PATH_SEP_CHAR, fn); - else - pa_snprintf(s, l, "%s%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u))); + if (r && ln[0]) + return pa_utf8_filter(ln); } + if ((h = pa_get_host_name_malloc())) + return h; -#ifdef OS_IS_WIN32 - { - char buf[l]; - strcpy(buf, s); - ExpandEnvironmentStrings(buf, s, l); - } -#endif + /* 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); +} - return s; +char *pa_session_id(void) { + const char *e; + + if (!(e = getenv("XDG_SESSION_COOKIE"))) + return NULL; + + return pa_utf8_filter(e); } -/* 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; +char *pa_uname_string(void) { + struct utsname u; - pa_assert(s); - pa_assert(ret_i); + pa_assert_se(uname(&u) >= 0); - errno = 0; - l = strtol(s, &x, 0); + return pa_sprintf_malloc("%s %s %s %s", u.sysname, u.machine, u.release, u.version); +} - if (!x || *x || errno != 0) - return -1; +#ifdef HAVE_VALGRIND_MEMCHECK_H +pa_bool_t pa_in_valgrind(void) { + static int b = 0; - if ((int32_t) l != l) - return -1; + /* To make heisenbugs a bit simpler to find we check for $VALGRIND + * here instead of really checking whether we run in valgrind or + * not. */ - *ret_i = (int32_t) l; + if (b < 1) + b = getenv("VALGRIND") ? 2 : 1; - return 0; + return b > 1; } +#endif -/* 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; +unsigned pa_gcd(unsigned a, unsigned b) { - pa_assert(s); - pa_assert(ret_u); + while (b > 0) { + unsigned t = b; + b = a % b; + a = t; + } - errno = 0; - l = strtoul(s, &x, 0); + return a; +} - if (!x || *x || errno != 0) - return -1; +void pa_reduce(unsigned *num, unsigned *den) { - if ((uint32_t) l != l) - return -1; + unsigned gcd = pa_gcd(*num, *den); - *ret_u = (uint32_t) l; + if (gcd <= 0) + return; - return 0; + *num /= gcd; + *den /= gcd; + + pa_assert(pa_gcd(*num, *den) == 1); } -#ifdef HAVE_STRTOF_L -static locale_t c_locale = NULL; +unsigned pa_ncpus(void) { + long ncpus; -static void c_locale_destroy(void) { - freelocale(c_locale); -} +#ifdef _SC_NPROCESSORS_CONF + ncpus = sysconf(_SC_NPROCESSORS_CONF); +#else + ncpus = 1; #endif -int pa_atof(const char *s, float *ret_f) { - char *x = NULL; - float f; - int r = 0; - - pa_assert(s); - pa_assert(ret_f); + return ncpus <= 0 ? 1 : (unsigned) ncpus; +} - /* This should be locale independent */ +char *pa_replace(const char*s, const char*a, const char *b) { + pa_strbuf *sb; + size_t an; -#ifdef HAVE_STRTOF_L + pa_assert(s); + pa_assert(a); + pa_assert(b); - PA_ONCE_BEGIN { + an = strlen(a); + sb = pa_strbuf_new(); - if ((c_locale = newlocale(LC_ALL_MASK, "C", NULL))) - atexit(c_locale_destroy); + for (;;) { + const char *p; - } PA_ONCE_END; + if (!(p = strstr(s, a))) + break; - if (c_locale) { - errno = 0; - f = strtof_l(s, &x, c_locale); - } else -#endif - { - errno = 0; -#ifdef HAVE_STRTOF - f = strtof(s, &x); -#else - f = strtod(s, &x); -#endif + pa_strbuf_putsn(sb, s, p-s); + pa_strbuf_puts(sb, b); + s = p + an; } - if (!x || *x || errno != 0) - r = -1; - else - *ret_f = f; + pa_strbuf_puts(sb, s); - return r; + return pa_strbuf_tostring_free(sb); } -/* Same as snprintf, but guarantees NUL-termination on every platform */ -int pa_snprintf(char *str, size_t size, const char *format, ...) { - int ret; - va_list ap; +char *pa_unescape(char *p) { + char *s, *d; + pa_bool_t escaped = FALSE; - pa_assert(str); - pa_assert(size > 0); - pa_assert(format); + for (s = p, d = p; *s; s++) { + if (!escaped && *s == '\\') { + escaped = TRUE; + continue; + } - va_start(ap, format); - ret = vsnprintf(str, size, format, ap); - va_end(ap); + *(d++) = *s; + escaped = FALSE; + } - str[size-1] = 0; + *d = 0; - return ret; + return p; } -/* 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)); +char *pa_realpath(const char *path) { + char *t; + pa_assert(path); - if (strlen(c) <= l) - return c; + /* We want only abolsute paths */ + if (path[0] != '/') { + errno = EINVAL; + return NULL; + } - c[l] = 0; +#if defined(__GLIBC__) || defined(__APPLE__) + { + char *r; - while (l > 0 && !pa_utf8_valid(c)) - c[--l] = 0; + if (!(r = realpath(path, NULL))) + return NULL; - return c; + /* 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); + + if (!(t = realpath(path, path_buf))) { + pa_xfree(path_buf); + return NULL; + } + } +#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 + + return t; } -char *pa_getcwd(void) { - size_t l = 128; +void pa_disable_sigpipe(void) { - for (;;) { - char *p = pa_xnew(char, l); - if (getcwd(p, l)) - return p; +#ifdef SIGPIPE + struct sigaction sa; - if (errno != ERANGE) - return NULL; + pa_zero(sa); - pa_xfree(p); - l *= 2; + if (sigaction(SIGPIPE, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; } -} -char *pa_make_path_absolute(const char *p) { - char *r; - char *cwd; + sa.sa_handler = SIG_IGN; - pa_assert(p); + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } +#endif +} - if (p[0] == '/') - return pa_xstrdup(p); +void pa_xfreev(void**a) { + void **p; - if (!(cwd = pa_getcwd())) - return pa_xstrdup(p); + if (!a) + return; - r = pa_sprintf_malloc("%s/%s", cwd, p); - pa_xfree(cwd); - return r; + for (p = a; *p; p++) + pa_xfree(*p); + + pa_xfree(a); } -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; +char **pa_split_spaces_strv(const char *s) { + char **t, *e; + unsigned i = 0, n = 8; + const char *state = NULL; - pa_assert(p); - pa_assert(l > 0); + t = pa_xnew(char*, n); + while ((e = pa_split_spaces(s, &state))) { + t[i++] = e; - a = PA_PAGE_ALIGN_PTR(p); - size = (const uint8_t*) p + l - (const uint8_t*) a; + if (i >= n) { + n *= 2; + t = pa_xrenew(char*, t, n); + } + } -#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; + if (i <= 0) { + pa_xfree(t); + return NULL; } -#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." */ + t[i] = NULL; + return t; +} -#ifdef RLIMIT_MEMLOCK - pa_assert_se(getrlimit(RLIMIT_MEMLOCK, &rlim) == 0); +char* pa_maybe_prefix_path(const char *path, const char *prefix) { + pa_assert(path); - 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)); - return (void*) p; - } + 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; - bs = PA_PAGE_ALIGN(rlim.rlim_cur); + if ((n = fpathconf(fd, _PC_PIPE_BUF)) >= 0) + return (size_t) n; +#endif + +#ifdef PIPE_BUF + return PIPE_BUF; #else - bs = PA_PAGE_SIZE*4; + return 4096; #endif +} - pa_log_debug("posix_madvise() failed (or doesn't exist), trying mlock(): %s", pa_cstrerror(r)); +void pa_reset_personality(void) { -#ifdef HAVE_MLOCK - while (size > 0 && bs > 0) { +#ifdef __linux__ + if (personality(PER_LINUX) < 0) + pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); +#endif - if (bs > size) - bs = size; +} - if (mlock(a, bs) < 0) { - bs = PA_PAGE_ALIGN(bs / 2); - continue; - } +#if defined(__linux__) && !defined(__OPTIMIZE__) - pa_assert_se(munlock(a, bs) == 0); +pa_bool_t pa_run_from_build_tree(void) { + char *rp; + pa_bool_t b = FALSE; - a = (const uint8_t*) a + bs; - size -= bs; + /* 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 - 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!"); +const char *pa_get_temp_dir(void) { + const char *t; - return (void*) p; -} + if ((t = getenv("TMPDIR")) && + pa_is_path_absolute(t)) + return t; -void pa_close_pipe(int fds[2]) { - pa_assert(fds); + if ((t = getenv("TMP")) && + pa_is_path_absolute(t)) + return t; - if (fds[0] >= 0) - pa_assert_se(pa_close(fds[0]) == 0); + if ((t = getenv("TEMP")) && + pa_is_path_absolute(t)) + return t; - if (fds[1] >= 0) - pa_assert_se(pa_close(fds[1]) == 0); + if ((t = getenv("TEMPDIR")) && + pa_is_path_absolute(t)) + return t; - fds[0] = fds[1] = -1; + return "/tmp"; }