| /*** | |
| 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, see <http://www.gnu.org/licenses/>. | |
| ***/ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <math.h> | |
| #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_STRTOD_L | |
| #ifdef HAVE_LOCALE_H | |
| #include <locale.h> | |
| #endif | |
| #ifdef HAVE_XLOCALE_H | |
| #include <xlocale.h> | |
| #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> | |
| #include <shlobj.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 HAVE_DBUS | |
| #include <pulsecore/rtkit.h> | |
| #endif | |
| #if defined(__linux__) && !defined(__ANDROID__) | |
| #include <sys/personality.h> | |
| #endif | |
| #ifdef HAVE_CPUID_H | |
| #include <cpuid.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/pipe.h> | |
| #include <pulsecore/once.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 | |
| static fd_set nonblocking_fds; | |
| #endif | |
| #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 && pa_streq(p + 1, "bin")) | |
| *p = '\0'; | |
| } | |
| return toplevel; | |
| } | |
| char *pa_win32_get_system_appdata() { | |
| static char appdata[MAX_PATH] = {0}; | |
| if (!*appdata && SHGetFolderPathAndSubDirA(NULL, CSIDL_COMMON_APPDATA|CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, "PulseAudio", appdata) != S_OK) | |
| return NULL; | |
| return appdata; | |
| } | |
| #endif | |
| static void set_nonblock(int fd, bool nonblock) { | |
| #ifdef O_NONBLOCK | |
| int v, nv; | |
| pa_assert(fd >= 0); | |
| pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); | |
| if (nonblock) | |
| nv = v | O_NONBLOCK; | |
| else | |
| nv = v & ~O_NONBLOCK; | |
| if (v != nv) | |
| pa_assert_se(fcntl(fd, F_SETFL, nv) >= 0); | |
| #elif defined(OS_IS_WIN32) | |
| u_long arg; | |
| if (nonblock) | |
| arg = 1; | |
| else | |
| arg = 0; | |
| if (ioctlsocket(fd, FIONBIO, &arg) < 0) { | |
| pa_assert_se(WSAGetLastError() == WSAENOTSOCK); | |
| pa_log_warn("Only sockets can be made non-blocking!"); | |
| return; | |
| } | |
| /* There is no method to query status, so we remember all fds */ | |
| if (nonblock) | |
| FD_SET(fd, &nonblocking_fds); | |
| else | |
| FD_CLR(fd, &nonblocking_fds); | |
| #else | |
| pa_log_warn("Non-blocking I/O not supported.!"); | |
| #endif | |
| } | |
| /** Make a file descriptor nonblock. Doesn't do any error checking */ | |
| void pa_make_fd_nonblock(int fd) { | |
| set_nonblock(fd, true); | |
| } | |
| /** Make a file descriptor blocking. Doesn't do any error checking */ | |
| void pa_make_fd_block(int fd) { | |
| set_nonblock(fd, false); | |
| } | |
| /** Query if a file descriptor is non-blocking */ | |
| bool pa_is_fd_nonblock(int fd) { | |
| #ifdef O_NONBLOCK | |
| int v; | |
| pa_assert(fd >= 0); | |
| pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); | |
| return !!(v & O_NONBLOCK); | |
| #elif defined(OS_IS_WIN32) | |
| return !!FD_ISSET(fd, &nonblocking_fds); | |
| #else | |
| return false; | |
| #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. Will create parent directories recursively if | |
| * required. This will not update permissions on parent directories if they | |
| * already exist, however. */ | |
| int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid, bool update_perms) { | |
| struct stat st; | |
| int r, saved_errno; | |
| bool retry = true; | |
| pa_assert(dir); | |
| again: | |
| #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 == ENOENT && retry) { | |
| /* If a parent directory in the path doesn't exist, try to create that | |
| * first, then try again. */ | |
| pa_make_secure_parent_dir(dir, m, uid, gid, false); | |
| retry = false; | |
| goto again; | |
| } | |
| if (r < 0 && errno != EEXIST) | |
| return -1; | |
| #if defined(HAVE_FSTAT) && !defined(OS_IS_WIN32) | |
| { | |
| int fd; | |
| 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; | |
| } | |
| if (!update_perms) { | |
| pa_assert_se(pa_close(fd) >= 0); | |
| return 0; | |
| } | |
| #ifdef HAVE_FCHOWN | |
| if (uid == (uid_t) -1) | |
| uid = getuid(); | |
| if (gid == (gid_t) -1) | |
| gid = getgid(); | |
| if (((st.st_uid != uid) || (st.st_gid != gid)) && fchown(fd, uid, gid) < 0) { | |
| pa_assert_se(pa_close(fd) >= 0); | |
| goto fail; | |
| } | |
| #endif | |
| #ifdef HAVE_FCHMOD | |
| if ((st.st_mode & 07777) != m && fchmod(fd, m) < 0) { | |
| pa_assert_se(pa_close(fd) >= 0); | |
| goto fail; | |
| }; | |
| #endif | |
| pa_assert_se(pa_close(fd) >= 0); | |
| } | |
| #else | |
| pa_log_warn("Secure directory creation not supported on this platform."); | |
| #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, bool update_perms) { | |
| int ret = -1; | |
| char *dir; | |
| if (!(dir = pa_parent_dir(fn))) | |
| goto finish; | |
| if (pa_make_secure_dir(dir, m, uid, gid, update_perms) < 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) { | |
| errno = 0; | |
| #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(); | |
| if (errno == WSAEWOULDBLOCK) | |
| errno = EAGAIN; | |
| 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(); | |
| if (errno == WSAEWOULDBLOCK) | |
| errno = EAGAIN; | |
| 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, | |
| * 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 close function. Necessary since not all | |
| * systems treat all file descriptors equal. */ | |
| int pa_close(int fd) { | |
| #ifdef OS_IS_WIN32 | |
| int ret; | |
| FD_CLR(fd, &nonblocking_fds); | |
| if ((ret = closesocket(fd)) == 0) | |
| return 0; | |
| if (WSAGetLastError() != WSAENOTSOCK) { | |
| errno = WSAGetLastError(); | |
| return ret; | |
| } | |
| #endif | |
| 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 | |
| * 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, ...) { | |
| 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) { | |
| 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 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_private(DBUS_BUS_SYSTEM, &error))) { | |
| pa_log("Failed to connect to system bus: %s", 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_close(bus); | |
| 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%08lX", 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_thread_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 | |
| } | |
| /* Check whenever any substring in v matches the provided regex. */ | |
| int pa_match(const char *expr, const char *v) { | |
| #if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) | |
| int k; | |
| regex_t re; | |
| int r; | |
| pa_assert(expr); | |
| pa_assert(v); | |
| 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; | |
| #else | |
| errno = ENOSYS; | |
| return -1; | |
| #endif | |
| } | |
| /* Check whenever the provided regex pattern is valid. */ | |
| bool pa_is_regex_valid(const char *expr) { | |
| #if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) | |
| regex_t re; | |
| if (expr == NULL || regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) { | |
| return false; | |
| } | |
| regfree(&re); | |
| return true; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| /* Try to parse a boolean string value.*/ | |
| int pa_parse_boolean(const char *v) { | |
| pa_assert(v); | |
| /* First we check language independent */ | |
| if (pa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t") | |
| || !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on")) | |
| return 1; | |
| else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f") | |
| || !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off")) | |
| return 0; | |
| #ifdef HAVE_LANGINFO_H | |
| { | |
| const char *expr; | |
| /* And then we check language dependent */ | |
| 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; | |
| } | |
| /* Try to parse a volume string to pa_volume_t. The allowed formats are: | |
| * db, % and unsigned integer */ | |
| int pa_parse_volume(const char *v, pa_volume_t *volume) { | |
| int len; | |
| uint32_t i; | |
| double d; | |
| char str[64]; | |
| pa_assert(v); | |
| pa_assert(volume); | |
| len = strlen(v); | |
| if (len <= 0 || len >= 64) | |
| return -1; | |
| memcpy(str, v, len + 1); | |
| if (str[len - 1] == '%') { | |
| str[len - 1] = '\0'; | |
| if (pa_atod(str, &d) < 0) | |
| return -1; | |
| d = d / 100 * PA_VOLUME_NORM; | |
| if (d < 0 || d > PA_VOLUME_MAX) | |
| return -1; | |
| *volume = d; | |
| return 0; | |
| } | |
| if (len > 2 && (str[len - 1] == 'b' || str[len - 1] == 'B') && | |
| (str[len - 2] == 'd' || str[len - 2] == 'D')) { | |
| str[len - 2] = '\0'; | |
| if (pa_atod(str, &d) < 0) | |
| return -1; | |
| if (d > pa_sw_volume_to_dB(PA_VOLUME_MAX)) | |
| return -1; | |
| *volume = pa_sw_volume_from_dB(d); | |
| return 0; | |
| } | |
| if (pa_atou(v, &i) < 0 || !PA_VOLUME_IS_VALID(i)) | |
| return -1; | |
| *volume = i; | |
| return 0; | |
| } | |
| /* Split the specified string wherever one of the characters in delimiter | |
| * occurs. Each time it is called returns a newly allocated string | |
| * with pa_xmalloc(). The variable state points to, should be | |
| * initialized 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 the specified string wherever one of the characters in delimiter | |
| * occurs. Each time it is called returns a pointer to the substring within the | |
| * string and the length in 'n'. Note that the resultant string cannot be used | |
| * as-is without the length parameter, since it is merely pointing to a point | |
| * within the original string. The variable state points to, should be | |
| * initialized to NULL before the first call. */ | |
| const char *pa_split_in_place(const char *c, const char *delimiter, size_t *n, 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)++; | |
| *n = l; | |
| return current; | |
| } | |
| /* 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); | |
| } | |
| /* Similar to pa_split_spaces, except this returns a string in-place. | |
| Returned string is generally not NULL-terminated. | |
| See pa_split_in_place(). */ | |
| const char *pa_split_spaces_in_place(const char *c, size_t *n, 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; | |
| *n = l; | |
| return current; | |
| } | |
| 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 = pa_streq(name, group->gr_name); | |
| 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 specific 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 given 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) { | |
| errno = ENOTSUP; | |
| return -1; | |
| } | |
| int pa_uid_in_group(uid_t uid, const char *name) { | |
| errno = ENOTSUP; | |
| return -1; | |
| } | |
| gid_t pa_get_gid_of_group(const char *name) { | |
| errno = ENOTSUP; | |
| return (gid_t) -1; | |
| } | |
| int pa_check_in_group(gid_t g) { | |
| 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 was 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%08lX", !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; | |
| } | |
| /* 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 lock 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 int check_ours(const char *p) { | |
| struct stat st; | |
| pa_assert(p); | |
| if (stat(p, &st) < 0) | |
| return -errno; | |
| #ifdef HAVE_GETUID | |
| if (st.st_uid != getuid() && st.st_uid != 0) | |
| return -EACCES; | |
| #endif | |
| return 0; | |
| } | |
| static char *get_pulse_home(void) { | |
| char *h, *ret; | |
| int t; | |
| h = pa_get_home_dir_malloc(); | |
| if (!h) { | |
| pa_log_error("Failed to get home directory."); | |
| return NULL; | |
| } | |
| t = check_ours(h); | |
| if (t < 0 && t != -ENOENT) { | |
| pa_log_error("Home directory not accessible: %s", pa_cstrerror(-t)); | |
| pa_xfree(h); | |
| return NULL; | |
| } | |
| /* If the old directory exists, use it. */ | |
| ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h); | |
| pa_xfree(h); | |
| if (access(ret, F_OK) >= 0) | |
| return ret; | |
| free(ret); | |
| /* Otherwise go for the XDG compliant directory. */ | |
| if (pa_get_config_home_dir(&ret) < 0) | |
| return NULL; | |
| 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; | |
| /* If PULSE_STATE_PATH and PULSE_RUNTIME_PATH point to the same | |
| * dir then this will break. */ | |
| if (pa_make_secure_dir(d, 0700U, (uid_t) -1, (gid_t) -1, true) < 0) { | |
| pa_log_error("Failed to create secure directory (%s): %s", d, 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; | |
| } | |
| int pa_append_to_home_dir(const char *path, char **_r) { | |
| char *home_dir; | |
| pa_assert(path); | |
| pa_assert(_r); | |
| home_dir = pa_get_home_dir_malloc(); | |
| if (!home_dir) { | |
| pa_log("Failed to get home directory."); | |
| return -PA_ERR_NOENTITY; | |
| } | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", home_dir, path); | |
| pa_xfree(home_dir); | |
| return 0; | |
| } | |
| int pa_get_config_home_dir(char **_r) { | |
| const char *e; | |
| char *home_dir; | |
| pa_assert(_r); | |
| e = getenv("XDG_CONFIG_HOME"); | |
| if (e && *e) { | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", e); | |
| return 0; | |
| } | |
| home_dir = pa_get_home_dir_malloc(); | |
| if (!home_dir) | |
| return -PA_ERR_NOENTITY; | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP ".config" PA_PATH_SEP "pulse", home_dir); | |
| pa_xfree(home_dir); | |
| return 0; | |
| } | |
| int pa_get_data_home_dir(char **_r) { | |
| const char *e; | |
| char *home_dir; | |
| pa_assert(_r); | |
| e = getenv("XDG_DATA_HOME"); | |
| if (e && *e) { | |
| if (pa_is_path_absolute(e)) { | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "pulseaudio", e); | |
| return 0; | |
| } | |
| else | |
| pa_log_warn("Ignored non-absolute XDG_DATA_HOME value '%s'", e); | |
| } | |
| home_dir = pa_get_home_dir_malloc(); | |
| if (!home_dir) | |
| return -PA_ERR_NOENTITY; | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP ".local" PA_PATH_SEP "share" PA_PATH_SEP "pulseaudio", home_dir); | |
| pa_xfree(home_dir); | |
| return 0; | |
| } | |
| int pa_get_data_dirs(pa_dynarray **_r) { | |
| const char *e; | |
| const char *def = "/usr/local/share/:/usr/share/"; | |
| const char *p; | |
| const char *split_state = NULL; | |
| char *n; | |
| pa_dynarray *paths; | |
| pa_assert(_r); | |
| e = getenv("XDG_DATA_DIRS"); | |
| p = e && *e ? e : def; | |
| paths = pa_dynarray_new((pa_free_cb_t) pa_xfree); | |
| while ((n = pa_split(p, ":", &split_state))) { | |
| char *path; | |
| if (!pa_is_path_absolute(n)) { | |
| pa_log_warn("Ignored non-absolute path '%s' in XDG_DATA_DIRS", n); | |
| pa_xfree(n); | |
| continue; | |
| } | |
| path = pa_sprintf_malloc("%s" PA_PATH_SEP "pulseaudio", n); | |
| pa_xfree(n); | |
| pa_dynarray_append(paths, path); | |
| } | |
| if (pa_dynarray_size(paths) == 0) { | |
| pa_log_warn("XDG_DATA_DIRS contains no valid paths"); | |
| pa_dynarray_free(paths); | |
| return -PA_ERR_INVALID; | |
| } | |
| *_r = paths; | |
| return 0; | |
| } | |
| int pa_append_to_config_home_dir(const char *path, char **_r) { | |
| int r; | |
| char *config_home_dir; | |
| pa_assert(path); | |
| pa_assert(_r); | |
| r = pa_get_config_home_dir(&config_home_dir); | |
| if (r < 0) | |
| return r; | |
| *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", config_home_dir, path); | |
| pa_xfree(config_home_dir); | |
| return 0; | |
| } | |
| 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; | |
| mode_t m; | |
| /* The runtime directory shall contain dynamic data that needs NOT | |
| * to be kept across reboots and is usually 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 try XDG_RUNTIME_DIR first, and if that isn't | |
| * set create a directory in $HOME and link it to a random subdir | |
| * in /tmp, if it was not explicitly configured. */ | |
| m = pa_in_system_mode() ? 0755U : 0700U; | |
| /* Use the explicitly configured value if it is set */ | |
| d = getenv("PULSE_RUNTIME_PATH"); | |
| if (d) { | |
| if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) { | |
| pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| return pa_xstrdup(d); | |
| } | |
| /* Use the XDG standard for the runtime directory. */ | |
| d = getenv("XDG_RUNTIME_DIR"); | |
| if (d) { | |
| #ifdef HAVE_GETUID | |
| struct stat st; | |
| if (stat(d, &st) == 0 && st.st_uid != getuid()) { | |
| pa_log(_("XDG_RUNTIME_DIR (%s) is not owned by us (uid %d), but by uid %d! " | |
| "(This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)"), | |
| d, getuid(), st.st_uid); | |
| goto fail; | |
| } | |
| #endif | |
| k = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", d); | |
| if (pa_make_secure_dir(k, m, (uid_t) -1, (gid_t) -1, true) < 0) { | |
| pa_log_error("Failed to create secure directory (%s): %s", k, pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| return k; | |
| } | |
| /* XDG_RUNTIME_DIR wasn't set, use the old legacy fallback */ | |
| d = get_pulse_home(); | |
| if (!d) | |
| goto fail; | |
| if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) { | |
| pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno)); | |
| pa_xfree(d); | |
| goto fail; | |
| } | |
| mid = pa_machine_id(); | |
| if (!mid) { | |
| 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 already exists */ | |
| p = pa_readlink(k); | |
| if (!p) { | |
| 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 | |
| * Do not check again if it exists since it cannot be a symlink */ | |
| if (mkdir(k) < 0 && errno != EEXIST) | |
| 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 | |
| { | |
| struct stat st; | |
| 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 else 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); | |
| f = pa_fopen_cloexec(fn, "r"); | |
| } else if ((h = pa_get_home_dir_malloc())) { | |
| fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local); | |
| f = pa_fopen_cloexec(fn, "r"); | |
| if (!f) { | |
| free(lfn); | |
| fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".config/pulse" PA_PATH_SEP "%s", h, local); | |
| f = pa_fopen_cloexec(fn, "r"); | |
| } | |
| pa_xfree(h); | |
| } else | |
| return NULL; | |
| if (f) { | |
| 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 (j+2 < slength && i < dlength) { | |
| 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 */ | |
| bool 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 */ | |
| bool 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 && pa_streq(s + l1 - l2, sfx); | |
| } | |
| bool 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 runtime or state dir (depending on the | |
| * rt parameter). If fn is non-NULL and starts with /, return fn. Otherwise, | |
| * append fn to the runtime/state dir and return it. */ | |
| static char *get_path(const char *fn, bool prependmid, bool rt) { | |
| char *rtp; | |
| rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir(); | |
| if (fn) { | |
| char *r, *canonical_rtp; | |
| if (pa_is_path_absolute(fn)) { | |
| pa_xfree(rtp); | |
| return pa_xstrdup(fn); | |
| } | |
| if (!rtp) | |
| return NULL; | |
| /* Hopefully make the path smaller to avoid 108 char limit (fdo#44680) */ | |
| if ((canonical_rtp = pa_realpath(rtp))) { | |
| if (strlen(rtp) >= strlen(canonical_rtp)) | |
| pa_xfree(rtp); | |
| else { | |
| pa_xfree(canonical_rtp); | |
| canonical_rtp = rtp; | |
| } | |
| } else | |
| canonical_rtp = rtp; | |
| if (prependmid) { | |
| char *mid; | |
| if (!(mid = pa_machine_id())) { | |
| pa_xfree(canonical_rtp); | |
| return NULL; | |
| } | |
| r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-%s", canonical_rtp, mid, fn); | |
| pa_xfree(mid); | |
| } else | |
| r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", canonical_rtp, fn); | |
| pa_xfree(canonical_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, bool 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) { | |
| long l; | |
| pa_assert(s); | |
| pa_assert(ret_i); | |
| if (pa_atol(s, &l) < 0) | |
| return -1; | |
| if (l < INT32_MIN || l > INT32_MAX) { | |
| errno = ERANGE; | |
| return -1; | |
| } | |
| *ret_i = (int32_t) l; | |
| return 0; | |
| } | |
| enum numtype { | |
| NUMTYPE_UINT, | |
| NUMTYPE_INT, | |
| NUMTYPE_DOUBLE, | |
| }; | |
| /* A helper function for pa_atou() and friends. This does some common checks, | |
| * because our number parsing is more strict than the strtoX functions. | |
| * | |
| * Leading zeros are stripped from integers so that they don't get parsed as | |
| * octal (but "0x" is preserved for hexadecimal numbers). For NUMTYPE_INT the | |
| * zero stripping may involve allocating a new string, in which case it's | |
| * stored in tmp. Otherwise tmp is set to NULL. The caller needs to free tmp | |
| * after they're done with ret. When parsing other types than NUMTYPE_INT the | |
| * caller can pass NULL as tmp. | |
| * | |
| * The final string to parse is returned in ret. ret will point either inside | |
| * s or to tmp. */ | |
| static int prepare_number_string(const char *s, enum numtype type, char **tmp, const char **ret) { | |
| const char *original = s; | |
| bool negative = false; | |
| pa_assert(s); | |
| pa_assert(type != NUMTYPE_INT || tmp); | |
| pa_assert(ret); | |
| if (tmp) | |
| *tmp = NULL; | |
| /* The strtoX functions accept leading spaces, we don't. */ | |
| if (isspace((unsigned char) s[0])) | |
| return -1; | |
| /* The strtoX functions accept a plus sign, we don't. */ | |
| if (s[0] == '+') | |
| return -1; | |
| /* The strtoul and strtoull functions allow a minus sign even though they | |
| * parse an unsigned number. In case of a minus sign the original negative | |
| * number gets negated. We don't want that kind of behviour. */ | |
| if (type == NUMTYPE_UINT && s[0] == '-') | |
| return -1; | |
| /* The strtoX functions interpret the number as octal if it starts with | |
| * a zero. We prefer to use base 10, so we strip all leading zeros (if the | |
| * string starts with "0x", strtoul() interprets it as hexadecimal, which | |
| * is fine, because it's unambiguous unlike octal). | |
| * | |
| * While stripping the leading zeros, we have to remember to also handle | |
| * the case where the number is negative, which makes the zero skipping | |
| * code somewhat complex. */ | |
| /* Doubles don't need zero stripping, we can finish now. */ | |
| if (type == NUMTYPE_DOUBLE) | |
| goto finish; | |
| if (s[0] == '-') { | |
| negative = true; | |
| s++; /* Skip the minus sign. */ | |
| } | |
| /* Don't skip zeros if the string starts with "0x". */ | |
| if (s[0] == '0' && s[1] != 'x') { | |
| while (s[0] == '0' && s[1]) | |
| s++; /* Skip zeros. */ | |
| } | |
| if (negative) { | |
| s--; /* Go back one step, we need the minus sign back. */ | |
| /* If s != original, then we have skipped some zeros and we need to replace | |
| * the last skipped zero with a minus sign. */ | |
| if (s != original) { | |
| *tmp = pa_xstrdup(s); | |
| *tmp[0] = '-'; | |
| s = *tmp; | |
| } | |
| } | |
| finish: | |
| *ret = s; | |
| 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); | |
| if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| errno = 0; | |
| l = strtoul(s, &x, 0); | |
| /* If x doesn't point to the end of s, there was some trailing garbage in | |
| * the string. If x points to s, no conversion was done (empty string). */ | |
| if (!x || *x || x == s || errno) { | |
| if (!errno) | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| if (l > UINT32_MAX) { | |
| errno = ERANGE; | |
| return -1; | |
| } | |
| *ret_u = (uint32_t) l; | |
| return 0; | |
| } | |
| /* Convert the string s to an unsigned 64 bit integer in *ret_u */ | |
| int pa_atou64(const char *s, uint64_t *ret_u) { | |
| char *x = NULL; | |
| unsigned long long l; | |
| pa_assert(s); | |
| pa_assert(ret_u); | |
| if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| errno = 0; | |
| l = strtoull(s, &x, 0); | |
| /* If x doesn't point to the end of s, there was some trailing garbage in | |
| * the string. If x points to s, no conversion was done (empty string). */ | |
| if (!x || *x || x == s || errno) { | |
| if (!errno) | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| if (l > UINT64_MAX) { | |
| errno = ERANGE; | |
| return -1; | |
| } | |
| *ret_u = (uint64_t) l; | |
| return 0; | |
| } | |
| /* Convert the string s to a signed long integer in *ret_l. */ | |
| int pa_atol(const char *s, long *ret_l) { | |
| char *tmp; | |
| char *x = NULL; | |
| long l; | |
| pa_assert(s); | |
| pa_assert(ret_l); | |
| if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| errno = 0; | |
| l = strtol(s, &x, 0); | |
| /* If x doesn't point to the end of s, there was some trailing garbage in | |
| * the string. If x points to s, no conversion was done (at least an empty | |
| * string can trigger this). */ | |
| if (!x || *x || x == s || errno) { | |
| if (!errno) | |
| errno = EINVAL; | |
| pa_xfree(tmp); | |
| return -1; | |
| } | |
| pa_xfree(tmp); | |
| *ret_l = l; | |
| return 0; | |
| } | |
| /* Convert the string s to a signed 64 bit integer in *ret_l. */ | |
| int pa_atoi64(const char *s, int64_t *ret_l) { | |
| char *tmp; | |
| char *x = NULL; | |
| long long l; | |
| pa_assert(s); | |
| pa_assert(ret_l); | |
| if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| errno = 0; | |
| l = strtoll(s, &x, 0); | |
| /* If x doesn't point to the end of s, there was some trailing garbage in | |
| * the string. If x points to s, no conversion was done (at least an empty | |
| * string can trigger this). */ | |
| if (!x || *x || x == s || errno) { | |
| if (!errno) | |
| errno = EINVAL; | |
| pa_xfree(tmp); | |
| return -1; | |
| } | |
| pa_xfree(tmp); | |
| *ret_l = l; | |
| if (l < INT64_MIN || l > INT64_MAX) { | |
| errno = ERANGE; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| #ifdef HAVE_STRTOD_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); | |
| if (prepare_number_string(s, NUMTYPE_DOUBLE, NULL, &s) < 0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| /* This should be locale independent */ | |
| #ifdef HAVE_STRTOD_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 doesn't point to the end of s, there was some trailing garbage in | |
| * the string. If x points to s, no conversion was done (at least an empty | |
| * string can trigger this). */ | |
| if (!x || *x || x == s || errno) { | |
| if (!errno) | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| if (isnan(f)) { | |
| 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) { | |
| pa_xfree(p); | |
| 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 = ENOTSUP; | |
| size_t bs; | |
| const size_t page_size = pa_page_size(); | |
| 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 < 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 = 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; | |
| #if defined(__linux__) || defined(__sun) | |
| int saved_errno; | |
| DIR *d; | |
| if ((d = opendir("/proc/self/fd"))) { | |
| struct dirent *de; | |
| while ((de = readdir(d))) { | |
| bool 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; | |
| bool 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++) { | |
| bool 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 | |
| int kl = strlen(key); | |
| int vl = strlen(value); | |
| char *tmp = pa_xmalloc(kl+vl+2); | |
| memcpy(tmp, key, kl); | |
| memcpy(tmp+kl+1, value, vl); | |
| tmp[kl] = '='; | |
| tmp[kl+1+vl] = '\0'; | |
| putenv(tmp); | |
| /* Even though it should be safe to free it on Windows, we don't want to | |
| * rely on undocumented behaviour. */ | |
| #else | |
| setenv(key, value, 1); | |
| #endif | |
| } | |
| void pa_unset_env(const char *key) { | |
| pa_assert(key); | |
| /* This is not thread-safe */ | |
| #ifdef OS_IS_WIN32 | |
| int kl = strlen(key); | |
| char *tmp = pa_xmalloc(kl+2); | |
| memcpy(tmp, key, kl); | |
| tmp[kl] = '='; | |
| tmp[kl+1] = '\0'; | |
| putenv(tmp); | |
| /* Even though it should be safe to free it on Windows, we don't want to | |
| * rely on undocumented behaviour. */ | |
| #else | |
| unsetenv(key); | |
| #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; | |
| pa_unset_env(s); | |
| pa_xfree(s); | |
| } | |
| } | |
| bool pa_in_system_mode(void) { | |
| const char *e; | |
| if (!(e = getenv("PULSE_SYSTEM"))) | |
| return false; | |
| return !!atoi(e); | |
| } | |
| /* Checks a delimiters-separated list of words in haystack for needle */ | |
| bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle) { | |
| char *s; | |
| const char *state = NULL; | |
| if (!haystack || !needle) | |
| return false; | |
| while ((s = pa_split(haystack, delimiters, &state))) { | |
| if (pa_streq(needle, s)) { | |
| pa_xfree(s); | |
| return true; | |
| } | |
| pa_xfree(s); | |
| } | |
| return false; | |
| } | |
| /* Checks a whitespace-separated list of words in haystack for needle */ | |
| bool pa_str_in_list_spaces(const char *haystack, const char *needle) { | |
| const char *s; | |
| size_t n; | |
| const char *state = NULL; | |
| if (!haystack || !needle) | |
| return false; | |
| while ((s = pa_split_spaces_in_place(haystack, &n, &state))) { | |
| if (pa_strneq(needle, s, n)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| char* pa_str_strip_suffix(const char *str, const char *suffix) { | |
| size_t str_l, suf_l, prefix; | |
| char *ret; | |
| pa_assert(str); | |
| pa_assert(suffix); | |
| str_l = strlen(str); | |
| suf_l = strlen(suffix); | |
| if (str_l < suf_l) | |
| return NULL; | |
| prefix = str_l - suf_l; | |
| if (!pa_streq(&str[prefix], suffix)) | |
| return NULL; | |
| ret = pa_xmalloc(prefix + 1); | |
| strncpy(ret, str, prefix); | |
| ret[prefix] = '\0'; | |
| return ret; | |
| } | |
| 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 (k <= 0) | |
| #endif | |
| k = 32; | |
| u = pa_xnew(char, k+1); | |
| if (!(pa_get_user_name(u, k))) { | |
| pa_xfree(u); | |
| return NULL; | |
| } | |
| return u; | |
| } | |
| char *pa_get_host_name_malloc(void) { | |
| size_t l; | |
| l = 100; | |
| for (;;) { | |
| char *c; | |
| c = pa_xmalloc(l); | |
| if (!pa_get_host_name(c, l)) { | |
| if (errno != EINVAL && errno != ENAMETOOLONG) | |
| break; | |
| } else if (strlen(c) < l-1) { | |
| char *u; | |
| if (*c == 0) { | |
| pa_xfree(c); | |
| break; | |
| } | |
| 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; | |
| } | |
| 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 if the machine-id | |
| * file is available. If it's available, that's great, since it provides an | |
| * identifier that suits our needs perfectly. If it's not, we fall back to | |
| * the hostname, which is not as good, since it can change over time. */ | |
| /* We search for the machine-id file from four locations. The first two are | |
| * relative to the configured installation prefix, but if we're installed | |
| * under /usr/local, for example, it's likely that the machine-id won't be | |
| * found there, so we also try the hardcoded paths. | |
| * | |
| * PA_MACHINE_ID or PA_MACHINE_ID_FALLBACK might exist on a Windows system, | |
| * but the last two hardcoded paths certainly don't, hence we don't try | |
| * them on Windows. */ | |
| if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r")) || | |
| (f = pa_fopen_cloexec(PA_MACHINE_ID_FALLBACK, "r")) || | |
| #if !defined(OS_IS_WIN32) | |
| (f = pa_fopen_cloexec("/etc/machine-id", "r")) || | |
| (f = pa_fopen_cloexec("/var/lib/dbus/machine-id", "r")) | |
| #else | |
| false | |
| #endif | |
| ) { | |
| 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; | |
| #if !defined(OS_IS_WIN32) && !defined(__ANDROID__) | |
| /* 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 | |
| } | |
| char *pa_session_id(void) { | |
| const char *e; | |
| e = getenv("XDG_SESSION_ID"); | |
| if (!e) | |
| 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 %lu.%lu (%lu) %s", i.dwMajorVersion, i.dwMinorVersion, i.dwBuildNumber, i.szCSDVersion); | |
| #endif | |
| } | |
| #ifdef HAVE_VALGRIND_MEMCHECK_H | |
| bool 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); | |
| } | |
| unsigned pa_ncpus(void) { | |
| long ncpus; | |
| #ifdef _SC_NPROCESSORS_ONLN | |
| ncpus = sysconf(_SC_NPROCESSORS_ONLN); | |
| #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(*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_to_string_free(sb); | |
| } | |
| char *pa_escape(const char *p, const char *chars) { | |
| const char *s; | |
| const char *c; | |
| char *out_string, *output; | |
| int char_count = strlen(p); | |
| /* Maximum number of characters in output string | |
| * including trailing 0. */ | |
| char_count = 2 * char_count + 1; | |
| /* allocate output string */ | |
| out_string = pa_xmalloc(char_count); | |
| output = out_string; | |
| /* write output string */ | |
| for (s = p; *s; ++s) { | |
| if (*s == '\\') | |
| *output++ = '\\'; | |
| else if (chars) { | |
| for (c = chars; *c; ++c) { | |
| if (*s == *c) { | |
| *output++ = '\\'; | |
| break; | |
| } | |
| } | |
| } | |
| *output++ = *s; | |
| } | |
| *output = 0; | |
| /* Remove trailing garbage */ | |
| output = pa_xstrdup(out_string); | |
| pa_xfree(out_string); | |
| return output; | |
| } | |
| char *pa_unescape(char *p) { | |
| char *s, *d; | |
| bool escaped = false; | |
| for (s = p, d = p; *s; s++) { | |
| if (!escaped && *s == '\\') { | |
| escaped = true; | |
| continue; | |
| } | |
| *(d++) = *s; | |
| escaped = false; | |
| } | |
| *d = 0; | |
| return p; | |
| } | |
| char *pa_realpath(const char *path) { | |
| char *t; | |
| pa_assert(path); | |
| /* We want only absolute paths */ | |
| if (path[0] != '/') { | |
| errno = EINVAL; | |
| return NULL; | |
| } | |
| #if defined(__GLIBC__) | |
| { | |
| char *r; | |
| 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); | |
| #if defined(OS_IS_WIN32) | |
| if (!(t = _fullpath(path_buf, path, _MAX_PATH))) { | |
| pa_xfree(path_buf); | |
| return NULL; | |
| } | |
| #else | |
| if (!(t = realpath(path, path_buf))) { | |
| pa_xfree(path_buf); | |
| return NULL; | |
| } | |
| #endif | |
| } | |
| #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; | |
| } | |
| 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 | |
| } | |
| 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) { | |
| #if defined(__linux__) && !defined(__ANDROID__) | |
| if (personality(PER_LINUX) < 0) | |
| pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); | |
| #endif | |
| } | |
| bool pa_run_from_build_tree(void) { | |
| static bool b = false; | |
| #ifdef HAVE_RUNNING_FROM_BUILD_TREE | |
| char *rp; | |
| PA_ONCE_BEGIN { | |
| if ((rp = pa_readlink("/proc/self/exe"))) { | |
| b = pa_startswith(rp, PA_BUILDDIR); | |
| pa_xfree(rp); | |
| } | |
| } PA_ONCE_END; | |
| #endif | |
| return b; | |
| } | |
| 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) | |
| goto finish; | |
| /* return error */ | |
| 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) | |
| goto finish; | |
| /* return error */ | |
| 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 == EMFILE) { | |
| pa_log_error("The per-process limit on the number of open file descriptors has been reached."); | |
| return r; | |
| } | |
| if (errno == ENFILE) { | |
| pa_log_error("The system-wide limit on the total number of open files has been reached."); | |
| return r; | |
| } | |
| if (errno != EINVAL && errno != ENOSYS) | |
| return r; | |
| #endif | |
| if ((r = pipe(pipefd)) >= 0) | |
| goto finish; | |
| if (errno == EMFILE) { | |
| pa_log_error("The per-process limit on the number of open file descriptors has been reached."); | |
| return r; | |
| } | |
| if (errno == ENFILE) { | |
| pa_log_error("The system-wide limit on the total number of open files has been reached."); | |
| return r; | |
| } | |
| /* return error */ | |
| 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; | |
| errno = 0; | |
| #ifdef HAVE_ACCEPT4 | |
| if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0) | |
| goto finish; | |
| if (errno != EINVAL && errno != ENOSYS) | |
| return fd; | |
| #endif | |
| #ifdef HAVE_PACCEPT | |
| if ((fd = paccept(sockfd, addr, addrlen, NULL, SOCK_CLOEXEC)) >= 0) | |
| goto finish; | |
| #endif | |
| if ((fd = accept(sockfd, addr, addrlen)) >= 0) | |
| goto finish; | |
| /* return error */ | |
| 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); | |
| } | |
| bool pa_running_in_vm(void) { | |
| #if defined(__i386__) || defined(__x86_64__) | |
| /* Both CPUID and DMI are x86 specific interfaces... */ | |
| #ifdef HAVE_CPUID_H | |
| unsigned int eax, ebx, ecx, edx; | |
| #endif | |
| #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 | |
| #ifdef HAVE_CPUID_H | |
| /* Hypervisors provide presence on 0x1 cpuid leaf. | |
| * http://lwn.net/Articles/301888/ */ | |
| if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) | |
| return false; | |
| if (ecx & 0x80000000) | |
| return true; | |
| #endif /* HAVE_CPUID_H */ | |
| #endif /* defined(__i386__) || defined(__x86_64__) */ | |
| return false; | |
| } | |
| size_t pa_page_size(void) { | |
| #if defined(PAGE_SIZE) | |
| return PAGE_SIZE; | |
| #elif defined(PAGESIZE) | |
| return PAGESIZE; | |
| #elif defined(HAVE_SYSCONF) | |
| static size_t page_size = 4096; /* Let's hope it's like x86. */ | |
| PA_ONCE_BEGIN { | |
| long ret = sysconf(_SC_PAGE_SIZE); | |
| if (ret > 0) | |
| page_size = ret; | |
| } PA_ONCE_END; | |
| return page_size; | |
| #else | |
| return 4096; | |
| #endif | |
| } |