X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/f5046759cdd72daf5ba3b31c9dfc7b8d5be6bc9b..5610d41482df995baaf510308e07ccbe04c9e18b:/src/pulsecore/core-util.c diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index d64c7388..3e2c615f 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -34,16 +34,24 @@ #include #include #include -#include #include #include #include -#include #include -#include + +#ifdef HAVE_LANGINFO_H #include +#endif + +#ifdef HAVE_UNAME #include -#include +#endif + +#if defined(HAVE_REGEX_H) +#include +#elif defined(HAVE_PCREPOSIX_H) +#include +#endif #ifdef HAVE_STRTOF_L #include @@ -81,6 +89,10 @@ #include #endif +#ifndef ENOTSUP +#define ENOTSUP 135 +#endif + #ifdef HAVE_PWD_H #include #endif @@ -95,13 +107,17 @@ #ifdef __APPLE__ #include +#include +#include +#include +#include #endif #ifdef HAVE_DBUS #include "rtkit.h" #endif -#ifdef __linux__ +#if defined(__linux__) && !defined(__ANDROID__) #include #endif @@ -110,13 +126,15 @@ #include #include -#include +#include #include #include #include #include #include #include +#include +#include #include "core-util.h" @@ -125,58 +143,114 @@ #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 -#define PULSE_ROOTENV "PULSE_ROOT" +#ifdef OS_IS_WIN32 -int pa_set_root(HANDLE handle) { - char library_path[MAX_PATH + sizeof(PULSE_ROOTENV) + 1], *sep; +/* 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; - strcpy(library_path, PULSE_ROOTENV "="); + if (!toplevel) { + char library_path[MAX_PATH]; + char *p; - /* FIXME: Needs to set errno */ + if (!GetModuleFileName(handle, library_path, MAX_PATH)) + return NULL; - if (!GetModuleFileName(handle, library_path + sizeof(PULSE_ROOTENV), MAX_PATH)) - return 0; + toplevel = pa_xstrdup(library_path); - sep = strrchr(library_path, PA_PATH_SEP_CHAR); - if (sep) - *sep = '\0'; + p = strrchr(toplevel, PA_PATH_SEP_CHAR); + if (p) + *p = '\0'; - if (_putenv(library_path) < 0) - return 0; + p = strrchr(toplevel, PA_PATH_SEP_CHAR); + if (p && pa_streq(p + 1, "bin")) + *p = '\0'; + } - return 1; + return toplevel; } #endif -/** Make a file descriptor nonblock. Doesn't do any error checking */ -void pa_make_fd_nonblock(int fd) { +static void set_nonblock(int fd, bool nonblock) { #ifdef O_NONBLOCK - int v; + int v, nv; pa_assert(fd >= 0); pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); - if (!(v & O_NONBLOCK)) + if (nonblock) + nv = v | O_NONBLOCK; + else + nv = v & ~O_NONBLOCK; + + if (v != nv) pa_assert_se(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0); #elif defined(OS_IS_WIN32) - u_long arg = 1; + 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) { @@ -192,37 +266,88 @@ 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) { +/** 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; -#ifdef HAVE_CHOWN - if (uid == (uid_t)-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) + if (gid == (gid_t) -1) gid = getgid(); - (void) chown(dir, uid, gid); + 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 + (void) fchmod(fd, m); #endif -#ifdef HAVE_CHMOD - chmod(dir, m); + pa_assert_se(pa_close(fd) >= 0); +} #endif #ifdef HAVE_LSTAT @@ -269,14 +394,14 @@ char *pa_parent_dir(const char *fn) { } /* Creates a the parent directory of the specified path securely */ -int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid) { +int 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) < 0) + if (pa_make_secure_dir(dir, m, uid, gid, update_perms) < 0) goto finish; ret = 0; @@ -430,13 +555,15 @@ ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { return ret; } -/** Platform independent read function. Necessary since not all +/** 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; @@ -501,7 +628,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, ...) { - size_t size = 100; + size_t size = 100; char *c = NULL; pa_assert(format); @@ -531,7 +658,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) { - size_t size = 100; + size_t size = 100; char *c = NULL; pa_assert(format); @@ -577,10 +704,16 @@ char *pa_strlcpy(char *b, const char *s, size_t l) { return b; } +#ifdef _POSIX_PRIORITY_SCHEDULING static int set_scheduler(int rtprio) { +#ifdef HAVE_SCHED_H struct sched_param sp; - int r; #ifdef HAVE_DBUS + int r; + long long rttime; +#ifdef RLIMIT_RTTIME + struct rlimit rl; +#endif DBusError error; DBusConnection *bus; @@ -601,11 +734,12 @@ static int set_scheduler(int rtprio) { pa_log_debug("SCHED_RR worked."); return 0; } +#endif /* HAVE_SCHED_H */ #ifdef HAVE_DBUS /* Try to talk to RealtimeKit */ - if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) { + if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) { pa_log("Failed to connect to system bus: %s\n", error.message); dbus_error_free(&error); errno = -EIO; @@ -617,28 +751,82 @@ static int set_scheduler(int rtprio) { * 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); + rttime = rtkit_get_rttime_usec_max(bus); + if (rttime >= 0) { +#ifdef RLIMIT_RTTIME + r = getrlimit(RLIMIT_RTTIME, &rl); - if (r >= 0) { - pa_log_debug("RealtimeKit worked."); - return 0; + if (r >= 0 && (long long) rl.rlim_max > rttime) { + pa_log_info("Clamping rlimit-rttime to %lld for RealtimeKit\n", rttime); + rl.rlim_cur = rl.rlim_max = rttime; + r = setrlimit(RLIMIT_RTTIME, &rl); + + if (r < 0) + pa_log("setrlimit() failed: %s", pa_cstrerror(errno)); + } +#endif + r = rtkit_make_realtime(bus, 0, rtprio); + dbus_connection_close(bus); + dbus_connection_unref(bus); + + if (r >= 0) { + pa_log_debug("RealtimeKit worked."); + return 0; + } + + errno = -r; + } else { + dbus_connection_close(bus); + dbus_connection_unref(bus); + errno = -rttime; } - errno = -r; #else - errno = r; + errno = 0; #endif return -1; } +#endif /* Make the current thread a realtime thread, and acquire the highest * rtprio we can get that is less or equal the specified parameter. If * the thread is already realtime, don't do anything. */ int pa_make_realtime(int rtprio) { -#ifdef _POSIX_PRIORITY_SCHEDULING +#if defined(OS_IS_DARWIN) + struct thread_time_constraint_policy ttcpolicy; + uint64_t freq = 0; + size_t size = sizeof(freq); + int ret; + + ret = sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0); + if (ret < 0) { + pa_log_info("Unable to read CPU frequency, acquisition of real-time scheduling failed."); + return -1; + } + + pa_log_debug("sysctl for hw.cpufrequency: %llu", freq); + + /* See http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/KernelProgramming/scheduler/scheduler.html */ + ttcpolicy.period = freq / 160; + ttcpolicy.computation = freq / 3300; + ttcpolicy.constraint = freq / 2200; + ttcpolicy.preemptible = 1; + + ret = thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret) { + pa_log_info("Unable to set real-time thread priority (%08x).", ret); + return -1; + } + + pa_log_info("Successfully acquired real-time thread priority."); + return 0; + +#elif defined(_POSIX_PRIORITY_SCHEDULING) int p; if (set_scheduler(rtprio) >= 0) { @@ -647,20 +835,28 @@ int pa_make_realtime(int rtprio) { } for (p = rtprio-1; p >= 1; p--) - if (set_scheduler(p)) { + if (set_scheduler(p) >= 0) { pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio); return 0; } +#elif defined(OS_IS_WIN32) + /* Windows only allows realtime scheduling to be set on a per process basis. + * Therefore, instead of making the thread realtime, just give it the highest non-realtime priority. */ + if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) { + pa_log_info("Successfully enabled THREAD_PRIORITY_TIME_CRITICAL scheduling for thread."); + return 0; + } - pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno)); - return -1; + pa_log_warn("SetThreadPriority() failed: 0x%08X", GetLastError()); + errno = EPERM; #else - errno = ENOTSUP; - return -1; #endif + pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno)); + return -1; } +#ifdef HAVE_SYS_RESOURCE_H static int set_nice(int nice_level) { #ifdef HAVE_DBUS DBusError error; @@ -670,10 +866,12 @@ static int set_nice(int nice_level) { 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 */ @@ -703,6 +901,7 @@ static int set_nice(int nice_level) { return -1; } +#endif /* Raise the priority of the current process as much as possible that * is <= the specified nice level..*/ @@ -717,7 +916,7 @@ int pa_raise_priority(int nice_level) { } for (n = nice_level+1; n < 0; n++) - if (set_nice(n) > 0) { + 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; } @@ -759,6 +958,7 @@ void pa_reset_priority(void) { } 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; @@ -781,20 +981,28 @@ int pa_match(const char *expr, const char *v) { errno = EINVAL; return r; +#else + errno = ENOSYS; + return -1; +#endif } /* 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")) + /* 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 (!strcmp(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off")) + else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f") + || !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off")) return 0; - /* And then we check language dependant */ +#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) @@ -804,15 +1012,59 @@ int pa_parse_boolean(const char *v) { 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, ret = -1; + uint32_t i; + double d; + char str[64]; + + pa_assert(v); + pa_assert(volume); + + len = strlen(v); + + if (len >= 64) + return -1; + + memcpy(str, v, len + 1); + + if (str[len - 1] == '%') { + str[len - 1] = '\0'; + if (pa_atou(str, &i) == 0) { + *volume = PA_CLAMP_VOLUME((uint64_t) PA_VOLUME_NORM * i / 100); + ret = 0; + } + } else 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) { + *volume = pa_sw_volume_from_dB(d); + ret = 0; + } + } else { + if (pa_atou(v, &i) == 0) { + *volume= PA_CLAMP_VOLUME(i); + ret = 0; + } + + } + + return ret; +} + /* Split the specified string wherever one of the strings in delimiter * occurs. Each time it is called returns a newly allocated string * with pa_xmalloc(). The variable state points to, should be - * initiallized to NULL before the first call. */ + * 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; @@ -829,8 +1081,28 @@ char *pa_split(const char *c, const char *delimiter, const char**state) { return pa_xstrndup(current, l); } -/* What is interpreted as whitespace? */ -#define WHITESPACE " \t\n" +/* Split the specified string wherever one of the strings 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, int *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) { @@ -875,7 +1147,7 @@ const char *pa_sig2str(int sig) { } #else - switch(sig) { + switch (sig) { #ifdef SIGHUP case SIGHUP: return "SIGHUP"; #endif @@ -986,8 +1258,7 @@ static int is_group(gid_t gid, const char *name) { int r = -1; errno = 0; - if (!(group = pa_getgrgid_malloc(gid))) - { + if (!(group = pa_getgrgid_malloc(gid))) { if (!errno) errno = ENOENT; @@ -996,7 +1267,7 @@ static int is_group(gid_t gid, const char *name) { goto finish; } - r = strcmp(name, group->gr_name) == 0; + r = pa_streq(name, group->gr_name); finish: pa_getgrgid_free(group); @@ -1046,15 +1317,14 @@ finish: return r; } -/* Check whether the specifc user id is a member of the specified group */ +/* 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 (!(group = pa_getgrnam_malloc(name))) { if (!errno) errno = ENOENT; goto finish; @@ -1083,14 +1353,13 @@ finish: return r; } -/* Get the GID of a gfiven group, return (gid_t) -1 on failure. */ +/* 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 (!(gr = pa_getgrnam_malloc(name))) { if (!errno) errno = ENOENT; goto finish; @@ -1120,23 +1389,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; + errno = ENOTSUP; return -1; } int pa_uid_in_group(uid_t uid, const char *name) { - errno = ENOSUP; + errno = ENOTSUP; return -1; } gid_t pa_get_gid_of_group(const char *name) { - errno = ENOSUP; + errno = ENOTSUP; return (gid_t) -1; } int pa_check_in_group(gid_t g) { - errno = ENOSUP; + errno = ENOTSUP; return -1; } @@ -1158,18 +1427,18 @@ int pa_lock_fd(int fd, int b) { 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. */ + /* 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)); + pa_log("%slock: %s", !b ? "un" : "", pa_cstrerror(errno)); #endif #ifdef OS_IS_WIN32 - HANDLE h = (HANDLE)_get_osfhandle(fd); + HANDLE h = (HANDLE) _get_osfhandle(fd); if (b && LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)) return 0; @@ -1188,7 +1457,27 @@ int pa_lock_fd(int fd, int b) { char* pa_strip_nl(char *s) { pa_assert(s); - s[strcspn(s, "\r\n")] = 0; + 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; } @@ -1200,10 +1489,7 @@ int pa_lock_lockfile(const char *fn) { for (;;) { struct stat st; - if ((fd = open(fn, O_CREAT|O_RDWR -#ifdef O_NOCTTY - |O_NOCTTY -#endif + if ((fd = pa_open_cloexec(fn, O_CREAT|O_RDWR #ifdef O_NOFOLLOW |O_NOFOLLOW #endif @@ -1252,7 +1538,7 @@ fail: return -1; } -/* Unlock a temporary lcok file */ +/* Unlock a temporary lock file */ int pa_unlock_lockfile(const char *fn, int fd) { int r = 0; pa_assert(fd >= 0); @@ -1277,31 +1563,62 @@ int pa_unlock_lockfile(const char *fn, int fd) { return r; } -static char *get_pulse_home(void) { - char *h; +static char *get_config_home(char *home) { + char *t; + + t = getenv("XDG_CONFIG_HOME"); + if (t) + return pa_xstrdup(t); + + return pa_sprintf_malloc("%s" PA_PATH_SEP ".config", home); +} + +static int check_ours(const char *p) { struct stat st; - char *ret = NULL; - if (!(h = pa_get_home_dir_malloc())) { + pa_assert(p); + + if (stat(p, &st) < 0) + return -errno; + +#ifdef HAVE_GETUID + if (st.st_uid != getuid()) + return -EACCES; +#endif + + return 0; +} + +static char *get_pulse_home(void) { + char *h, *ret, *config_home; + int t; + + h = pa_get_home_dir_malloc(); + if (!h) { pa_log_error("Failed to get home directory."); return NULL; } - if (stat(h, &st) < 0) { - pa_log_error("Failed to stat home directory %s: %s", h, pa_cstrerror(errno)); - goto finish; - } - - if (st.st_uid != getuid()) { - pa_log_error("Home directory %s not ours.", h); - errno = EACCES; - goto finish; + 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); + if (access(ret, F_OK) >= 0) { + free(h); + return ret; + } + free(ret); -finish: - pa_xfree(h); + /* Otherwise go for the XDG compliant directory. */ + config_home = get_config_home(h); + free(h); + ret = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", config_home); + free(config_home); return ret; } @@ -1319,8 +1636,8 @@ char *pa_get_state_dir(void) { /* 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) < 0) { - pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno)); + 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; } @@ -1378,19 +1695,10 @@ static char* make_random_dir(mode_t m) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; - const char *tmpdir; char *fn; size_t pathlen; - if (!(tmpdir = getenv("TMPDIR"))) - if (!(tmpdir = getenv("TMP"))) - if (!(tmpdir = getenv("TEMP"))) - tmpdir = getenv("TEMPDIR"); - - if (!tmpdir || !pa_is_path_absolute(tmpdir)) - tmpdir = "/tmp"; - - fn = pa_sprintf_malloc("%s/pulse-XXXXXXXXXXXX", tmpdir); + fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir()); pathlen = strlen(fn); for (;;) { @@ -1403,7 +1711,11 @@ static char* make_random_dir(mode_t m) { 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); @@ -1426,6 +1738,7 @@ static int make_random_dir_and_link(mode_t m, const char *k) { if (!(p = make_random_dir(m))) return -1; +#ifdef HAVE_SYMLINK if (symlink(p, k) < 0) { int saved_errno = errno; @@ -1438,6 +1751,10 @@ static int make_random_dir_and_link(mode_t m, const char *k) { errno = saved_errno; return -1; } +#else + pa_xfree(p); + return -1; +#endif pa_xfree(p); return 0; @@ -1445,38 +1762,56 @@ static int make_random_dir_and_link(mode_t m, const char *k) { 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, + * 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 link it to a random subdir in /tmp, if it - * was not explicitly configured. */ + * 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; - if ((d = getenv("PULSE_RUNTIME_PATH"))) { + /* 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) < 0) { - pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno)); + 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); } - if (!(d = get_pulse_home())) + /* Use the XDG standard for the runtime directory. */ + d = getenv("XDG_RUNTIME_DIR"); + if (d) { + 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) < 0) { - pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno)); + 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; } - if (!(mid = pa_machine_id())) { + mid = pa_machine_id(); + if (!mid) { pa_xfree(d); goto fail; } @@ -1486,16 +1821,17 @@ char *pa_get_runtime_dir(void) { pa_xfree(mid); for (;;) { - /* OK, first let's check if the "runtime" symlink is already - * existant */ + /* OK, first let's check if the "runtime" symlink already exists */ - if (!(p = pa_readlink(k))) { + 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 */ @@ -1508,6 +1844,12 @@ char *pa_get_runtime_dir(void) { 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; } @@ -1519,9 +1861,10 @@ char *pa_get_runtime_dir(void) { goto fail; } - /* Hmm, so this symlink is still around, make sure nobody fools - * us */ - + /* 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) { @@ -1541,6 +1884,8 @@ char *pa_get_runtime_dir(void) { pa_log_info("Hmm, runtime path exists, but points to an invalid directory. Changing runtime directory."); } +} +#endif pa_xfree(p); p = NULL; @@ -1562,7 +1907,7 @@ char *pa_get_runtime_dir(void) { pa_xfree(t); t = NULL; - /* Hmm, someone lese was quicker then us. Let's give + /* Hmm, someone else was quicker then us. Let's give * him some time to finish, and retry. */ pa_msleep(10); continue; @@ -1595,24 +1940,10 @@ fail: * 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 + FILE *f; 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 ((f = pa_fopen_cloexec(fn, "r"))) { if (result) *result = pa_xstrdup(fn); @@ -1627,26 +1958,23 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env const char *e; char *lfn; char *h; - FILE *f; - if ((e = getenv("PULSE_CONFIG_PATH"))) + 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())) { + 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; -#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 (f) { if (result) *result = pa_xstrdup(fn); @@ -1664,22 +1992,26 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env } if (global) { - FILE *f; + char *gfn; #ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) - /* FIXME: Needs to set errno! */ - return NULL; - global = buf; + 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 = fopen(global, "r"))) { - + if ((f = pa_fopen_cloexec(gfn, "r"))) { if (result) - *result = pa_xstrdup(global); + *result = gfn; + else + pa_xfree(gfn); return f; } + pa_xfree(gfn); } errno = ENOENT; @@ -1688,22 +2020,8 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env 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); @@ -1724,15 +2042,6 @@ char *pa_find_config_file(const char *global, const char *local, const char *env } 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); @@ -1749,15 +2058,20 @@ char *pa_find_config_file(const char *global, const char *local, const char *env } if (global) { + char *gfn; + #ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) - /* FIXME: Needs to set errno! */ - return NULL; - global = buf; + 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(global, R_OK) == 0) - return pa_xstrdup(global); + if (access(gfn, R_OK) == 0) + return gfn; + pa_xfree(gfn); } errno = ENOENT; @@ -1774,7 +2088,7 @@ char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) { pa_assert(s); pa_assert(slength > 0); - while (i < dlength && j+3 <= slength) { + while (j+2 < slength && i < dlength) { s[j++] = hex[*d >> 4]; s[j++] = hex[*d & 0xF]; @@ -1830,7 +2144,7 @@ size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) { } /* Returns nonzero when *s starts with *pfx */ -pa_bool_t pa_startswith(const char *s, const char *pfx) { +bool pa_startswith(const char *s, const char *pfx) { size_t l; pa_assert(s); @@ -1842,7 +2156,7 @@ pa_bool_t pa_startswith(const char *s, const char *pfx) { } /* Returns nonzero when *s ends with *sfx */ -pa_bool_t pa_endswith(const char *s, const char *sfx) { +bool pa_endswith(const char *s, const char *sfx) { size_t l1, l2; pa_assert(s); @@ -1851,10 +2165,10 @@ pa_bool_t pa_endswith(const char *s, const char *sfx) { l1 = strlen(s); l2 = strlen(sfx); - return l1 >= l2 && strcmp(s+l1-l2, sfx) == 0; + return l1 >= l2 && pa_streq(s + l1 - l2, sfx); } -pa_bool_t pa_is_path_absolute(const char *fn) { +bool pa_is_path_absolute(const char *fn) { pa_assert(fn); #ifndef OS_IS_WIN32 @@ -1881,66 +2195,72 @@ char *pa_make_path_absolute(const char *p) { 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) { +/* 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; + char *r, *canonical_rtp; - if (pa_is_path_absolute(fn)) + 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(rtp); + pa_xfree(canonical_rtp); return NULL; } - r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-%s", rtp, mid, fn); + 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", rtp, fn); + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", canonical_rtp, fn); - pa_xfree(rtp); + pa_xfree(canonical_rtp); return r; } else return rtp; } char *pa_runtime_path(const char *fn) { - return get_path(fn, FALSE, TRUE); + return get_path(fn, false, true); } -char *pa_state_path(const char *fn, pa_bool_t appendmid) { - return get_path(fn, appendmid, FALSE); +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) { - 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; + if (pa_atol(s, &l) < 0) return -1; - } if ((int32_t) l != l) { errno = ERANGE; @@ -1979,6 +2299,28 @@ int pa_atou(const char *s, uint32_t *ret_u) { return 0; } +/* Convert the string s to a signed long integer in *ret_l. */ +int pa_atol(const char *s, long *ret_l) { + char *x = NULL; + long l; + + pa_assert(s); + pa_assert(ret_l); + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || *x || errno) { + if (!errno) + errno = EINVAL; + return -1; + } + + *ret_l = l; + + return 0; +} + #ifdef HAVE_STRTOF_L static locale_t c_locale = NULL; @@ -2102,7 +2444,7 @@ void *pa_will_need(const void *p, size_t l) { #endif const void *a; size_t size; - int r; + int r = ENOTSUP; size_t bs; pa_assert(p); @@ -2180,6 +2522,7 @@ void pa_close_pipe(int fds[2]) { } char *pa_readlink(const char *p) { +#ifdef HAVE_READLINK size_t l = 100; for (;;) { @@ -2201,6 +2544,9 @@ char *pa_readlink(const char *p) { pa_xfree(c); l *= 2; } +#else + return NULL; +#endif } int pa_close_all(int except_fd, ...) { @@ -2239,6 +2585,7 @@ int pa_close_all(int except_fd, ...) { } int pa_close_allv(const int except_fds[]) { +#ifndef OS_IS_WIN32 struct rlimit rl; int maxfd, fd; @@ -2251,7 +2598,7 @@ int pa_close_allv(const int except_fds[]) { struct dirent *de; while ((de = readdir(d))) { - pa_bool_t found; + bool found; long l; char *e = NULL; int i; @@ -2281,10 +2628,10 @@ int pa_close_allv(const int except_fds[]) { if (fd == dirfd(d)) continue; - found = FALSE; + found = false; for (i = 0; except_fds[i] >= 0; i++) if (except_fds[i] == fd) { - found = TRUE; + found = true; break; } @@ -2313,12 +2660,12 @@ int pa_close_allv(const int except_fds[]) { for (fd = 3; fd < maxfd; fd++) { int i; - pa_bool_t found; + bool found; - found = FALSE; + found = false; for (i = 0; except_fds[i] >= 0; i++) if (except_fds[i] == fd) { - found = TRUE; + found = true; break; } @@ -2328,6 +2675,7 @@ int pa_close_allv(const int except_fds[]) { if (pa_close(fd) < 0 && errno != EBADF) return -1; } +#endif /* !OS_IS_WIN32 */ return 0; } @@ -2368,6 +2716,7 @@ int pa_unblock_sigs(int except, ...) { } int pa_unblock_sigsv(const int except[]) { +#ifndef OS_IS_WIN32 int i; sigset_t ss; @@ -2379,6 +2728,9 @@ int pa_unblock_sigsv(const int except[]) { return -1; return sigprocmask(SIG_SETMASK, &ss, NULL); +#else + return 0; +#endif } int pa_reset_sigs(int except, ...) { @@ -2404,7 +2756,7 @@ int pa_reset_sigs(int except, ...) { p[i++] = except; while ((sig = va_arg(ap, int)) >= 0) - sig = p[i++]; + p[i++] = sig; } p[i] = -1; @@ -2417,15 +2769,16 @@ int pa_reset_sigs(int except, ...) { } int pa_reset_sigsv(const int except[]) { +#ifndef OS_IS_WIN32 int sig; for (sig = 1; sig < NSIG; sig++) { - pa_bool_t reset = TRUE; + bool reset = true; switch (sig) { case SIGKILL: case SIGSTOP: - reset = FALSE; + reset = false; break; default: { @@ -2433,7 +2786,7 @@ int pa_reset_sigsv(const int except[]) { for (i = 0; except[i] > 0; i++) { if (sig == except[i]) { - reset = FALSE; + reset = false; break; } } @@ -2453,6 +2806,7 @@ int pa_reset_sigsv(const int except[]) { return -1; } } +#endif return 0; } @@ -2463,7 +2817,11 @@ void pa_set_env(const char *key, const char *value) { /* This is not thread-safe */ - putenv(pa_sprintf_malloc("%s=%s", key, value)); +#ifdef OS_IS_WIN32 + SetEnvironmentVariable(key, value); +#else + setenv(key, value, 1); +#endif } void pa_set_env_and_record(const char *key, const char *value) { @@ -2488,20 +2846,44 @@ void pa_unset_env_recorded(void) { if (!s) break; +#ifdef OS_IS_WIN32 + SetEnvironmentVariable(s, NULL); +#else unsetenv(s); +#endif pa_xfree(s); } } -pa_bool_t pa_in_system_mode(void) { +bool pa_in_system_mode(void) { const char *e; if (!(e = getenv("PULSE_SYSTEM"))) - return FALSE; + return false; return !!atoi(e); } +/* Checks a whitespace-separated list of words in haystack for needle */ +bool pa_str_in_list_spaces(const char *haystack, const char *needle) { + char *s; + const char *state = NULL; + + if (!haystack || !needle) + return false; + + while ((s = pa_split_spaces(haystack, &state))) { + if (pa_streq(needle, s)) { + pa_xfree(s); + return true; + } + + pa_xfree(s); + } + + return false; +} + char *pa_get_user_name_malloc(void) { ssize_t k; char *u; @@ -2568,11 +2950,12 @@ char *pa_machine_id(void) { /* 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. */ + /* First we try the /etc/machine-id, which is the best option we + * have, since it fits perfectly our needs and is not as volatile + * as the hostname which might be set from dhcp. */ - if ((f = fopen(PA_MACHINE_ID, "r"))) { + if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r")) || + (f = pa_fopen_cloexec(PA_MACHINE_ID_FALLBACK, "r"))) { char ln[34] = "", *r; r = fgets(ln, sizeof(ln)-1, f); @@ -2587,30 +2970,46 @@ char *pa_machine_id(void) { 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); + return pa_sprintf_malloc("%08lx", (unsigned long) gethostid()); +#else + return NULL; +#endif } char *pa_session_id(void) { const char *e; - if (!(e = getenv("XDG_SESSION_COOKIE"))) + 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 %d.%d (%d) %s", i.dwMajorVersion, i.dwMinorVersion, i.dwBuildNumber, i.szCSDVersion); +#endif } #ifdef HAVE_VALGRIND_MEMCHECK_H -pa_bool_t pa_in_valgrind(void) { +bool pa_in_valgrind(void) { static int b = 0; /* To make heisenbugs a bit simpler to find we check for $VALGRIND @@ -2687,18 +3086,40 @@ char *pa_replace(const char*s, const char*a, const char *b) { return pa_strbuf_tostring_free(sb); } +char *pa_escape(const char *p, const char *chars) { + const char *s; + const char *c; + pa_strbuf *buf = pa_strbuf_new(); + + for (s = p; *s; ++s) { + if (*s == '\\') + pa_strbuf_putc(buf, '\\'); + else if (chars) { + for (c = chars; *c; ++c) { + if (*s == *c) { + pa_strbuf_putc(buf, '\\'); + break; + } + } + } + pa_strbuf_putc(buf, *s); + } + + return pa_strbuf_tostring_free(buf); +} + char *pa_unescape(char *p) { char *s, *d; - pa_bool_t escaped = FALSE; + bool escaped = false; for (s = p, d = p; *s; s++) { if (!escaped && *s == '\\') { - escaped = TRUE; + escaped = true; continue; } *(d++) = *s; - escaped = FALSE; + escaped = false; } *d = 0; @@ -2710,13 +3131,13 @@ char *pa_realpath(const char *path) { char *t; pa_assert(path); - /* We want only abolsute paths */ + /* We want only absolute paths */ if (path[0] != '/') { errno = EINVAL; return NULL; } -#if defined(__GLIBC__) || defined(__APPLE__) +#if defined(__GLIBC__) { char *r; @@ -2733,10 +3154,17 @@ char *pa_realpath(const char *path) { 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." @@ -2829,28 +3257,266 @@ size_t pa_pipe_buf(int fd) { void pa_reset_personality(void) { -#ifdef __linux__ +#if defined(__linux__) && !defined(__ANDROID__) if (personality(PER_LINUX) < 0) pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); #endif } -#if defined(__linux__) && !defined(__OPTIMIZE__) - -pa_bool_t pa_run_from_build_tree(void) { +bool pa_run_from_build_tree(void) { char *rp; - pa_bool_t b = FALSE; + static bool b = false; + + PA_ONCE_BEGIN { + if ((rp = pa_readlink("/proc/self/exe"))) { + b = pa_startswith(rp, PA_BUILDDIR); + pa_xfree(rp); + } + } PA_ONCE_END; + + 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) + return fd; + +finish: + /* Some implementations might simply ignore O_CLOEXEC if it is not + * understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_socket_cloexec(int domain, int type, int protocol) { + int fd; + +#ifdef SOCK_CLOEXEC + if ((fd = socket(domain, type | SOCK_CLOEXEC, protocol)) >= 0) + goto finish; + + if (errno != EINVAL) + return fd; +#endif + + if ((fd = socket(domain, type, protocol)) < 0) + return fd; + +finish: + /* Some implementations might simply ignore SOCK_CLOEXEC if it is + * not understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_pipe_cloexec(int pipefd[2]) { + int r; + +#ifdef HAVE_PIPE2 + if ((r = pipe2(pipefd, O_CLOEXEC)) >= 0) + goto finish; + + if (errno != EINVAL && errno != ENOSYS) + return r; - /* We abuse __OPTIMIZE__ as a check whether we are a debug build - * or not. */ +#endif + + if ((r = pipe(pipefd)) < 0) + return r; + +finish: + pa_make_fd_cloexec(pipefd[0]); + pa_make_fd_cloexec(pipefd[1]); + + return 0; +} + +int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + int fd; + +#ifdef HAVE_ACCEPT4 + if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0) + goto finish; + + if (errno != EINVAL && errno != ENOSYS) + return fd; + +#endif - if ((rp = pa_readlink("/proc/self/exe"))) { - b = pa_startswith(rp, PA_BUILDDIR); - pa_xfree(rp); + if ((fd = accept(sockfd, addr, addrlen)) < 0) + return fd; + +finish: + pa_make_fd_cloexec(fd); + return fd; +} + +FILE* pa_fopen_cloexec(const char *path, const char *mode) { + FILE *f; + char *m; + + m = pa_sprintf_malloc("%se", mode); + + errno = 0; + if ((f = fopen(path, m))) { + pa_xfree(m); + goto finish; } - return b; + 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... */ + + uint32_t eax = 0x40000000; + union { + uint32_t sig32[3]; + char text[13]; + } sig; + +#ifdef __linux__ + const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + unsigned i; + + for (i = 0; i < PA_ELEMENTSOF(dmi_vendors); i++) { + char *s; + + if ((s = pa_read_line_from_file(dmi_vendors[i]))) { + + if (pa_startswith(s, "QEMU") || + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + pa_startswith(s, "VMware") || + pa_startswith(s, "VMW") || + pa_startswith(s, "Microsoft Corporation") || + pa_startswith(s, "innotek GmbH") || + pa_startswith(s, "Xen")) { + + pa_xfree(s); + return true; + } + + pa_xfree(s); + } + } + +#endif + + /* http://lwn.net/Articles/301888/ */ + pa_zero(sig); + + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"PA_REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"PA_REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + if (pa_streq(sig.text, "XenVMMXenVMM") || + pa_streq(sig.text, "KVMKVMKVM") || + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + pa_streq(sig.text, "VMwareVMware") || + /* http://msdn.microsoft.com/en-us/library/bb969719.aspx */ + pa_streq(sig.text, "Microsoft Hv")) + return true; + +#endif + + return false; +}