diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
commit | 2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1 (patch) | |
tree | 465b29cb405d3af0b0ad50c78e1dccc636594fec /src/daemon | |
parent | Initial commit. (diff) | |
download | pulseaudio-upstream.tar.xz pulseaudio-upstream.zip |
Adding upstream version 14.2.upstream/14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/daemon')
26 files changed, 4755 insertions, 0 deletions
diff --git a/src/daemon/.gitignore b/src/daemon/.gitignore new file mode 100644 index 0000000..0efa55b --- /dev/null +++ b/src/daemon/.gitignore @@ -0,0 +1,2 @@ +org.pulseaudio.policy +pulseaudio.desktop diff --git a/src/daemon/caps.c b/src/daemon/caps.c new file mode 100644 index 0000000..fd135c0 --- /dev/null +++ b/src/daemon/caps.c @@ -0,0 +1,99 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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 + 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 <unistd.h> +#include <errno.h> +#include <sys/types.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif + +#include "caps.h" + +/* Glibc <= 2.2 has broken unistd.h */ +#if defined(__linux__) && (__GLIBC__ <= 2 && __GLIBC_MINOR__ <= 2) +int setresgid(gid_t r, gid_t e, gid_t s); +int setresuid(uid_t r, uid_t e, uid_t s); +#endif + +/* Drop root rights when called SUID root */ +void pa_drop_root(void) { + +#ifdef HAVE_GETUID + uid_t uid; + gid_t gid; + + pa_log_debug("Cleaning up privileges."); + uid = getuid(); + gid = getgid(); + +#if defined(HAVE_SETRESUID) + pa_assert_se(setresuid(uid, uid, uid) >= 0); + pa_assert_se(setresgid(gid, gid, gid) >= 0); +#elif defined(HAVE_SETREUID) + pa_assert_se(setreuid(uid, uid) >= 0); + pa_assert_se(setregid(gid, gid) >= 0); +#else + pa_assert_se(setuid(uid) >= 0); + pa_assert_se(seteuid(uid) >= 0); + pa_assert_se(setgid(gid) >= 0); + pa_assert_se(setegid(gid) >= 0); +#endif + + pa_assert_se(getuid() == uid); + pa_assert_se(geteuid() == uid); + pa_assert_se(getgid() == gid); + pa_assert_se(getegid() == gid); + + if (uid != 0) + pa_drop_caps(); +#endif +} + +void pa_drop_caps(void) { +#ifdef HAVE_SYS_CAPABILITY_H +#if defined(__linux__) + cap_t caps; + pa_assert_se(caps = cap_init()); + pa_assert_se(cap_clear(caps) == 0); + pa_assert_se(cap_set_proc(caps) == 0); + pa_assert_se(cap_free(caps) == 0); +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + /* FreeBSD doesn't have this functionality, even though sys/capability.h is + * available. See https://bugs.freedesktop.org/show_bug.cgi?id=72580 */ + pa_log_warn("FreeBSD cannot drop extra capabilities, implementation needed."); +#else +#error "Don't know how to do capabilities on your system. Please send a patch." +#endif /* __linux__ */ +#else /* HAVE_SYS_CAPABILITY_H */ + pa_log_warn("Normally all extra capabilities would be dropped now, but " + "that's impossible because PulseAudio was built without " + "capabilities support."); +#endif +} diff --git a/src/daemon/caps.h b/src/daemon/caps.h new file mode 100644 index 0000000..9b88241 --- /dev/null +++ b/src/daemon/caps.h @@ -0,0 +1,29 @@ +#ifndef foocapshfoo +#define foocapshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> + +void pa_drop_root(void); + +void pa_drop_caps(void); + +#endif diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c new file mode 100644 index 0000000..b325596 --- /dev/null +++ b/src/daemon/cmdline.c @@ -0,0 +1,420 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/macro.h> + +#include "cmdline.h" + +/* Argument codes for getopt_long() */ +enum { + ARG_HELP = 256, + ARG_VERSION, + ARG_DUMP_CONF, + ARG_DUMP_MODULES, + ARG_DAEMONIZE, + ARG_FAIL, + ARG_LOG_LEVEL, + ARG_HIGH_PRIORITY, + ARG_REALTIME, + ARG_DISALLOW_MODULE_LOADING, + ARG_DISALLOW_EXIT, + ARG_EXIT_IDLE_TIME, + ARG_SCACHE_IDLE_TIME, + ARG_LOG_TARGET, + ARG_LOG_META, + ARG_LOG_TIME, + ARG_LOG_BACKTRACE, + ARG_LOAD, + ARG_FILE, + ARG_DL_SEARCH_PATH, + ARG_RESAMPLE_METHOD, + ARG_KILL, + ARG_USE_PID_FILE, + ARG_CHECK, + ARG_NO_CPU_LIMIT, + ARG_DISABLE_SHM, + ARG_ENABLE_MEMFD, + ARG_DUMP_RESAMPLE_METHODS, + ARG_SYSTEM, + ARG_CLEANUP_SHM, + ARG_START +}; + +/* Table for getopt_long() */ +static const struct option long_options[] = { + {"help", 0, 0, ARG_HELP}, + {"version", 0, 0, ARG_VERSION}, + {"dump-conf", 0, 0, ARG_DUMP_CONF}, + {"dump-modules", 0, 0, ARG_DUMP_MODULES}, + {"daemonize", 2, 0, ARG_DAEMONIZE}, + {"fail", 2, 0, ARG_FAIL}, + {"verbose", 2, 0, ARG_LOG_LEVEL}, + {"log-level", 2, 0, ARG_LOG_LEVEL}, + {"high-priority", 2, 0, ARG_HIGH_PRIORITY}, + {"realtime", 2, 0, ARG_REALTIME}, + {"disallow-module-loading", 2, 0, ARG_DISALLOW_MODULE_LOADING}, + {"disallow-exit", 2, 0, ARG_DISALLOW_EXIT}, + {"exit-idle-time", 1, 0, ARG_EXIT_IDLE_TIME}, + {"scache-idle-time", 1, 0, ARG_SCACHE_IDLE_TIME}, + {"log-target", 1, 0, ARG_LOG_TARGET}, + {"log-meta", 2, 0, ARG_LOG_META}, + {"log-time", 2, 0, ARG_LOG_TIME}, + {"log-backtrace", 1, 0, ARG_LOG_BACKTRACE}, + {"load", 1, 0, ARG_LOAD}, + {"file", 1, 0, ARG_FILE}, + {"dl-search-path", 1, 0, ARG_DL_SEARCH_PATH}, + {"resample-method", 1, 0, ARG_RESAMPLE_METHOD}, + {"kill", 0, 0, ARG_KILL}, + {"start", 0, 0, ARG_START}, + {"use-pid-file", 2, 0, ARG_USE_PID_FILE}, + {"check", 0, 0, ARG_CHECK}, + {"system", 2, 0, ARG_SYSTEM}, + {"no-cpu-limit", 2, 0, ARG_NO_CPU_LIMIT}, + {"disable-shm", 2, 0, ARG_DISABLE_SHM}, + {"enable-memfd", 2, 0, ARG_ENABLE_MEMFD}, + {"dump-resample-methods", 2, 0, ARG_DUMP_RESAMPLE_METHODS}, + {"cleanup-shm", 2, 0, ARG_CLEANUP_SHM}, + {NULL, 0, 0, 0} +}; + +void pa_cmdline_help(const char *argv0) { + pa_assert(argv0); + + printf(_("%s [options]\n\n" + "COMMANDS:\n" + " -h, --help Show this help\n" + " --version Show version\n" + " --dump-conf Dump default configuration\n" + " --dump-modules Dump list of available modules\n" + " --dump-resample-methods Dump available resample methods\n" + " --cleanup-shm Cleanup stale shared memory segments\n" + " --start Start the daemon if it is not running\n" + " -k --kill Kill a running daemon\n" + " --check Check for a running daemon (only returns exit code)\n\n" + + "OPTIONS:\n" + " --system[=BOOL] Run as system-wide instance\n" + " -D, --daemonize[=BOOL] Daemonize after startup\n" + " --fail[=BOOL] Quit when startup fails\n" + " --high-priority[=BOOL] Try to set high nice level\n" + " (only available as root, when SUID or\n" + " with elevated RLIMIT_NICE)\n" + " --realtime[=BOOL] Try to enable realtime scheduling\n" + " (only available as root, when SUID or\n" + " with elevated RLIMIT_RTPRIO)\n" + " --disallow-module-loading[=BOOL] Disallow user requested module\n" + " loading/unloading after startup\n" + " --disallow-exit[=BOOL] Disallow user requested exit\n" + " --exit-idle-time=SECS Terminate the daemon when idle and this\n" + " time passed\n" + " --scache-idle-time=SECS Unload autoloaded samples when idle and\n" + " this time passed\n" + " --log-level[=LEVEL] Increase or set verbosity level\n" + " -v --verbose Increase the verbosity level\n" + " --log-target={auto,syslog,stderr,file:PATH,newfile:PATH}\n" + " Specify the log target\n" + " --log-meta[=BOOL] Include code location in log messages\n" + " --log-time[=BOOL] Include timestamps in log messages\n" + " --log-backtrace=FRAMES Include a backtrace in log messages\n" + " -p, --dl-search-path=PATH Set the search path for dynamic shared\n" + " objects (plugins)\n" + " --resample-method=METHOD Use the specified resampling method\n" + " (See --dump-resample-methods for\n" + " possible values)\n" + " --use-pid-file[=BOOL] Create a PID file\n" + " --no-cpu-limit[=BOOL] Do not install CPU load limiter on\n" + " platforms that support it.\n" + " --disable-shm[=BOOL] Disable shared memory support.\n" + " --enable-memfd[=BOOL] Enable memfd shared memory support.\n\n" + + "STARTUP SCRIPT:\n" + " -L, --load=\"MODULE ARGUMENTS\" Load the specified plugin module with\n" + " the specified argument\n" + " -F, --file=FILENAME Run the specified script\n" + " -C Open a command line on the running TTY\n" + " after startup\n\n" + + " -n Don't load default script file\n"), + pa_path_get_filename(argv0)); +} + +int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) { + pa_strbuf *buf = NULL; + int c; + int b; + + pa_assert(conf); + pa_assert(argc > 0); + pa_assert(argv); + + buf = pa_strbuf_new(); + + if (conf->script_commands) + pa_strbuf_puts(buf, conf->script_commands); + + while ((c = getopt_long(argc, argv, "L:F:ChDnp:kv", long_options, NULL)) != -1) { + switch (c) { + case ARG_HELP: + case 'h': + conf->cmd = PA_CMD_HELP; + break; + + case ARG_VERSION: + conf->cmd = PA_CMD_VERSION; + break; + + case ARG_DUMP_CONF: + conf->cmd = PA_CMD_DUMP_CONF; + break; + + case ARG_DUMP_MODULES: + conf->cmd = PA_CMD_DUMP_MODULES; + break; + + case ARG_DUMP_RESAMPLE_METHODS: + conf->cmd = PA_CMD_DUMP_RESAMPLE_METHODS; + break; + + case ARG_CLEANUP_SHM: + conf->cmd = PA_CMD_CLEANUP_SHM; + break; + + case 'k': + case ARG_KILL: + conf->cmd = PA_CMD_KILL; + break; + + case ARG_START: + conf->cmd = PA_CMD_START; + conf->daemonize = true; + break; + + case ARG_CHECK: + conf->cmd = PA_CMD_CHECK; + break; + + case ARG_LOAD: + case 'L': + pa_strbuf_printf(buf, "load-module %s\n", optarg); + break; + + case ARG_FILE: + case 'F': { + char *p; + pa_strbuf_printf(buf, ".include %s\n", p = pa_make_path_absolute(optarg)); + pa_xfree(p); + break; + } + + case 'C': + pa_strbuf_puts(buf, "load-module module-cli exit_on_eof=1\n"); + break; + + case ARG_DAEMONIZE: + case 'D': + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--daemonize expects boolean argument")); + goto fail; + } + conf->daemonize = !!b; + break; + + case ARG_FAIL: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--fail expects boolean argument")); + goto fail; + } + conf->fail = !!b; + break; + + case 'v': + case ARG_LOG_LEVEL: + + if (optarg) { + if (pa_daemon_conf_set_log_level(conf, optarg) < 0) { + pa_log(_("--log-level expects log level argument (either numeric in range 0..4 or one of debug, info, notice, warn, error).")); + goto fail; + } + } else { + if (conf->log_level < PA_LOG_LEVEL_MAX-1) + conf->log_level++; + } + + break; + + case ARG_HIGH_PRIORITY: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--high-priority expects boolean argument")); + goto fail; + } + conf->high_priority = !!b; + break; + + case ARG_REALTIME: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--realtime expects boolean argument")); + goto fail; + } + conf->realtime_scheduling = !!b; + break; + + case ARG_DISALLOW_MODULE_LOADING: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--disallow-module-loading expects boolean argument")); + goto fail; + } + conf->disallow_module_loading = !!b; + break; + + case ARG_DISALLOW_EXIT: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--disallow-exit expects boolean argument")); + goto fail; + } + conf->disallow_exit = !!b; + break; + + case ARG_USE_PID_FILE: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--use-pid-file expects boolean argument")); + goto fail; + } + conf->use_pid_file = !!b; + break; + + case 'p': + case ARG_DL_SEARCH_PATH: + pa_xfree(conf->dl_search_path); + conf->dl_search_path = pa_xstrdup(optarg); + break; + + case 'n': + conf->load_default_script_file = false; + break; + + case ARG_LOG_TARGET: + if (pa_daemon_conf_set_log_target(conf, optarg) < 0) { +#ifdef HAVE_SYSTEMD_JOURNAL + pa_log(_("Invalid log target: use either 'syslog', 'journal', 'stderr' or 'auto' or a valid file name 'file:<path>', 'newfile:<path>'.")); +#else + pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto' or a valid file name 'file:<path>', 'newfile:<path>'.")); +#endif + goto fail; + } + break; + + case ARG_LOG_TIME: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--log-time expects boolean argument")); + goto fail; + } + conf->log_time = !!b; + break; + + case ARG_LOG_META: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--log-meta expects boolean argument")); + goto fail; + } + conf->log_meta = !!b; + break; + + case ARG_LOG_BACKTRACE: + conf->log_backtrace = (unsigned) atoi(optarg); + break; + + case ARG_EXIT_IDLE_TIME: + conf->exit_idle_time = atoi(optarg); + break; + + case ARG_SCACHE_IDLE_TIME: + conf->scache_idle_time = atoi(optarg); + break; + + case ARG_RESAMPLE_METHOD: + if (pa_daemon_conf_set_resample_method(conf, optarg) < 0) { + pa_log(_("Invalid resample method '%s'."), optarg); + goto fail; + } + break; + + case ARG_SYSTEM: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--system expects boolean argument")); + goto fail; + } + conf->system_instance = !!b; + break; + + case ARG_NO_CPU_LIMIT: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--no-cpu-limit expects boolean argument")); + goto fail; + } + conf->no_cpu_limit = !!b; + break; + + case ARG_DISABLE_SHM: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--disable-shm expects boolean argument")); + goto fail; + } + conf->disable_shm = !!b; + break; + + case ARG_ENABLE_MEMFD: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--enable-memfd expects boolean argument")); + goto fail; + } + conf->disable_memfd = !b; + break; + + default: + goto fail; + } + } + + pa_xfree(conf->script_commands); + conf->script_commands = pa_strbuf_to_string_free(buf); + + *d = optind; + + return 0; + +fail: + if (buf) + pa_strbuf_free(buf); + + return -1; +} diff --git a/src/daemon/cmdline.h b/src/daemon/cmdline.h new file mode 100644 index 0000000..771c14d --- /dev/null +++ b/src/daemon/cmdline.h @@ -0,0 +1,33 @@ +#ifndef foocmdlinehfoo +#define foocmdlinehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include "daemon-conf.h" + +/* Parse the command line and store its data in *c. Return the index + * of the first unparsed argument in *d. */ +int pa_cmdline_parse(pa_daemon_conf*c, int argc, char *const argv [], int *d); + +/* Show the command line help. The command name is extracted from + * argv[0] which should be passed in argv0. */ +void pa_cmdline_help(const char *argv0); + +#endif diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c new file mode 100644 index 0000000..a3deaf2 --- /dev/null +++ b/src/daemon/cpulimit.c @@ -0,0 +1,244 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpulimit.h" + +#ifdef HAVE_SIGXCPU + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +/* This module implements a watchdog that makes sure that the current + * process doesn't consume more than 70% CPU time for 10 seconds. This + * is very useful when using SCHED_FIFO scheduling which effectively + * disables multitasking. */ + +/* Method of operation: Using SIGXCPU a signal handler is called every + * 10s process CPU time. That function checks if less than 14s system + * time have passed. In that case, it tries to contact the main event + * loop through a pipe. After two additional seconds it is checked + * whether the main event loop contact was successful. If not, the + * program is terminated forcibly. */ + +/* Utilize this much CPU time at maximum */ +#define CPUTIME_PERCENT 70 + +/* Check every 10s */ +#define CPUTIME_INTERVAL_SOFT (10) + +/* Recheck after 5s */ +#define CPUTIME_INTERVAL_HARD (5) + +/* Time of the last CPU load check */ +static pa_usec_t last_time = 0; + +/* Pipe for communicating with the main loop */ +static int the_pipe[2] = {-1, -1}; + +/* Main event loop and IO event for the FIFO */ +static pa_mainloop_api *api = NULL; +static pa_io_event *io_event = NULL; + +/* Saved sigaction struct for SIGXCPU */ +static struct sigaction sigaction_prev; + +/* Nonzero after pa_cpu_limit_init() */ +static bool installed = false; + +/* The current state of operation */ +static enum { + PHASE_IDLE, /* Normal state */ + PHASE_SOFT /* After CPU overload has been detected */ +} phase = PHASE_IDLE; + +/* Reset the SIGXCPU timer to the next t seconds */ +static void reset_cpu_time(int t) { + long n; + struct rlimit rl; + struct rusage ru; + + /* Get the current CPU time of the current process */ + pa_assert_se(getrusage(RUSAGE_SELF, &ru) >= 0); + + n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t; + pa_assert_se(getrlimit(RLIMIT_CPU, &rl) >= 0); + + rl.rlim_cur = (rlim_t) n; + pa_assert_se(setrlimit(RLIMIT_CPU, &rl) >= 0); +} + +/* A simple, thread-safe puts() work-alike */ +static void write_err(const char *p) { + pa_loop_write(2, p, strlen(p), NULL); +} + +/* The signal handler, called on every SIGXCPU */ +static void signal_handler(int sig) { + int saved_errno; + + saved_errno = errno; + pa_assert(sig == SIGXCPU); + + if (phase == PHASE_IDLE) { + pa_usec_t now, elapsed; + +#ifdef PRINT_CPU_LOAD + char t[256]; +#endif + + now = pa_rtclock_now(); + elapsed = now - last_time; + +#ifdef PRINT_CPU_LOAD + pa_snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", ((double) CPUTIME_INTERVAL_SOFT * (double) PA_USEC_PER_SEC) / (double) elapsed * 100.0); + write_err(t); +#endif + + if (((double) CPUTIME_INTERVAL_SOFT * (double) PA_USEC_PER_SEC) >= ((double) elapsed * (double) CPUTIME_PERCENT / 100.0)) { + static const char c = 'X'; + + write_err("Soft CPU time limit exhausted, terminating.\n"); + + /* Try a soft cleanup */ + (void) pa_write(the_pipe[1], &c, sizeof(c), NULL); + phase = PHASE_SOFT; + reset_cpu_time(CPUTIME_INTERVAL_HARD); + + } else { + + /* Everything's fine */ + reset_cpu_time(CPUTIME_INTERVAL_SOFT); + last_time = now; + } + + } else if (phase == PHASE_SOFT) { + write_err("Hard CPU time limit exhausted, terminating forcibly.\n"); + abort(); /* Forced exit */ + } + + errno = saved_errno; +} + +/* Callback for IO events on the FIFO */ +static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata) { + char c; + pa_assert(m); + pa_assert(e); + pa_assert(f == PA_IO_EVENT_INPUT); + pa_assert(e == io_event); + pa_assert(fd == the_pipe[0]); + + pa_log("Received request to terminate due to CPU overload."); + + (void) pa_read(the_pipe[0], &c, sizeof(c), NULL); + m->quit(m, 1); /* Quit the main loop */ +} + +/* Initializes CPU load limiter */ +int pa_cpu_limit_init(pa_mainloop_api *m) { + struct sigaction sa; + + pa_assert(m); + pa_assert(!api); + pa_assert(!io_event); + pa_assert(the_pipe[0] == -1); + pa_assert(the_pipe[1] == -1); + pa_assert(!installed); + + last_time = pa_rtclock_now(); + + /* Prepare the main loop pipe */ + if (pa_pipe_cloexec(the_pipe) < 0) { + pa_log("pipe() failed: %s", pa_cstrerror(errno)); + return -1; + } + + pa_make_fd_nonblock(the_pipe[0]); + pa_make_fd_nonblock(the_pipe[1]); + + api = m; + io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL); + + phase = PHASE_IDLE; + + /* Install signal handler for SIGXCPU */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGXCPU, &sa, &sigaction_prev) < 0) { + pa_cpu_limit_done(); + return -1; + } + + installed = true; + + reset_cpu_time(CPUTIME_INTERVAL_SOFT); + + return 0; +} + +/* Shutdown CPU load limiter */ +void pa_cpu_limit_done(void) { + + if (io_event) { + pa_assert(api); + api->io_free(io_event); + io_event = NULL; + api = NULL; + } + + pa_close_pipe(the_pipe); + + if (installed) { + pa_assert_se(sigaction(SIGXCPU, &sigaction_prev, NULL) >= 0); + installed = false; + } +} + +#else /* HAVE_SIGXCPU */ + +int pa_cpu_limit_init(pa_mainloop_api *m) { + return 0; +} + +void pa_cpu_limit_done(void) { +} + +#endif diff --git a/src/daemon/cpulimit.h b/src/daemon/cpulimit.h new file mode 100644 index 0000000..460f8c5 --- /dev/null +++ b/src/daemon/cpulimit.h @@ -0,0 +1,32 @@ +#ifndef foocpulimithfoo +#define foocpulimithfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> + +/* This kills the pulseaudio process if it eats more than 70% of the + * CPU time. This is build around setrlimit() and SIGXCPU. It is handy + * in case of using SCHED_FIFO which may freeze the whole machine */ + +int pa_cpu_limit_init(pa_mainloop_api *m); +void pa_cpu_limit_done(void); + +#endif diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c new file mode 100644 index 0000000..bcf7329 --- /dev/null +++ b/src/daemon/daemon-conf.c @@ -0,0 +1,872 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#ifdef HAVE_SCHED_H +#include <sched.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/version.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> + +#include "daemon-conf.h" + +#define DEFAULT_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "default.pa" +#define DEFAULT_SCRIPT_FILE_USER PA_PATH_SEP "default.pa" +#define DEFAULT_SYSTEM_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "system.pa" + +#define DEFAULT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "daemon.conf" +#define DEFAULT_CONFIG_FILE_USER PA_PATH_SEP "daemon.conf" + +#define ENV_SCRIPT_FILE "PULSE_SCRIPT" +#define ENV_CONFIG_FILE "PULSE_CONFIG" +#define ENV_DL_SEARCH_PATH "PULSE_DLPATH" + +static const pa_daemon_conf default_conf = { + .cmd = PA_CMD_DAEMON, + .daemonize = false, + .fail = true, + .high_priority = true, + .nice_level = -11, + .realtime_scheduling = true, + .realtime_priority = 5, /* Half of JACK's default rtprio */ + .disallow_module_loading = false, + .disallow_exit = false, + .flat_volumes = false, + .rescue_streams = true, + .exit_idle_time = 20, + .scache_idle_time = 20, + .script_commands = NULL, + .dl_search_path = NULL, + .load_default_script_file = true, + .default_script_file = NULL, + .log_target = NULL, + .log_level = PA_LOG_NOTICE, + .log_backtrace = 0, + .log_meta = false, + .log_time = false, + .resample_method = PA_RESAMPLER_AUTO, + .avoid_resampling = false, + .disable_remixing = false, + .remixing_use_all_sink_channels = true, + .remixing_produce_lfe = false, + .remixing_consume_lfe = false, + .lfe_crossover_freq = 0, + .config_file = NULL, + .use_pid_file = true, + .system_instance = false, +#ifdef HAVE_DBUS + .local_server_type = PA_SERVER_TYPE_UNSET, /* The actual default is _USER, but we have to detect when the user doesn't specify this option. */ +#endif + .no_cpu_limit = true, + .disable_shm = false, + .disable_memfd = false, + .lock_memory = false, + .deferred_volume = true, + .default_n_fragments = 4, + .default_fragment_size_msec = 25, + .deferred_volume_safety_margin_usec = 8000, + .deferred_volume_extra_delay_usec = 0, + .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }, + .alternate_sample_rate = 48000, + .default_channel_map = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, + .shm_size = 0 +#ifdef HAVE_SYS_RESOURCE_H + ,.rlimit_fsize = { .value = 0, .is_set = false }, + .rlimit_data = { .value = 0, .is_set = false }, + .rlimit_stack = { .value = 0, .is_set = false }, + .rlimit_core = { .value = 0, .is_set = false } +#ifdef RLIMIT_RSS + ,.rlimit_rss = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_NPROC + ,.rlimit_nproc = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_NOFILE + ,.rlimit_nofile = { .value = 256, .is_set = true } +#endif +#ifdef RLIMIT_MEMLOCK + ,.rlimit_memlock = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_AS + ,.rlimit_as = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_LOCKS + ,.rlimit_locks = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_SIGPENDING + ,.rlimit_sigpending = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_MSGQUEUE + ,.rlimit_msgqueue = { .value = 0, .is_set = false } +#endif +#ifdef RLIMIT_NICE + ,.rlimit_nice = { .value = 31, .is_set = true } /* nice level of -11 */ +#endif +#ifdef RLIMIT_RTPRIO + ,.rlimit_rtprio = { .value = 9, .is_set = true } /* One below JACK's default for the server */ +#endif +#ifdef RLIMIT_RTTIME + ,.rlimit_rttime = { .value = 200*PA_USEC_PER_MSEC, .is_set = true } /* rtkit's limit is 200 ms */ +#endif +#endif +}; + +pa_daemon_conf *pa_daemon_conf_new(void) { + pa_daemon_conf *c; + + c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); + +#ifdef OS_IS_WIN32 + c->dl_search_path = pa_sprintf_malloc("%s" PA_PATH_SEP "lib" PA_PATH_SEP "pulse-%d.%d" PA_PATH_SEP "modules", + pa_win32_get_toplevel(NULL), PA_MAJOR, PA_MINOR); +#else +#ifdef HAVE_RUNNING_FROM_BUILD_TREE + if (pa_run_from_build_tree()) { + pa_log_notice("Detected that we are run from the build tree, fixing search path."); +#ifdef MESON_BUILD + c->dl_search_path = pa_xstrdup(PA_BUILDDIR PA_PATH_SEP "src" PA_PATH_SEP "modules"); +#else + c->dl_search_path = pa_xstrdup(PA_BUILDDIR); +#endif // Endof #ifdef MESON_BUILD + } else +#endif // Endof #ifdef HAVE_RUNNING_FROM_BUILD_TREE + c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); +#endif // Endof #ifdef OS_IS_WIN32 + + return c; +} + +void pa_daemon_conf_free(pa_daemon_conf *c) { + pa_assert(c); + + pa_xfree(c->script_commands); + pa_xfree(c->dl_search_path); + pa_xfree(c->default_script_file); + + if (c->log_target) + pa_log_target_free(c->log_target); + + pa_xfree(c->config_file); + pa_xfree(c); +} + +int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) { + pa_log_target *log_target = NULL; + + pa_assert(c); + pa_assert(string); + + if (!pa_streq(string, "auto")) { + log_target = pa_log_parse_target(string); + + if (!log_target) + return -1; + } + + c->log_target = log_target; + + return 0; +} + +int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string) { + uint32_t u; + pa_assert(c); + pa_assert(string); + + if (pa_atou(string, &u) >= 0) { + if (u >= PA_LOG_LEVEL_MAX) + return -1; + + c->log_level = (pa_log_level_t) u; + } else if (pa_startswith(string, "debug")) + c->log_level = PA_LOG_DEBUG; + else if (pa_startswith(string, "info")) + c->log_level = PA_LOG_INFO; + else if (pa_startswith(string, "notice")) + c->log_level = PA_LOG_NOTICE; + else if (pa_startswith(string, "warn")) + c->log_level = PA_LOG_WARN; + else if (pa_startswith(string, "err")) + c->log_level = PA_LOG_ERROR; + else + return -1; + + return 0; +} + +int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) { + int m; + pa_assert(c); + pa_assert(string); + + if ((m = pa_parse_resample_method(string)) < 0) + return -1; + + c->resample_method = m; + return 0; +} + +int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string) { + pa_assert(c); + pa_assert(string); + + if (pa_streq(string, "user")) + c->local_server_type = PA_SERVER_TYPE_USER; + else if (pa_streq(string, "system")) { + c->local_server_type = PA_SERVER_TYPE_SYSTEM; + } else if (pa_streq(string, "none")) { + c->local_server_type = PA_SERVER_TYPE_NONE; + } else + return -1; + + return 0; +} + +static int parse_log_target(pa_config_parser_state *state) { + pa_daemon_conf *c; + + pa_assert(state); + + c = state->data; + + if (pa_daemon_conf_set_log_target(c, state->rvalue) < 0) { + pa_log(_("[%s:%u] Invalid log target '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + return 0; +} + +static int parse_log_level(pa_config_parser_state *state) { + pa_daemon_conf *c; + + pa_assert(state); + + c = state->data; + + if (pa_daemon_conf_set_log_level(c, state->rvalue) < 0) { + pa_log(_("[%s:%u] Invalid log level '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + return 0; +} + +static int parse_resample_method(pa_config_parser_state *state) { + pa_daemon_conf *c; + + pa_assert(state); + + c = state->data; + + if (pa_daemon_conf_set_resample_method(c, state->rvalue) < 0) { + pa_log(_("[%s:%u] Invalid resample method '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + return 0; +} + +#ifdef HAVE_SYS_RESOURCE_H +static int parse_rlimit(pa_config_parser_state *state) { + struct pa_rlimit *r; + + pa_assert(state); + + r = state->data; + + if (state->rvalue[strspn(state->rvalue, "\t ")] == 0) { + /* Empty string */ + r->is_set = 0; + r->value = 0; + } else { + int32_t k; + if (pa_atoi(state->rvalue, &k) < 0) { + pa_log(_("[%s:%u] Invalid rlimit '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + r->is_set = k >= 0; + r->value = k >= 0 ? (rlim_t) k : 0; + } + + return 0; +} +#endif + +static int parse_sample_format(pa_config_parser_state *state) { + pa_daemon_conf *c; + pa_sample_format_t f; + + pa_assert(state); + + c = state->data; + + if ((f = pa_parse_sample_format(state->rvalue)) < 0) { + pa_log(_("[%s:%u] Invalid sample format '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->default_sample_spec.format = f; + return 0; +} + +static int parse_sample_rate(pa_config_parser_state *state) { + pa_daemon_conf *c; + uint32_t r; + + pa_assert(state); + + c = state->data; + + if (pa_atou(state->rvalue, &r) < 0 || !pa_sample_rate_valid(r)) { + pa_log(_("[%s:%u] Invalid sample rate '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->default_sample_spec.rate = r; + return 0; +} + +static int parse_alternate_sample_rate(pa_config_parser_state *state) { + pa_daemon_conf *c; + uint32_t r; + + pa_assert(state); + + c = state->data; + + if (pa_atou(state->rvalue, &r) < 0 || !pa_sample_rate_valid(r)) { + pa_log(_("[%s:%u] Invalid sample rate '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->alternate_sample_rate = r; + return 0; +} + +struct channel_conf_info { + pa_daemon_conf *conf; + bool default_sample_spec_set; + bool default_channel_map_set; +}; + +static int parse_sample_channels(pa_config_parser_state *state) { + struct channel_conf_info *i; + int32_t n; + + pa_assert(state); + + i = state->data; + + if (pa_atoi(state->rvalue, &n) < 0 || !pa_channels_valid(n)) { + pa_log(_("[%s:%u] Invalid sample channels '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + i->conf->default_sample_spec.channels = (uint8_t) n; + i->default_sample_spec_set = true; + return 0; +} + +static int parse_channel_map(pa_config_parser_state *state) { + struct channel_conf_info *i; + + pa_assert(state); + + i = state->data; + + if (!pa_channel_map_parse(&i->conf->default_channel_map, state->rvalue)) { + pa_log(_("[%s:%u] Invalid channel map '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + i->default_channel_map_set = true; + return 0; +} + +static int parse_fragments(pa_config_parser_state *state) { + pa_daemon_conf *c; + int32_t n; + + pa_assert(state); + + c = state->data; + + if (pa_atoi(state->rvalue, &n) < 0 || n < 2) { + pa_log(_("[%s:%u] Invalid number of fragments '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->default_n_fragments = (unsigned) n; + return 0; +} + +static int parse_fragment_size_msec(pa_config_parser_state *state) { + pa_daemon_conf *c; + int32_t n; + + pa_assert(state); + + c = state->data; + + if (pa_atoi(state->rvalue, &n) < 0 || n < 1) { + pa_log(_("[%s:%u] Invalid fragment size '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->default_fragment_size_msec = (unsigned) n; + return 0; +} + +static int parse_nice_level(pa_config_parser_state *state) { + pa_daemon_conf *c; + int32_t level; + + pa_assert(state); + + c = state->data; + + if (pa_atoi(state->rvalue, &level) < 0 || level < -20 || level > 19) { + pa_log(_("[%s:%u] Invalid nice level '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + c->nice_level = (int) level; + return 0; +} + +static int parse_rtprio(pa_config_parser_state *state) { +#if !defined(OS_IS_WIN32) && defined(HAVE_SCHED_H) + pa_daemon_conf *c; + int32_t rtprio; +#endif + + pa_assert(state); + +#ifdef OS_IS_WIN32 + pa_log("[%s:%u] Realtime priority not available on win32.", state->filename, state->lineno); +#else +# ifdef HAVE_SCHED_H + c = state->data; + + if (pa_atoi(state->rvalue, &rtprio) < 0 || rtprio < sched_get_priority_min(SCHED_FIFO) || rtprio > sched_get_priority_max(SCHED_FIFO)) { + pa_log("[%s:%u] Invalid realtime priority '%s'.", state->filename, state->lineno, state->rvalue); + return -1; + } + + c->realtime_priority = (int) rtprio; +# endif +#endif /* OS_IS_WIN32 */ + + return 0; +} + +static int parse_disable_lfe_remix(pa_config_parser_state *state) { + pa_daemon_conf *c; + int k; + + pa_assert(state); + c = state->data; + + if ((k = pa_parse_boolean(state->rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + c->remixing_produce_lfe = c->remixing_consume_lfe = !k; + + pa_log("[%s:%u] Deprecated option 'disable-lfe-remixing' found.", state->filename, state->lineno); + pa_log("[%s:%u] Please migrate to 'remixing-produce-lfe' and 'remixing-consume-lfe', set both to '%s'.", + state->filename, state->lineno, pa_yes_no(c->remixing_produce_lfe)); + + return 0; +} + +static int parse_enable_lfe_remix(pa_config_parser_state *state) { + pa_daemon_conf *c; + int k; + + pa_assert(state); + c = state->data; + + if ((k = pa_parse_boolean(state->rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + c->remixing_produce_lfe = c->remixing_consume_lfe = k; + + pa_log("[%s:%u] Deprecated option 'enable-lfe-remixing' found.", state->filename, state->lineno); + pa_log("[%s:%u] Please migrate to 'remixing-produce-lfe' and 'remixing-consume-lfe', set both to '%s'.", + state->filename, state->lineno, pa_yes_no(c->remixing_produce_lfe)); + + return 0; +} + +#ifdef HAVE_DBUS +static int parse_server_type(pa_config_parser_state *state) { + pa_daemon_conf *c; + + pa_assert(state); + + c = state->data; + + if (pa_daemon_conf_set_local_server_type(c, state->rvalue) < 0) { + pa_log(_("[%s:%u] Invalid server type '%s'."), state->filename, state->lineno, state->rvalue); + return -1; + } + + return 0; +} +#endif + +int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { + int r = -1; + FILE *f = NULL; + struct channel_conf_info ci; + pa_config_item table[] = { + { "daemonize", pa_config_parse_bool, &c->daemonize, NULL }, + { "fail", pa_config_parse_bool, &c->fail, NULL }, + { "high-priority", pa_config_parse_bool, &c->high_priority, NULL }, + { "realtime-scheduling", pa_config_parse_bool, &c->realtime_scheduling, NULL }, + { "disallow-module-loading", pa_config_parse_bool, &c->disallow_module_loading, NULL }, + { "allow-module-loading", pa_config_parse_not_bool, &c->disallow_module_loading, NULL }, + { "disallow-exit", pa_config_parse_bool, &c->disallow_exit, NULL }, + { "allow-exit", pa_config_parse_not_bool, &c->disallow_exit, NULL }, + { "use-pid-file", pa_config_parse_bool, &c->use_pid_file, NULL }, + { "system-instance", pa_config_parse_bool, &c->system_instance, NULL }, +#ifdef HAVE_DBUS + { "local-server-type", parse_server_type, c, NULL }, +#endif + { "no-cpu-limit", pa_config_parse_bool, &c->no_cpu_limit, NULL }, + { "cpu-limit", pa_config_parse_not_bool, &c->no_cpu_limit, NULL }, + { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, + { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "enable-memfd", pa_config_parse_not_bool, &c->disable_memfd, NULL }, + { "flat-volumes", pa_config_parse_bool, &c->flat_volumes, NULL }, + { "rescue-streams", pa_config_parse_bool, &c->rescue_streams, NULL }, + { "lock-memory", pa_config_parse_bool, &c->lock_memory, NULL }, + { "enable-deferred-volume", pa_config_parse_bool, &c->deferred_volume, NULL }, + { "exit-idle-time", pa_config_parse_int, &c->exit_idle_time, NULL }, + { "scache-idle-time", pa_config_parse_int, &c->scache_idle_time, NULL }, + { "realtime-priority", parse_rtprio, c, NULL }, + { "dl-search-path", pa_config_parse_string, &c->dl_search_path, NULL }, + { "default-script-file", pa_config_parse_string, &c->default_script_file, NULL }, + { "log-target", parse_log_target, c, NULL }, + { "log-level", parse_log_level, c, NULL }, + { "verbose", parse_log_level, c, NULL }, + { "resample-method", parse_resample_method, c, NULL }, + { "default-sample-format", parse_sample_format, c, NULL }, + { "default-sample-rate", parse_sample_rate, c, NULL }, + { "alternate-sample-rate", parse_alternate_sample_rate, c, NULL }, + { "default-sample-channels", parse_sample_channels, &ci, NULL }, + { "default-channel-map", parse_channel_map, &ci, NULL }, + { "default-fragments", parse_fragments, c, NULL }, + { "default-fragment-size-msec", parse_fragment_size_msec, c, NULL }, + { "deferred-volume-safety-margin-usec", + pa_config_parse_unsigned, &c->deferred_volume_safety_margin_usec, NULL }, + { "deferred-volume-extra-delay-usec", + pa_config_parse_int, &c->deferred_volume_extra_delay_usec, NULL }, + { "nice-level", parse_nice_level, c, NULL }, + { "avoid-resampling", pa_config_parse_bool, &c->avoid_resampling, NULL }, + { "disable-remixing", pa_config_parse_bool, &c->disable_remixing, NULL }, + { "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL }, + { "remixing-use-all-sink-channels", + pa_config_parse_bool, &c->remixing_use_all_sink_channels, NULL }, + { "disable-lfe-remixing", parse_disable_lfe_remix, c, NULL }, + { "enable-lfe-remixing", parse_enable_lfe_remix, c, NULL }, + { "remixing-produce-lfe", pa_config_parse_bool, &c->remixing_produce_lfe, NULL }, + { "remixing-consume-lfe", pa_config_parse_bool, &c->remixing_consume_lfe, NULL }, + { "lfe-crossover-freq", pa_config_parse_unsigned, &c->lfe_crossover_freq, NULL }, + { "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL }, + { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, + { "log-meta", pa_config_parse_bool, &c->log_meta, NULL }, + { "log-time", pa_config_parse_bool, &c->log_time, NULL }, + { "log-backtrace", pa_config_parse_unsigned, &c->log_backtrace, NULL }, +#ifdef HAVE_SYS_RESOURCE_H + { "rlimit-fsize", parse_rlimit, &c->rlimit_fsize, NULL }, + { "rlimit-data", parse_rlimit, &c->rlimit_data, NULL }, + { "rlimit-stack", parse_rlimit, &c->rlimit_stack, NULL }, + { "rlimit-core", parse_rlimit, &c->rlimit_core, NULL }, +#ifdef RLIMIT_RSS + { "rlimit-rss", parse_rlimit, &c->rlimit_rss, NULL }, +#endif +#ifdef RLIMIT_NOFILE + { "rlimit-nofile", parse_rlimit, &c->rlimit_nofile, NULL }, +#endif +#ifdef RLIMIT_AS + { "rlimit-as", parse_rlimit, &c->rlimit_as, NULL }, +#endif +#ifdef RLIMIT_NPROC + { "rlimit-nproc", parse_rlimit, &c->rlimit_nproc, NULL }, +#endif +#ifdef RLIMIT_MEMLOCK + { "rlimit-memlock", parse_rlimit, &c->rlimit_memlock, NULL }, +#endif +#ifdef RLIMIT_LOCKS + { "rlimit-locks", parse_rlimit, &c->rlimit_locks, NULL }, +#endif +#ifdef RLIMIT_SIGPENDING + { "rlimit-sigpending", parse_rlimit, &c->rlimit_sigpending, NULL }, +#endif +#ifdef RLIMIT_MSGQUEUE + { "rlimit-msgqueue", parse_rlimit, &c->rlimit_msgqueue, NULL }, +#endif +#ifdef RLIMIT_NICE + { "rlimit-nice", parse_rlimit, &c->rlimit_nice, NULL }, +#endif +#ifdef RLIMIT_RTPRIO + { "rlimit-rtprio", parse_rlimit, &c->rlimit_rtprio, NULL }, +#endif +#ifdef RLIMIT_RTTIME + { "rlimit-rttime", parse_rlimit, &c->rlimit_rttime, NULL }, +#endif +#endif + { NULL, NULL, NULL, NULL }, + }; + + pa_xfree(c->config_file); + c->config_file = NULL; + + f = filename ? + pa_fopen_cloexec(c->config_file = pa_xstrdup(filename), "r") : + pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file); + + if (!f && errno != ENOENT) { + pa_log_warn(_("Failed to open configuration file: %s"), pa_cstrerror(errno)); + goto finish; + } + + ci.default_channel_map_set = ci.default_sample_spec_set = false; + ci.conf = c; + + r = f ? pa_config_parse(c->config_file, f, table, NULL, true, NULL) : 0; + + if (r >= 0) { + + /* Make sure that channel map and sample spec fit together */ + + if (ci.default_sample_spec_set && + ci.default_channel_map_set && + c->default_channel_map.channels != c->default_sample_spec.channels) { + pa_log_error(_("The specified default channel map has a different number of channels than the specified default number of channels.")); + r = -1; + goto finish; + } else if (ci.default_sample_spec_set) + pa_channel_map_init_extend(&c->default_channel_map, c->default_sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); + else if (ci.default_channel_map_set) + c->default_sample_spec.channels = c->default_channel_map.channels; + } + +finish: + if (f) + fclose(f); + + return r; +} + +int pa_daemon_conf_env(pa_daemon_conf *c) { + char *e; + pa_assert(c); + + if ((e = getenv(ENV_DL_SEARCH_PATH))) { + pa_xfree(c->dl_search_path); + c->dl_search_path = pa_xstrdup(e); + } + if ((e = getenv(ENV_SCRIPT_FILE))) { + pa_xfree(c->default_script_file); + c->default_script_file = pa_xstrdup(e); + } + + return 0; +} + +const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c) { + pa_assert(c); + + if (!c->default_script_file) { + if (c->system_instance) + c->default_script_file = pa_find_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE); + else + c->default_script_file = pa_find_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE); + } + + return c->default_script_file; +} + +FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) { + FILE *f; + pa_assert(c); + + if (!c->default_script_file) { + if (c->system_instance) + f = pa_open_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE, &c->default_script_file); + else + f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file); + } else + f = pa_fopen_cloexec(c->default_script_file, "r"); + + return f; +} + +char *pa_daemon_conf_dump(pa_daemon_conf *c) { + static const char* const log_level_to_string[] = { + [PA_LOG_DEBUG] = "debug", + [PA_LOG_INFO] = "info", + [PA_LOG_NOTICE] = "notice", + [PA_LOG_WARN] = "warning", + [PA_LOG_ERROR] = "error" + }; + +#ifdef HAVE_DBUS + static const char* const server_type_to_string[] = { + [PA_SERVER_TYPE_UNSET] = "!!UNSET!!", + [PA_SERVER_TYPE_USER] = "user", + [PA_SERVER_TYPE_SYSTEM] = "system", + [PA_SERVER_TYPE_NONE] = "none" + }; +#endif + + pa_strbuf *s; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char *log_target = NULL; + + pa_assert(c); + + s = pa_strbuf_new(); + + if (c->config_file) + pa_strbuf_printf(s, _("### Read from configuration file: %s ###\n"), c->config_file); + + pa_assert(c->log_level < PA_LOG_LEVEL_MAX); + + if (c->log_target) + log_target = pa_log_target_to_string(c->log_target); + + pa_strbuf_printf(s, "daemonize = %s\n", pa_yes_no(c->daemonize)); + pa_strbuf_printf(s, "fail = %s\n", pa_yes_no(c->fail)); + pa_strbuf_printf(s, "high-priority = %s\n", pa_yes_no(c->high_priority)); + pa_strbuf_printf(s, "nice-level = %i\n", c->nice_level); + pa_strbuf_printf(s, "realtime-scheduling = %s\n", pa_yes_no(c->realtime_scheduling)); + pa_strbuf_printf(s, "realtime-priority = %i\n", c->realtime_priority); + pa_strbuf_printf(s, "allow-module-loading = %s\n", pa_yes_no(!c->disallow_module_loading)); + pa_strbuf_printf(s, "allow-exit = %s\n", pa_yes_no(!c->disallow_exit)); + pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file)); + pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance)); +#ifdef HAVE_DBUS + pa_strbuf_printf(s, "local-server-type = %s\n", server_type_to_string[c->local_server_type]); +#endif + pa_strbuf_printf(s, "cpu-limit = %s\n", pa_yes_no(!c->no_cpu_limit)); + pa_strbuf_printf(s, "enable-shm = %s\n", pa_yes_no(!c->disable_shm)); + pa_strbuf_printf(s, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes)); + pa_strbuf_printf(s, "rescue-streams = %s\n", pa_yes_no(c->rescue_streams)); + pa_strbuf_printf(s, "lock-memory = %s\n", pa_yes_no(c->lock_memory)); + pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time); + pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time); + pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path)); + pa_strbuf_printf(s, "default-script-file = %s\n", pa_strempty(pa_daemon_conf_get_default_script_file(c))); + pa_strbuf_printf(s, "load-default-script-file = %s\n", pa_yes_no(c->load_default_script_file)); + pa_strbuf_printf(s, "log-target = %s\n", pa_strempty(log_target)); + pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]); + pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method)); + pa_strbuf_printf(s, "avoid-resampling = %s\n", pa_yes_no(c->avoid_resampling)); + pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing)); + pa_strbuf_printf(s, "remixing-use-all-sink-channels = %s\n", pa_yes_no(c->remixing_use_all_sink_channels)); + pa_strbuf_printf(s, "remixing-produce-lfe = %s\n", pa_yes_no(c->remixing_produce_lfe)); + pa_strbuf_printf(s, "remixing-consume-lfe = %s\n", pa_yes_no(c->remixing_consume_lfe)); + pa_strbuf_printf(s, "lfe-crossover-freq = %u\n", c->lfe_crossover_freq); + pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format)); + pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate); + pa_strbuf_printf(s, "alternate-sample-rate = %u\n", c->alternate_sample_rate); + pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels); + pa_strbuf_printf(s, "default-channel-map = %s\n", pa_channel_map_snprint(cm, sizeof(cm), &c->default_channel_map)); + pa_strbuf_printf(s, "default-fragments = %u\n", c->default_n_fragments); + pa_strbuf_printf(s, "default-fragment-size-msec = %u\n", c->default_fragment_size_msec); + pa_strbuf_printf(s, "enable-deferred-volume = %s\n", pa_yes_no(c->deferred_volume)); + pa_strbuf_printf(s, "deferred-volume-safety-margin-usec = %u\n", c->deferred_volume_safety_margin_usec); + pa_strbuf_printf(s, "deferred-volume-extra-delay-usec = %d\n", c->deferred_volume_extra_delay_usec); + pa_strbuf_printf(s, "shm-size-bytes = %lu\n", (unsigned long) c->shm_size); + pa_strbuf_printf(s, "log-meta = %s\n", pa_yes_no(c->log_meta)); + pa_strbuf_printf(s, "log-time = %s\n", pa_yes_no(c->log_time)); + pa_strbuf_printf(s, "log-backtrace = %u\n", c->log_backtrace); +#ifdef HAVE_SYS_RESOURCE_H + pa_strbuf_printf(s, "rlimit-fsize = %li\n", c->rlimit_fsize.is_set ? (long int) c->rlimit_fsize.value : -1); + pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1); + pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1); + pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1); +#ifdef RLIMIT_RSS + pa_strbuf_printf(s, "rlimit-rss = %li\n", c->rlimit_rss.is_set ? (long int) c->rlimit_rss.value : -1); +#endif +#ifdef RLIMIT_AS + pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1); +#endif +#ifdef RLIMIT_NPROC + pa_strbuf_printf(s, "rlimit-nproc = %li\n", c->rlimit_nproc.is_set ? (long int) c->rlimit_nproc.value : -1); +#endif +#ifdef RLIMIT_NOFILE + pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1); +#endif +#ifdef RLIMIT_MEMLOCK + pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1); +#endif +#ifdef RLIMIT_LOCKS + pa_strbuf_printf(s, "rlimit-locks = %li\n", c->rlimit_locks.is_set ? (long int) c->rlimit_locks.value : -1); +#endif +#ifdef RLIMIT_SIGPENDING + pa_strbuf_printf(s, "rlimit-sigpending = %li\n", c->rlimit_sigpending.is_set ? (long int) c->rlimit_sigpending.value : -1); +#endif +#ifdef RLIMIT_MSGQUEUE + pa_strbuf_printf(s, "rlimit-msgqueue = %li\n", c->rlimit_msgqueue.is_set ? (long int) c->rlimit_msgqueue.value : -1); +#endif +#ifdef RLIMIT_NICE + pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_nice.is_set ? (long int) c->rlimit_nice.value : -1); +#endif +#ifdef RLIMIT_RTPRIO + pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_rtprio.is_set ? (long int) c->rlimit_rtprio.value : -1); +#endif +#ifdef RLIMIT_RTTIME + pa_strbuf_printf(s, "rlimit-rttime = %li\n", c->rlimit_rttime.is_set ? (long int) c->rlimit_rttime.value : -1); +#endif +#endif + + pa_xfree(log_target); + + return pa_strbuf_to_string_free(s); +} diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h new file mode 100644 index 0000000..fa713b9 --- /dev/null +++ b/src/daemon/daemon-conf.h @@ -0,0 +1,169 @@ +#ifndef foodaemonconfhfoo +#define foodaemonconfhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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 + 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/>. +***/ + +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +/* The actual command to execute */ +typedef enum pa_daemon_conf_cmd { + PA_CMD_DAEMON, /* the default */ + PA_CMD_START, + PA_CMD_HELP, + PA_CMD_VERSION, + PA_CMD_DUMP_CONF, + PA_CMD_DUMP_MODULES, + PA_CMD_KILL, + PA_CMD_CHECK, + PA_CMD_DUMP_RESAMPLE_METHODS, + PA_CMD_CLEANUP_SHM +} pa_daemon_conf_cmd_t; + +#ifdef HAVE_SYS_RESOURCE_H +typedef struct pa_rlimit { + rlim_t value; + bool is_set; +} pa_rlimit; +#endif + +/* A structure containing configuration data for the PulseAudio server . */ +typedef struct pa_daemon_conf { + pa_daemon_conf_cmd_t cmd; + bool daemonize, + fail, + high_priority, + realtime_scheduling, + disallow_module_loading, + use_pid_file, + system_instance, + no_cpu_limit, + disable_shm, + disable_memfd, + avoid_resampling, + disable_remixing, + remixing_use_all_sink_channels, + remixing_produce_lfe, + remixing_consume_lfe, + load_default_script_file, + disallow_exit, + log_meta, + log_time, + flat_volumes, + rescue_streams, + lock_memory, + deferred_volume; + pa_server_type_t local_server_type; + int exit_idle_time, + scache_idle_time, + realtime_priority, + nice_level, + resample_method; + char *script_commands, *dl_search_path, *default_script_file; + pa_log_target *log_target; + pa_log_level_t log_level; + unsigned log_backtrace; + char *config_file; + +#ifdef HAVE_SYS_RESOURCE_H + pa_rlimit rlimit_fsize, rlimit_data, rlimit_stack, rlimit_core; +#ifdef RLIMIT_RSS + pa_rlimit rlimit_rss; +#endif +#ifdef RLIMIT_NOFILE + pa_rlimit rlimit_nofile; +#endif +#ifdef RLIMIT_AS + pa_rlimit rlimit_as; +#endif +#ifdef RLIMIT_NPROC + pa_rlimit rlimit_nproc; +#endif +#ifdef RLIMIT_MEMLOCK + pa_rlimit rlimit_memlock; +#endif +#ifdef RLIMIT_LOCKS + pa_rlimit rlimit_locks; +#endif +#ifdef RLIMIT_SIGPENDING + pa_rlimit rlimit_sigpending; +#endif +#ifdef RLIMIT_MSGQUEUE + pa_rlimit rlimit_msgqueue; +#endif +#ifdef RLIMIT_NICE + pa_rlimit rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO + pa_rlimit rlimit_rtprio; +#endif +#ifdef RLIMIT_RTTIME + pa_rlimit rlimit_rttime; +#endif +#endif + + unsigned default_n_fragments, default_fragment_size_msec; + unsigned deferred_volume_safety_margin_usec; + int deferred_volume_extra_delay_usec; + unsigned lfe_crossover_freq; + pa_sample_spec default_sample_spec; + uint32_t alternate_sample_rate; + pa_channel_map default_channel_map; + size_t shm_size; +} pa_daemon_conf; + +/* Allocate a new structure and fill it with sane defaults */ +pa_daemon_conf* pa_daemon_conf_new(void); +void pa_daemon_conf_free(pa_daemon_conf*c); + +/* Load configuration data from the specified file overwriting the + * current settings in *c. If filename is NULL load the default daemon + * configuration file */ +int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename); + +/* Pretty print the current configuration data of the daemon. The + * returned string has to be freed manually. The output of this + * function may be parsed with pa_daemon_conf_load(). */ +char *pa_daemon_conf_dump(pa_daemon_conf *c); + +/* Load the configuration data from the process' environment + * overwriting the current settings in *c. */ +int pa_daemon_conf_env(pa_daemon_conf *c); + +/* Set these configuration variables in the structure by passing a string */ +int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string); + +const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c); +FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c); + +#endif diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in new file mode 100644 index 0000000..7409976 --- /dev/null +++ b/src/daemon/daemon.conf.in @@ -0,0 +1,97 @@ +# This file is part of PulseAudio. +# +# 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. +changequote(`[', `]')dnl Set up m4 quoting + +; daemonize = no +; fail = yes +; allow-module-loading = yes +; allow-exit = yes +; use-pid-file = yes +; system-instance = no +ifelse(@HAVE_DBUS@, 1, [dnl +; local-server-type = user +])dnl +; enable-shm = yes +; enable-memfd = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB +; lock-memory = no +; cpu-limit = no + +; high-priority = yes +; nice-level = -11 + +; realtime-scheduling = yes +; realtime-priority = 5 + +; exit-idle-time = 20 +; scache-idle-time = 20 + +; dl-search-path = (depends on architecture) + +; load-default-script-file = yes +; default-script-file = @PA_DEFAULT_CONFIG_DIR@/default.pa + +; log-target = auto +; log-level = notice +; log-meta = no +; log-time = no +; log-backtrace = 0 + +; resample-method = speex-float-1 +; avoid-resampling = false +; enable-remixing = yes +; remixing-use-all-sink-channels = yes +; remixing-produce-lfe = no +; remixing-consume-lfe = no +; lfe-crossover-freq = 0 + +; flat-volumes = no + +; rescue-streams = yes + +ifelse(@HAVE_SYS_RESOURCE_H@, 1, [dnl +; rlimit-fsize = -1 +; rlimit-data = -1 +; rlimit-stack = -1 +; rlimit-core = -1 +; rlimit-as = -1 +; rlimit-rss = -1 +; rlimit-nproc = -1 +; rlimit-nofile = 256 +; rlimit-memlock = -1 +; rlimit-locks = -1 +; rlimit-sigpending = -1 +; rlimit-msgqueue = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 +; rlimit-rttime = 200000 +])dnl + +; default-sample-format = s16le +; default-sample-rate = 44100 +; alternate-sample-rate = 48000 +; default-sample-channels = 2 +; default-channel-map = front-left,front-right + +; default-fragments = 4 +; default-fragment-size-msec = 25 + +; enable-deferred-volume = yes +; deferred-volume-safety-margin-usec = 8000 +; deferred-volume-extra-delay-usec = 0 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in new file mode 100755 index 0000000..030334f --- /dev/null +++ b/src/daemon/default.pa.in @@ -0,0 +1,175 @@ +#!@PA_BINARY@ -nF +# +# This file is part of PulseAudio. +# +# 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +# This startup script is used only if PulseAudio is started per-user +# (i.e. not in system mode) +changequote(`[', `]')dnl Set up m4 quoting + +.fail + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Automatically augment property information from .desktop files +### stored in /usr/share/application +load-module module-augment-properties + +### Should be after module-*-restore but before module-*-detect +load-module module-switch-on-port-available + +### Load audio drivers statically +### (it's probably better to not load these drivers manually, but instead +### use module-udev-detect -- see below -- for doing this automatically) +ifelse(@HAVE_ALSA@, 1, [dnl +#load-module module-alsa-sink +#load-module module-alsa-source device=hw:1,0 +])dnl +ifelse(@HAVE_OSS_OUTPUT@, 1, [dnl +#load-module module-oss device="/dev/dsp" sink_name=output source_name=input +#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input +])dnl +ifelse(@HAVE_WAVEOUT@, 1, [dnl +load-module module-waveout sink_name=output source_name=input +])dnl +#load-module module-null-sink +ifelse(@HAVE_MKFIFO@, 1, [dnl +#load-module module-pipe-sink +])dnl + +### Automatically load driver modules depending on the hardware available +ifelse(@HAVE_UDEV@, 1, [dnl +.ifexists module-udev-detect@PA_SOEXT@ +load-module module-udev-detect +.else +], @HAVE_COREAUDIO@, 1, [dnl +.ifexists module-coreaudio-detect@PA_SOEXT@ +load-module module-coreaudio-detect +.else +], [dnl +.ifexists module-detect@PA_SOEXT@ +])dnl +### Use the static hardware detection module (for systems that lack udev support) +load-module module-detect +.endif + +### Automatically connect sink and source if JACK server is present +.ifexists module-jackdbus-detect@PA_SOEXT@ +.nofail +load-module module-jackdbus-detect channels=2 +.fail +.endif + +ifelse(@HAVE_BLUEZ@, 1, [dnl +### Automatically load driver modules for Bluetooth hardware +.ifexists module-bluetooth-policy@PA_SOEXT@ +load-module module-bluetooth-policy +.endif + +.ifexists module-bluetooth-discover@PA_SOEXT@ +load-module module-bluetooth-discover +.endif +])dnl + +ifelse(@HAVE_AF_UNIX@, 1, [dnl +### Load several protocols +.ifexists module-esound-protocol-unix@PA_SOEXT@ +load-module module-esound-protocol-unix +.endif +load-module module-native-protocol-unix +])dnl + +### Network access (may be configured with paprefs, so leave this commented +### here if you plan to use paprefs) +#load-module module-esound-protocol-tcp +#load-module module-native-protocol-tcp +ifelse(@HAVE_AVAHI@, 1, [dnl +#load-module module-zeroconf-publish +])dnl + +ifelse(@OS_IS_WIN32@, 0, [dnl +### Load the RTP receiver module (also configured via paprefs, see above) +#load-module module-rtp-recv + +### Load the RTP sender module (also configured via paprefs, see above) +#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'" +#load-module module-rtp-send source=rtp.monitor + +ifelse(@HAVE_GSETTINGS@, 1, [dnl +### Load additional modules from GSettings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists module-gsettings@PA_SOEXT@ +.nofail +load-module module-gsettings +.fail +.endif +])dnl + +ifelse(@HAVE_GCONF@, 1, [dnl +### Load additional modules from GConf settings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists module-gconf@PA_SOEXT@ +.nofail +load-module module-gconf +.fail +.endif +])dnl + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Honour intended role device property +load-module module-intended-roles + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### If autoexit on idle is enabled we want to make sure we only quit +### when no local session needs us anymore. +.ifexists module-console-kit@PA_SOEXT@ +load-module module-console-kit +.endif +.ifexists module-systemd-login@PA_SOEXT@ +load-module module-systemd-login +.endif + +### Enable positioned event sounds +load-module module-position-event-sounds + +### Cork music/video streams when a phone stream is active +load-module module-role-cork + +### Modules to allow autoloading of filters (such as echo cancellation) +### on demand. module-filter-heuristics tries to determine what filters +### make sense, and module-filter-apply does the heavy-lifting of +### loading modules and rerouting streams. +load-module module-filter-heuristics +load-module module-filter-apply +])dnl + +### Make some devices default +#set-default-sink output +#set-default-source input diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c new file mode 100644 index 0000000..8410bbc --- /dev/null +++ b/src/daemon/dumpmodules.c @@ -0,0 +1,154 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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 + 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 <string.h> +#include <stdio.h> +#include <ltdl.h> + +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/modinfo.h> + +#include "dumpmodules.h" + +#define PREFIX "module-" + +static void short_info(const char *name, const char *path, pa_modinfo *i) { + pa_assert(name); + pa_assert(i); + + printf("%-40s%s\n", name, i->description ? i->description : "n/a"); +} + +static void long_info(const char *name, const char *path, pa_modinfo *i) { + static int nl = 0; + pa_assert(name); + pa_assert(i); + + if (nl) + printf("\n"); + + nl = 1; + + printf(_("Name: %s\n"), name); + + if (!i->description && !i->version && !i->author && !i->usage) + printf(_("No module information available\n")); + else { + if (i->version) + printf(_("Version: %s\n"), i->version); + if (i->description) + printf(_("Description: %s\n"), i->description); + if (i->author) + printf(_("Author: %s\n"), i->author); + if (i->usage) + printf(_("Usage: %s\n"), i->usage); + printf(_("Load Once: %s\n"), pa_yes_no(i->load_once)); + if (i->deprecated) + printf(_("DEPRECATION WARNING: %s\n"), i->deprecated); + } + + if (path) + printf(_("Path: %s\n"), path); +} + +static void show_info(const char *name, const char *path, void (*info)(const char *name, const char *path, pa_modinfo*i)) { + pa_modinfo *i; + + pa_assert(name); + + if ((i = pa_modinfo_get_by_name(path ? path : name))) { + info(name, path, i); + pa_modinfo_free(i); + } +} + +static int is_preloaded(const char *name) { + const lt_dlsymlist *l; + + for (l = lt_preloaded_symbols; l->name; l++) { + char buf[64], *e; + + if (l->address) + continue; + + pa_snprintf(buf, sizeof(buf), "%s", l->name); + if ((e = strrchr(buf, '.'))) + *e = 0; + + if (pa_streq(name, buf)) + return 1; + } + + return 0; +} + +static int callback(const char *path, lt_ptr data) { + const char *e; + pa_daemon_conf *c = (data); + + e = pa_path_get_filename(path); + + if (strlen(e) <= sizeof(PREFIX)-1 || strncmp(e, PREFIX, sizeof(PREFIX)-1)) + return 0; + + if (is_preloaded(e)) + return 0; + + show_info(e, path, c->log_level >= PA_LOG_INFO ? long_info : short_info); + return 0; +} + +void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]) { + pa_assert(c); + + if (argc > 0) { + int i; + for (i = 0; i < argc; i++) + show_info(argv[i], NULL, long_info); + } else { + const lt_dlsymlist *l; + + for (l = lt_preloaded_symbols; l->name; l++) { + char buf[64], *e; + + if (l->address) + continue; + + if (strlen(l->name) <= sizeof(PREFIX)-1 || strncmp(l->name, PREFIX, sizeof(PREFIX)-1)) + continue; + + pa_snprintf(buf, sizeof(buf), "%s", l->name); + if ((e = strrchr(buf, '.'))) + *e = 0; + + show_info(buf, NULL, c->log_level >= PA_LOG_INFO ? long_info : short_info); + } + + lt_dlforeachfile(NULL, callback, c); + } +} diff --git a/src/daemon/dumpmodules.h b/src/daemon/dumpmodules.h new file mode 100644 index 0000000..289f35d --- /dev/null +++ b/src/daemon/dumpmodules.h @@ -0,0 +1,29 @@ +#ifndef foodumpmoduleshfoo +#define foodumpmoduleshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include "daemon-conf.h" + +/* Dump all available modules to STDOUT. If argc > 0 print information + * about the modules specified in argv[] instead. */ +void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]); + +#endif diff --git a/src/daemon/esdcompat.in b/src/daemon/esdcompat.in new file mode 100755 index 0000000..361ca63 --- /dev/null +++ b/src/daemon/esdcompat.in @@ -0,0 +1,94 @@ +#!/bin/sh + +# This file is part of PulseAudio. +# +# 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +VERSION_STRING="@PACKAGE_NAME@ esd wrapper @PACKAGE_VERSION@" + +fail() { + echo "ERROR: $1" + exit 1 +} + +ARGS=" --log-target=syslog" + +while [ "$#" -gt "0" ]; do + + case "$1" in + "") + ;; + + -v|--version) + echo "$VERSION_STRING" + exit 0 + ;; + + -h|--help) + cat <<EOF +$VERSION_STRING + +Usage: $0 [options] + + -v --version print version information + -h --help show this help + +Ignored directives: + + -tcp use tcp/ip sockets in addition to unix domain + -promiscuous don't require authentication + -d DEVICE force esd to use sound device DEVICE + -b run server in 8 bit sound mode + -r RATE run server at sample rate of RATE + -as SECS free audio device after SECS of inactivity + -unix use unix domain sockets instead of tcp/ip + -public make tcp/ip access public (other than localhost) + -terminate terminate esd daemone after last client exits + -nobeeps disable startup beeps + -trust start esd even if use of /tmp/.esd can be insecure + -port PORT listen for connections at PORT (only for tcp/ip) + -bind ADDRESS binds to ADDRESS (only for tcp/ip) +EOF + exit 0 + ;; + + -spawnpid) + shift + ARGS="$ARGS '-Lmodule-esound-compat-spawnpid pid=$1'" + ;; + + -spawnfd) + shift + ARGS="$ARGS '-Lmodule-esound-compat-spawnfd fd=$1'" + ;; + + -unix|-b|-public|-terminate|-nobeeps|-trust|-tcp|-promiscuous) + # Ignore these commands + ;; + + -d|-r|-as|-port|-bind) + # Ignore these commands and their arguments + shift + + ;; + + *) + fail "Unknown command: $1" + ;; + esac + + shift +done + +eval "exec '@PA_BINARY@'$ARGS" diff --git a/src/daemon/ltdl-bind-now.c b/src/daemon/ltdl-bind-now.c new file mode 100644 index 0000000..3492571 --- /dev/null +++ b/src/daemon/ltdl-bind-now.c @@ -0,0 +1,159 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 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 + 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 + +#ifdef HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#ifdef HAVE_SYS_DL_H +#include <sys/dl.h> +#endif + +#include <string.h> + +#include <ltdl.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +#include "ltdl-bind-now.h" + +#ifdef RTLD_NOW +#define PA_BIND_NOW RTLD_NOW +#elif defined(DL_NOW) +#define PA_BIND_NOW DL_NOW +#else +#undef PA_BIND_NOW +#endif + +#ifdef OS_IS_WIN32 +#undef PA_BIND_NOW +#endif + +#ifdef PA_BIND_NOW + +/* + To avoid lazy relocations during runtime in our RT threads we add + our own shared object loader with uses RTLD_NOW if it is + available. The standard ltdl loader prefers RTLD_LAZY. + + Please note that this loader doesn't have any influence on + relocations on any libraries that are already loaded into our + process, i.e. because the pulseaudio binary links directly to + them. To disable lazy relocations for those libraries it is possible + to set $LT_BIND_NOW before starting the pulseaudio binary. +*/ + +static lt_module bind_now_open(lt_user_data d, const char *fname, lt_dladvise advise) { + lt_module m; + + pa_assert(fname); + + if (!(m = dlopen(fname, PA_BIND_NOW))) { + pa_log(_("Failed to open module %s: %s"), fname, dlerror()); + lt_dlseterror(LT_ERROR_CANNOT_OPEN); + return NULL; + } + + return m; +} + +static int bind_now_close(lt_user_data d, lt_module m) { + + pa_assert(m); + + if (dlclose(m) != 0) { + lt_dlseterror(LT_ERROR_CANNOT_CLOSE); + return 1; + } + + return 0; +} + +static lt_ptr bind_now_find_sym(lt_user_data d, lt_module m, const char *symbol) { + lt_ptr ptr; + + pa_assert(m); + pa_assert(symbol); + + if (!(ptr = dlsym(m, symbol))) { + lt_dlseterror(LT_ERROR_SYMBOL_NOT_FOUND); + return NULL; + } + + return ptr; +} + +static lt_dlvtable *bindnow_loader = NULL; +#endif + +void pa_ltdl_init(void) { + +#ifdef PA_BIND_NOW + const lt_dlvtable *dlopen_loader; +#endif + + pa_assert_se(lt_dlinit() == 0); + +#ifdef PA_BIND_NOW + /* Already initialised */ + if (bindnow_loader) + return; + + if (!(dlopen_loader = lt_dlloader_find((char*) "lt_dlopen"))) { + pa_log_warn(_("Failed to find original lt_dlopen loader.")); + return; + } + + if (!(bindnow_loader = malloc(sizeof(lt_dlvtable)))) { + pa_log_error(_("Failed to allocate new dl loader.")); + return; + } + + memcpy(bindnow_loader, dlopen_loader, sizeof(*bindnow_loader)); + bindnow_loader->name = "bind-now-loader"; + bindnow_loader->module_open = bind_now_open; + bindnow_loader->module_close = bind_now_close; + bindnow_loader->find_sym = bind_now_find_sym; + bindnow_loader->priority = LT_DLLOADER_PREPEND; + + /* Add our BIND_NOW loader as the default module loader. */ + if (lt_dlloader_add(bindnow_loader) != 0) { + pa_log_warn(_("Failed to add bind-now-loader.")); + free(bindnow_loader); + bindnow_loader = NULL; + } +#endif +} + +void pa_ltdl_done(void) { + pa_assert_se(lt_dlexit() == 0); + +#ifdef PA_BIND_NOW + /* lt_dlexit() will free our loader vtable, hence reset our + * pointer to it here */ + bindnow_loader = NULL; +#endif +} diff --git a/src/daemon/ltdl-bind-now.h b/src/daemon/ltdl-bind-now.h new file mode 100644 index 0000000..bb6d83f --- /dev/null +++ b/src/daemon/ltdl-bind-now.h @@ -0,0 +1,27 @@ +#ifndef foopulsecoreltdlbindnowhfoo +#define foopulsecoreltdlbindnowhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +void pa_ltdl_init(void); +void pa_ltdl_done(void); + +#endif + diff --git a/src/daemon/main.c b/src/daemon/main.c new file mode 100644 index 0000000..30ef499 --- /dev/null +++ b/src/daemon/main.c @@ -0,0 +1,1247 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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 + 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 <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <stddef.h> +#include <ltdl.h> +#include <limits.h> +#include <unistd.h> +#include <locale.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifdef HAVE_LIBWRAP +#include <syslog.h> +#include <tcpd.h> +#endif + +#ifdef HAVE_DBUS +#include <dbus/dbus.h> +#endif + +#ifdef HAVE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + +#include <pulse/client-conf.h> +#include <pulse/mainloop.h> +#include <pulse/mainloop-signal.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/lock-autospawn.h> +#include <pulsecore/socket.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/cli-command.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sioman.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/pid.h> +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/shm.h> +#include <pulsecore/memtrap.h> +#include <pulsecore/strlist.h> +#ifdef HAVE_DBUS +#include <pulsecore/dbus-shared.h> +#endif +#include <pulsecore/cpu.h> + +#include "cmdline.h" +#include "cpulimit.h" +#include "daemon-conf.h" +#include "dumpmodules.h" +#include "caps.h" +#include "ltdl-bind-now.h" +#include "server-lookup.h" + +#ifdef DISABLE_LIBTOOL_PRELOAD +/* FIXME: work around a libtool bug by making sure we have 2 elements. Bug has + * been reported: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29576 */ +const lt_dlsymlist lt_preloaded_symbols[] = { + { "@PROGRAM@", NULL }, + { NULL, NULL } +}; +#endif + +#ifdef HAVE_LIBWRAP +/* Only one instance of these variables */ +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#ifdef HAVE_OSS_WRAPPER +/* padsp looks for this symbol in the running process and disables + * itself if it finds it and it is set to 7 (which is actually a bit + * mask). For details see padsp. */ +int __padsp_disabled__ = 7; +#endif + +static void signal_callback(pa_mainloop_api* m, pa_signal_event *e, int sig, void *userdata) { + pa_module *module = NULL; + + pa_log_info("Got signal %s.", pa_sig2str(sig)); + + switch (sig) { +#ifdef SIGUSR1 + case SIGUSR1: + pa_module_load(&module, userdata, "module-cli", NULL); + break; +#endif + +#ifdef SIGUSR2 + case SIGUSR2: + pa_module_load(&module, userdata, "module-cli-protocol-unix", NULL); + break; +#endif + +#ifdef SIGHUP + case SIGHUP: { + char *c = pa_full_status_string(userdata); + pa_log_notice("%s", c); + pa_xfree(c); + return; + } +#endif + + case SIGINT: + case SIGTERM: + default: + pa_log_info("Exiting."); + m->quit(m, 0); + break; + } +} + +#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H) + +static int change_user(void) { + struct passwd *pw; + struct group * gr; + int r; + + /* This function is called only in system-wide mode. It creates a + * runtime dir in /var/run/ with proper UID/GID and drops privs + * afterwards. */ + + if (!(pw = getpwnam(PA_SYSTEM_USER))) { + pa_log(_("Failed to find user '%s'."), PA_SYSTEM_USER); + return -1; + } + + if (!(gr = getgrnam(PA_SYSTEM_GROUP))) { + pa_log(_("Failed to find group '%s'."), PA_SYSTEM_GROUP); + return -1; + } + + pa_log_info("Found user '%s' (UID %lu) and group '%s' (GID %lu).", + PA_SYSTEM_USER, (unsigned long) pw->pw_uid, + PA_SYSTEM_GROUP, (unsigned long) gr->gr_gid); + + if (pw->pw_gid != gr->gr_gid) { + pa_log(_("GID of user '%s' and of group '%s' don't match."), PA_SYSTEM_USER, PA_SYSTEM_GROUP); + return -1; + } + + if (!pa_streq(pw->pw_dir, PA_SYSTEM_RUNTIME_PATH)) + pa_log_warn(_("Home directory of user '%s' is not '%s', ignoring."), PA_SYSTEM_USER, PA_SYSTEM_RUNTIME_PATH); + + if (pa_make_secure_dir(PA_SYSTEM_RUNTIME_PATH, 0755, pw->pw_uid, gr->gr_gid, true) < 0) { + pa_log(_("Failed to create '%s': %s"), PA_SYSTEM_RUNTIME_PATH, pa_cstrerror(errno)); + return -1; + } + + if (pa_make_secure_dir(PA_SYSTEM_STATE_PATH, 0700, pw->pw_uid, gr->gr_gid, true) < 0) { + pa_log(_("Failed to create '%s': %s"), PA_SYSTEM_STATE_PATH, pa_cstrerror(errno)); + return -1; + } + + /* We don't create the config dir here, because we don't need to write to it */ + + if (initgroups(PA_SYSTEM_USER, gr->gr_gid) != 0) { + pa_log(_("Failed to change group list: %s"), pa_cstrerror(errno)); + return -1; + } + +#if defined(HAVE_SETRESGID) + r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid); +#elif defined(HAVE_SETEGID) + if ((r = setgid(gr->gr_gid)) >= 0) + r = setegid(gr->gr_gid); +#elif defined(HAVE_SETREGID) + r = setregid(gr->gr_gid, gr->gr_gid); +#else +#error "No API to drop privileges" +#endif + + if (r < 0) { + pa_log(_("Failed to change GID: %s"), pa_cstrerror(errno)); + return -1; + } + +#if defined(HAVE_SETRESUID) + r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid); +#elif defined(HAVE_SETEUID) + if ((r = setuid(pw->pw_uid)) >= 0) + r = seteuid(pw->pw_uid); +#elif defined(HAVE_SETREUID) + r = setreuid(pw->pw_uid, pw->pw_uid); +#else +#error "No API to drop privileges" +#endif + + if (r < 0) { + pa_log(_("Failed to change UID: %s"), pa_cstrerror(errno)); + return -1; + } + + pa_drop_caps(); + + pa_set_env("USER", PA_SYSTEM_USER); + pa_set_env("USERNAME", PA_SYSTEM_USER); + pa_set_env("LOGNAME", PA_SYSTEM_USER); + pa_set_env("HOME", PA_SYSTEM_RUNTIME_PATH); + + /* Relevant for pa_runtime_path() */ + if (!getenv("PULSE_RUNTIME_PATH")) + pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); + + if (!getenv("PULSE_CONFIG_PATH")) + pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH); + + if (!getenv("PULSE_STATE_PATH")) + pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH); + + pa_log_info("Successfully changed user to \"" PA_SYSTEM_USER "\"."); + + return 0; +} + +#else /* HAVE_PWD_H && HAVE_GRP_H */ + +static int change_user(void) { + pa_log(_("System wide mode unsupported on this platform.")); + return -1; +} + +#endif /* HAVE_PWD_H && HAVE_GRP_H */ + +#ifdef HAVE_SYS_RESOURCE_H + +static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) { + struct rlimit rl; + pa_assert(r); + + if (!r->is_set) + return 0; + + rl.rlim_cur = rl.rlim_max = r->value; + + if (setrlimit(resource, &rl) < 0) { + pa_log_info("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +static void set_all_rlimits(const pa_daemon_conf *conf) { + set_one_rlimit(&conf->rlimit_fsize, RLIMIT_FSIZE, "RLIMIT_FSIZE"); + set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA"); + set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK"); + set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE"); +#ifdef RLIMIT_RSS + set_one_rlimit(&conf->rlimit_rss, RLIMIT_RSS, "RLIMIT_RSS"); +#endif +#ifdef RLIMIT_NPROC + set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC"); +#endif +#ifdef RLIMIT_NOFILE + set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE"); +#endif +#ifdef RLIMIT_MEMLOCK + set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK"); +#endif +#ifdef RLIMIT_AS + set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS"); +#endif +#ifdef RLIMIT_LOCKS + set_one_rlimit(&conf->rlimit_locks, RLIMIT_LOCKS, "RLIMIT_LOCKS"); +#endif +#ifdef RLIMIT_SIGPENDING + set_one_rlimit(&conf->rlimit_sigpending, RLIMIT_SIGPENDING, "RLIMIT_SIGPENDING"); +#endif +#ifdef RLIMIT_MSGQUEUE + set_one_rlimit(&conf->rlimit_msgqueue, RLIMIT_MSGQUEUE, "RLIMIT_MSGQUEUE"); +#endif +#ifdef RLIMIT_NICE + set_one_rlimit(&conf->rlimit_nice, RLIMIT_NICE, "RLIMIT_NICE"); +#endif +#ifdef RLIMIT_RTPRIO + set_one_rlimit(&conf->rlimit_rtprio, RLIMIT_RTPRIO, "RLIMIT_RTPRIO"); +#endif +#ifdef RLIMIT_RTTIME + set_one_rlimit(&conf->rlimit_rttime, RLIMIT_RTTIME, "RLIMIT_RTTIME"); +#endif +} +#endif + +static char *check_configured_address(void) { + char *default_server = NULL; + pa_client_conf *c = pa_client_conf_new(); + + pa_client_conf_load(c, true, true); + + if (c->default_server && *c->default_server) + default_server = pa_xstrdup(c->default_server); + + pa_client_conf_free(c); + + return default_server; +} + +#ifdef HAVE_DBUS +static pa_dbus_connection *register_dbus_name(pa_core *c, DBusBusType bus, const char* name) { + DBusError error; + pa_dbus_connection *conn; + + dbus_error_init(&error); + + if (!(conn = pa_dbus_bus_get(c, bus, &error)) || dbus_error_is_set(&error)) { + pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message); + goto fail; + } + + if (dbus_bus_request_name(pa_dbus_connection_get(conn), name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + pa_log_debug("Got %s!", name); + return conn; + } + + if (dbus_error_is_set(&error)) + pa_log_error("Failed to acquire %s: %s: %s", name, error.name, error.message); + else + pa_log_error("D-Bus name %s already taken.", name); + + /* PA cannot be started twice by the same user and hence we can + * ignore mostly the case that a name is already taken. */ + +fail: + if (conn) + pa_dbus_connection_unref(conn); + + dbus_error_free(&error); + return NULL; +} +#endif + +int main(int argc, char *argv[]) { + pa_core *c = NULL; + pa_strbuf *buf = NULL; + pa_daemon_conf *conf = NULL; + pa_mainloop *mainloop = NULL; + char *s; + char *configured_address; + int r = 0, retval = 1, d = 0; + bool valid_pid_file = false; + bool ltdl_init = false; + int n_fds = 0, *passed_fds = NULL; + const char *e; +#ifdef HAVE_FORK + int daemon_pipe[2] = { -1, -1 }; + int daemon_pipe2[2] = { -1, -1 }; +#endif + int autospawn_fd = -1; + bool autospawn_locked = false; +#ifdef HAVE_DBUS + pa_dbusobj_server_lookup *server_lookup = NULL; /* /org/pulseaudio/server_lookup */ + pa_dbus_connection *lookup_service_bus = NULL; /* Always the user bus. */ + pa_dbus_connection *server_bus = NULL; /* The bus where we reserve org.pulseaudio.Server, either the user or the system bus. */ + bool start_server; +#endif + + pa_log_set_ident("pulseaudio"); + pa_log_set_level(PA_LOG_NOTICE); + pa_log_set_flags(PA_LOG_COLORS|PA_LOG_PRINT_FILE|PA_LOG_PRINT_LEVEL, PA_LOG_RESET); + +#if !defined(HAVE_BIND_NOW) && defined(__linux__) && defined(__OPTIMIZE__) + /* + Disable lazy relocations to make usage of external libraries + more deterministic for our RT threads. We abuse __OPTIMIZE__ as + a check whether we are a debug build or not. This all is + admittedly a bit snake-oilish. + */ + + if (!getenv("LD_BIND_NOW")) { + char *rp; + char *canonical_rp; + + /* We have to execute ourselves, because the libc caches the + * value of $LD_BIND_NOW on initialization. */ + + pa_set_env("LD_BIND_NOW", "1"); + + if ((canonical_rp = pa_realpath(PA_BINARY))) { + + if ((rp = pa_readlink("/proc/self/exe"))) { + + if (pa_streq(rp, canonical_rp)) + pa_assert_se(execv(rp, argv) == 0); + else + pa_log_warn("/proc/self/exe does not point to %s, cannot self execute. Are you playing games?", canonical_rp); + + pa_xfree(rp); + + } else + pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?"); + + pa_xfree(canonical_rp); + + } else + pa_log_warn("Couldn't canonicalize binary path, cannot self execute."); + } +#endif + +#ifdef HAVE_SYSTEMD_DAEMON + n_fds = sd_listen_fds(0); + if (n_fds > 0) { + int i = n_fds; + + passed_fds = pa_xnew(int, n_fds+2); + passed_fds[n_fds] = passed_fds[n_fds+1] = -1; + while (i--) + passed_fds[i] = SD_LISTEN_FDS_START + i; + } +#endif + + if (!passed_fds) { + n_fds = 0; + passed_fds = pa_xnew(int, 2); + passed_fds[0] = passed_fds[1] = -1; + } + + if ((e = getenv("PULSE_PASSED_FD"))) { + int passed_fd = atoi(e); + if (passed_fd > 2) + passed_fds[n_fds] = passed_fd; + } + + /* We might be autospawned, in which case have no idea in which + * context we have been started. Let's cleanup our execution + * context as good as possible */ + + pa_reset_personality(); + pa_drop_root(); + pa_close_allv(passed_fds); + pa_xfree(passed_fds); + pa_reset_sigs(-1); + pa_unblock_sigs(-1); + pa_reset_priority(); + + setlocale(LC_ALL, ""); + pa_init_i18n(); + + conf = pa_daemon_conf_new(); + + if (pa_daemon_conf_load(conf, NULL) < 0) + goto finish; + + if (pa_daemon_conf_env(conf) < 0) + goto finish; + + if (pa_cmdline_parse(conf, argc, argv, &d) < 0) { + pa_log(_("Failed to parse command line.")); + goto finish; + } + + if (conf->log_target) + pa_log_set_target(conf->log_target); + else { + pa_log_target target = { .type = PA_LOG_STDERR, .file = NULL }; + pa_log_set_target(&target); + } + + pa_log_set_level(conf->log_level); + if (conf->log_meta) + pa_log_set_flags(PA_LOG_PRINT_META, PA_LOG_SET); + if (conf->log_time) + pa_log_set_flags(PA_LOG_PRINT_TIME, PA_LOG_SET); + pa_log_set_show_backtrace(conf->log_backtrace); + +#ifdef HAVE_DBUS + /* conf->system_instance and conf->local_server_type control almost the + * same thing; make them agree about what is requested. */ + switch (conf->local_server_type) { + case PA_SERVER_TYPE_UNSET: + conf->local_server_type = conf->system_instance ? PA_SERVER_TYPE_SYSTEM : PA_SERVER_TYPE_USER; + break; + case PA_SERVER_TYPE_USER: + case PA_SERVER_TYPE_NONE: + conf->system_instance = false; + break; + case PA_SERVER_TYPE_SYSTEM: + conf->system_instance = true; + break; + default: + pa_assert_not_reached(); + } + + start_server = conf->local_server_type == PA_SERVER_TYPE_USER || (getuid() == 0 && conf->local_server_type == PA_SERVER_TYPE_SYSTEM); + + if (!start_server && conf->local_server_type == PA_SERVER_TYPE_SYSTEM) { + pa_log_notice(_("System mode refused for non-root user. Only starting the D-Bus server lookup service.")); + conf->system_instance = false; + } +#endif + + LTDL_SET_PRELOADED_SYMBOLS(); + pa_ltdl_init(); + ltdl_init = true; + + if (conf->dl_search_path) + lt_dlsetsearchpath(conf->dl_search_path); + +#ifdef OS_IS_WIN32 + { + WSADATA data; + WSAStartup(MAKEWORD(2, 0), &data); + } +#endif + + pa_random_seed(); + + switch (conf->cmd) { + case PA_CMD_DUMP_MODULES: + pa_dump_modules(conf, argc-d, argv+d); + retval = 0; + goto finish; + + case PA_CMD_DUMP_CONF: { + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + s = pa_daemon_conf_dump(conf); + fputs(s, stdout); + pa_xfree(s); + retval = 0; + goto finish; + } + + case PA_CMD_DUMP_RESAMPLE_METHODS: { + int i; + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + for (i = 0; i < PA_RESAMPLER_MAX; i++) + if (pa_resample_method_supported(i)) + printf("%s\n", pa_resample_method_to_string(i)); + + retval = 0; + goto finish; + } + + case PA_CMD_HELP : + pa_cmdline_help(argv[0]); + retval = 0; + goto finish; + + case PA_CMD_VERSION : + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + printf(PACKAGE_NAME" "PACKAGE_VERSION"\n"); + retval = 0; + goto finish; + + case PA_CMD_CHECK: { + pid_t pid; + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) + pa_log_info("Daemon not running"); + else { + pa_log_info("Daemon running as PID %u", pid); + retval = 0; + } + + goto finish; + + } + case PA_CMD_KILL: + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + if (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0) + pa_log(_("Failed to kill daemon: %s"), pa_cstrerror(errno)); + else + retval = 0; + + goto finish; + + case PA_CMD_CLEANUP_SHM: + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + + if (pa_shm_cleanup() >= 0) + retval = 0; + + goto finish; + + default: + pa_assert(conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START); + } + + if (d < argc) { + pa_log("Too many arguments."); + goto finish; + } + +#ifdef HAVE_GETUID + if (getuid() == 0 && !conf->system_instance) + pa_log_warn(_("This program is not intended to be run as root (unless --system is specified).")); +#ifndef HAVE_DBUS /* A similar, only a notice worthy check was done earlier, if D-Bus is enabled. */ + else if (getuid() != 0 && conf->system_instance) { + pa_log(_("Root privileges required.")); + goto finish; + } +#endif +#endif /* HAVE_GETUID */ + + if (conf->cmd == PA_CMD_START && conf->system_instance) { + pa_log(_("--start not supported for system instances.")); + goto finish; + } + + if (conf->cmd == PA_CMD_START && (configured_address = check_configured_address())) { + /* There is an server address in our config, but where did it come from? + * By default a standard X11 login will load module-x11-publish which will + * inject PULSE_SERVER X11 property. If the PA daemon crashes, we will end + * up hitting this code path. So we have to check to see if our configured_address + * is the same as the value that would go into this property so that we can + * recover (i.e. autospawn) from a crash. + */ + char *ufn; + bool start_anyway = false; + + if ((ufn = pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET))) { + char *id; + + if ((id = pa_machine_id())) { + pa_strlist *server_list; + char formatted_ufn[256]; + + pa_snprintf(formatted_ufn, sizeof(formatted_ufn), "{%s}unix:%s", id, ufn); + pa_xfree(id); + + if ((server_list = pa_strlist_parse(configured_address))) { + char *u = NULL; + + /* We only need to check the first server */ + server_list = pa_strlist_pop(server_list, &u); + pa_strlist_free(server_list); + + start_anyway = (u && pa_streq(formatted_ufn, u)); + pa_xfree(u); + } + } + pa_xfree(ufn); + } + + if (!start_anyway) { + pa_log_notice(_("User-configured server at %s, refusing to start/autospawn."), configured_address); + pa_xfree(configured_address); + retval = 0; + goto finish; + } + + pa_log_notice(_("User-configured server at %s, which appears to be local. Probing deeper."), configured_address); + pa_xfree(configured_address); + } + + if (conf->system_instance && !conf->disallow_exit) + pa_log_warn(_("Running in system mode, but --disallow-exit not set.")); + + if (conf->system_instance && !conf->disallow_module_loading) + pa_log_warn(_("Running in system mode, but --disallow-module-loading not set.")); + + if (conf->system_instance && !conf->disable_shm) { + pa_log_notice(_("Running in system mode, forcibly disabling SHM mode.")); + conf->disable_shm = true; + } + + if (conf->system_instance && conf->exit_idle_time >= 0) { + pa_log_notice(_("Running in system mode, forcibly disabling exit idle time.")); + conf->exit_idle_time = -1; + } + + if (conf->cmd == PA_CMD_START) { + /* If we shall start PA only when it is not running yet, we + * first take the autospawn lock to make things + * synchronous. */ + + /* This locking and thread synchronisation code doesn't work reliably + * on kFreeBSD (Debian bug #705435), or in upstream FreeBSD ports + * (bug reference: ports/128947, patched in SVN r231972). */ +#if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) + if ((autospawn_fd = pa_autospawn_lock_init()) < 0) { + pa_log("Failed to initialize autospawn lock"); + goto finish; + } + + if ((pa_autospawn_lock_acquire(true) < 0)) { + pa_log("Failed to acquire autospawn lock"); + goto finish; + } + + autospawn_locked = true; +#endif + } + + if (conf->daemonize) { +#ifdef HAVE_FORK + pid_t child; +#endif + + if (pa_stdio_acquire() < 0) { + pa_log(_("Failed to acquire stdio.")); + goto finish; + } + +#ifdef HAVE_FORK + if (pipe(daemon_pipe) < 0) { + pa_log(_("pipe() failed: %s"), pa_cstrerror(errno)); + goto finish; + } + + if ((child = fork()) < 0) { + pa_log(_("fork() failed: %s"), pa_cstrerror(errno)); + pa_close_pipe(daemon_pipe); + goto finish; + } + + if (child != 0) { + ssize_t n; + /* Father */ + + pa_assert_se(pa_close(daemon_pipe[1]) == 0); + daemon_pipe[1] = -1; + + if ((n = pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL)) != sizeof(retval)) { + + if (n < 0) + pa_log(_("read() failed: %s"), pa_cstrerror(errno)); + + retval = 1; + } + + if (retval) + pa_log(_("Daemon startup failed.")); + else + pa_log_info("Daemon startup successful."); + + goto finish; + } + + if (autospawn_fd >= 0) { + /* The lock file is unlocked from the parent, so we need + * to close it in the child */ + + pa_autospawn_lock_release(); + pa_autospawn_lock_done(true); + + autospawn_locked = false; + autospawn_fd = -1; + } + + pa_assert_se(pa_close(daemon_pipe[0]) == 0); + daemon_pipe[0] = -1; +#endif + + if (!conf->log_target) { +#ifdef HAVE_SYSTEMD_JOURNAL + pa_log_target target = { .type = PA_LOG_JOURNAL, .file = NULL }; +#else + pa_log_target target = { .type = PA_LOG_SYSLOG, .file = NULL }; +#endif + pa_log_set_target(&target); + } + +#ifdef HAVE_SETSID + if (setsid() < 0) { + pa_log(_("setsid() failed: %s"), pa_cstrerror(errno)); + goto finish; + } +#endif + +#ifdef HAVE_FORK + /* We now are a session and process group leader. Let's fork + * again and let the father die, so that we'll become a + * process that can never acquire a TTY again, in a session and + * process group without leader */ + + if (pipe(daemon_pipe2) < 0) { + pa_log(_("pipe() failed: %s"), pa_cstrerror(errno)); + goto finish; + } + + if ((child = fork()) < 0) { + pa_log(_("fork() failed: %s"), pa_cstrerror(errno)); + pa_close_pipe(daemon_pipe2); + goto finish; + } + + if (child != 0) { + ssize_t n; + /* Father */ + + pa_assert_se(pa_close(daemon_pipe2[1]) == 0); + daemon_pipe2[1] = -1; + + if ((n = pa_loop_read(daemon_pipe2[0], &retval, sizeof(retval), NULL)) != sizeof(retval)) { + + if (n < 0) + pa_log(_("read() failed: %s"), pa_cstrerror(errno)); + + retval = 1; + } + + /* We now have to take care of signalling the first fork with + * the return value we've received from this fork... */ + pa_assert(daemon_pipe[1] >= 0); + + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); + pa_close(daemon_pipe[1]); + daemon_pipe[1] = -1; + + goto finish; + } + + pa_assert_se(pa_close(daemon_pipe2[0]) == 0); + daemon_pipe2[0] = -1; + + /* We no longer need the (first) daemon_pipe as it's handled in our child above */ + pa_close_pipe(daemon_pipe); +#endif + +#ifdef SIGTTOU + signal(SIGTTOU, SIG_IGN); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + + pa_nullify_stdfds(); + } + + pa_set_env_and_record("PULSE_INTERNAL", "1"); + pa_assert_se(chdir("/") == 0); + umask(0077); + +#ifdef HAVE_SYS_RESOURCE_H + set_all_rlimits(conf); +#endif + pa_rtclock_hrtimer_enable(); + + if (conf->high_priority) + pa_raise_priority(conf->nice_level); + + if (conf->system_instance) + if (change_user() < 0) + goto finish; + + pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0"); + + pa_log_info("This is PulseAudio %s", PACKAGE_VERSION); + pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS); + +#ifdef HAVE_LIBSAMPLERATE + pa_log_warn("Compiled with DEPRECATED libsamplerate support!"); +#endif + + s = pa_uname_string(); + pa_log_debug("Running on host: %s", s); + pa_xfree(s); + + pa_log_debug("Found %u CPUs.", pa_ncpus()); + + pa_log_info("Page size is %zu bytes", pa_page_size()); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + pa_log_debug("Compiled with Valgrind support: yes"); +#else + pa_log_debug("Compiled with Valgrind support: no"); +#endif + + pa_log_debug("Running in valgrind mode: %s", pa_yes_no(pa_in_valgrind())); + + pa_log_debug("Running in VM: %s", pa_yes_no(pa_running_in_vm())); + +#ifdef HAVE_RUNNING_FROM_BUILD_TREE + pa_log_debug("Running from build tree: %s", pa_yes_no(pa_run_from_build_tree())); +#else + pa_log_debug("Running from build tree: no"); +#endif + +#ifdef __OPTIMIZE__ + pa_log_debug("Optimized build: yes"); +#else + pa_log_debug("Optimized build: no"); +#endif + +#ifdef NDEBUG + pa_log_debug("NDEBUG defined, all asserts disabled."); +#elif defined(FASTPATH) + pa_log_debug("FASTPATH defined, only fast path asserts disabled."); +#else + pa_log_debug("All asserts enabled."); +#endif + + if (!(s = pa_machine_id())) { + pa_log(_("Failed to get machine ID")); + goto finish; + } + pa_log_info("Machine ID is %s.", s); + pa_xfree(s); + + if ((s = pa_session_id())) { + pa_log_info("Session ID is %s.", s); + pa_xfree(s); + } + + if (!(s = pa_get_runtime_dir())) + goto finish; + pa_log_info("Using runtime directory %s.", s); + pa_xfree(s); + + if (!(s = pa_get_state_dir())) + goto finish; + pa_log_info("Using state directory %s.", s); + pa_xfree(s); + + pa_log_info("Using modules directory %s.", conf->dl_search_path); + + pa_log_info("Running in system mode: %s", pa_yes_no(pa_in_system_mode())); + + if (pa_in_system_mode()) + pa_log_warn(_("OK, so you are running PA in system mode. Please make sure that you actually do want to do that.\n" + "Please read http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/WhatIsWrongWithSystemWide/ for an explanation why system mode is usually a bad idea.")); + + if (conf->use_pid_file) { + int z; + + if ((z = pa_pid_file_create("pulseaudio")) != 0) { + + if (conf->cmd == PA_CMD_START && z > 0) { + /* If we are already running and with are run in + * --start mode, then let's return this as success. */ + + retval = 0; + goto finish; + } + + pa_log(_("pa_pid_file_create() failed.")); + goto finish; + } + + valid_pid_file = true; + } + + pa_disable_sigpipe(); + + if (pa_rtclock_hrtimer()) + pa_log_info("System supports high resolution timers"); + else + pa_log_info("System appears to not support high resolution timers"); + + if (conf->lock_memory) { +#if defined(HAVE_SYS_MMAN_H) && !defined(__ANDROID__) + if (mlockall(MCL_FUTURE) < 0) + pa_log_warn("mlockall() failed: %s", pa_cstrerror(errno)); + else + pa_log_info("Successfully locked process into memory."); +#else + pa_log_warn("Memory locking requested but not supported on platform."); +#endif + } + + pa_memtrap_install(); + + pa_assert_se(mainloop = pa_mainloop_new()); + + if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, + !conf->disable_shm && !conf->disable_memfd && pa_memfd_is_locally_supported(), + conf->shm_size))) { + pa_log(_("pa_core_new() failed.")); + goto finish; + } + + c->default_sample_spec = conf->default_sample_spec; + c->alternate_sample_rate = conf->alternate_sample_rate; + c->default_channel_map = conf->default_channel_map; + c->default_n_fragments = conf->default_n_fragments; + c->default_fragment_size_msec = conf->default_fragment_size_msec; + c->deferred_volume_safety_margin_usec = conf->deferred_volume_safety_margin_usec; + c->deferred_volume_extra_delay_usec = conf->deferred_volume_extra_delay_usec; + c->lfe_crossover_freq = conf->lfe_crossover_freq; + c->exit_idle_time = conf->exit_idle_time; + c->scache_idle_time = conf->scache_idle_time; + c->resample_method = conf->resample_method; + c->realtime_priority = conf->realtime_priority; + c->realtime_scheduling = conf->realtime_scheduling; + c->avoid_resampling = conf->avoid_resampling; + c->disable_remixing = conf->disable_remixing; + c->remixing_use_all_sink_channels = conf->remixing_use_all_sink_channels; + c->remixing_produce_lfe = conf->remixing_produce_lfe; + c->remixing_consume_lfe = conf->remixing_consume_lfe; + c->deferred_volume = conf->deferred_volume; + c->running_as_daemon = conf->daemonize; + c->disallow_exit = conf->disallow_exit; + c->flat_volumes = conf->flat_volumes; + c->rescue_streams = conf->rescue_streams; +#ifdef HAVE_DBUS + c->server_type = conf->local_server_type; +#endif + + pa_core_check_idle(c); + + c->state = PA_CORE_RUNNING; + + pa_cpu_init(&c->cpu_info); + + pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0); + pa_signal_new(SIGINT, signal_callback, c); + pa_signal_new(SIGTERM, signal_callback, c); +#ifdef SIGUSR1 + pa_signal_new(SIGUSR1, signal_callback, c); +#endif +#ifdef SIGUSR2 + pa_signal_new(SIGUSR2, signal_callback, c); +#endif +#ifdef SIGHUP + pa_signal_new(SIGHUP, signal_callback, c); +#endif + + if (!conf->no_cpu_limit) + pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0); + + buf = pa_strbuf_new(); + +#ifdef HAVE_DBUS + pa_assert_se(dbus_threads_init_default()); + + if (start_server) +#endif + { + const char *command_source = NULL; + + if (conf->load_default_script_file) { + FILE *f; + + if ((f = pa_daemon_conf_open_default_script_file(conf))) { + r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail); + fclose(f); + command_source = pa_daemon_conf_get_default_script_file(conf); + } + } + + if (r >= 0) { + r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail); + command_source = _("command line arguments"); + } + + pa_log_error("%s", s = pa_strbuf_to_string_free(buf)); + pa_xfree(s); + + if (r < 0 && conf->fail) { + pa_log(_("Failed to initialize daemon due to errors while executing startup commands. Source of commands: %s"), command_source); + goto finish; + } + + if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log(_("Daemon startup without any loaded modules, refusing to work.")); + goto finish; + } +#ifdef HAVE_DBUS + } else { + /* When we just provide the D-Bus server lookup service, we don't want + * any modules to be loaded. We haven't loaded any so far, so one might + * think there's no way to contact the server, but receiving certain + * signals could still cause modules to load. */ + conf->disallow_module_loading = true; +#endif + } + + /* We completed the initial module loading, so let's disable it + * from now on, if requested */ + c->disallow_module_loading = conf->disallow_module_loading; + +#ifdef HAVE_DBUS + if (!conf->system_instance) { + if ((server_lookup = pa_dbusobj_server_lookup_new(c))) { + if (!(lookup_service_bus = register_dbus_name(c, DBUS_BUS_SESSION, "org.PulseAudio1"))) + goto finish; + } + } + + if (start_server) + server_bus = register_dbus_name(c, conf->system_instance ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, "org.pulseaudio.Server"); +#endif + +#ifdef HAVE_FORK + if (daemon_pipe2[1] >= 0) { + int ok = 0; + pa_loop_write(daemon_pipe2[1], &ok, sizeof(ok), NULL); + pa_close(daemon_pipe2[1]); + daemon_pipe2[1] = -1; + } +#endif + + pa_log_info("Daemon startup complete."); + +#ifdef HAVE_SYSTEMD_DAEMON + sd_notify(0, "READY=1"); +#endif + + retval = 0; + if (pa_mainloop_run(mainloop, &retval) < 0) + goto finish; + + pa_log_info("Daemon shutdown initiated."); + +#ifdef HAVE_SYSTEMD_DAEMON + sd_notify(0, "STOPPING=1"); +#endif + +finish: +#ifdef HAVE_DBUS + if (server_bus) + pa_dbus_connection_unref(server_bus); + if (lookup_service_bus) + pa_dbus_connection_unref(lookup_service_bus); + if (server_lookup) + pa_dbusobj_server_lookup_free(server_lookup); +#endif + + if (autospawn_fd >= 0) { + if (autospawn_locked) + pa_autospawn_lock_release(); + + pa_autospawn_lock_done(false); + } + + if (c) { + /* Ensure all the modules/samples are unloaded when the core is still ref'ed, + * as unlink callback hooks in modules may need the core to be ref'ed */ + pa_module_unload_all(c); + pa_scache_free_all(c); + + pa_core_unref(c); + pa_log_info("Daemon terminated."); + } + + if (!conf->no_cpu_limit) + pa_cpu_limit_done(); + + pa_signal_done(); + +#ifdef HAVE_FORK + /* If we have daemon_pipe[1] still open, this means we've failed after + * the first fork, but before the second. Therefore just write to it. */ + if (daemon_pipe[1] >= 0) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); + else if (daemon_pipe2[1] >= 0) + pa_loop_write(daemon_pipe2[1], &retval, sizeof(retval), NULL); + + pa_close_pipe(daemon_pipe2); + pa_close_pipe(daemon_pipe); +#endif + + if (mainloop) + pa_mainloop_free(mainloop); + + if (conf) + pa_daemon_conf_free(conf); + + if (valid_pid_file) + pa_pid_file_remove(); + + /* This has no real purpose except making things valgrind-clean */ + pa_unset_env_recorded(); + +#ifdef OS_IS_WIN32 + WSACleanup(); +#endif + + if (ltdl_init) + pa_ltdl_done(); + +#ifdef HAVE_DBUS + dbus_shutdown(); +#endif + + return retval; +} diff --git a/src/daemon/meson.build b/src/daemon/meson.build new file mode 100644 index 0000000..9c9f807 --- /dev/null +++ b/src/daemon/meson.build @@ -0,0 +1,159 @@ +pulseaudio_sources = [ + 'caps.c', + 'cmdline.c', + 'cpulimit.c', + 'daemon-conf.c', + 'dumpmodules.c', + 'ltdl-bind-now.c', + 'main.c', +] + +pulseaudio_headers = [ + 'caps.h', + 'cmdline.h', + 'cpulimit.h', + 'daemon-conf.h', + 'dumpmodules.h', + 'ltdl-bind-now.h', +] + +if dbus_dep.found() + pulseaudio_sources += 'server-lookup.c' + pulseaudio_headers += 'server-lookup.h' +endif + +# FIXME: dependencies +executable('pulseaudio', + pulseaudio_sources, + pulseaudio_headers, + install: true, + install_rpath : privlibdir, + include_directories : [configinc, topinc], + link_args : ['-ffast-math'], + link_with : [libpulsecore, libpulsecommon, libpulse], + dependencies : [ltdl_dep, cap_dep, dbus_dep, libsystemd_dep, dl_dep, libintl_dep], + c_args : pa_c_args, +) + +if x11_dep.found() + conf = configuration_data() + conf.set('PACTL_BINARY', join_paths(bindir, 'pactl')) + + configure_file( + input : 'start-pulseaudio-x11.in', + output : 'start-pulseaudio-x11', + configuration : conf, + install : true, + install_dir : bindir, + ) + + desktop_file = i18n.merge_file( + input : 'pulseaudio.desktop.in', + output : 'pulseaudio.desktop', + po_dir : po_dir, + type : 'desktop', + install : true, + install_dir : join_paths(sysconfdir, 'xdg', 'autostart'), + ) + + desktop_utils = find_program('desktop-file-validate', required: false) + if desktop_utils.found() + test('Validate desktop file', desktop_utils, + args: [ desktop_file ], + ) + endif +endif + +# Configuration files + +m4 = find_program('m4', required: true) + +daemon_conf = configuration_data() +daemon_conf.merge_from(cdata) +daemon_conf.set('PA_DEFAULT_CONFIG_DIR', cdata.get_unquoted('PA_DEFAULT_CONFIG_DIR')) + +daemon_template_file = configure_file( + input : 'daemon.conf.in', + output : 'daemon.conf.tmp', + configuration : daemon_conf, +) + +custom_target('daemon.conf', + input : daemon_template_file, + output : 'daemon.conf', + capture : true, + command : [m4, '@INPUT@'], + build_by_default : true, + install : true, + install_dir : pulsesysconfdir, +) + +default_conf = configuration_data() +default_conf.merge_from(cdata) +default_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY')) +default_conf.set('PA_SOEXT', cdata.get_unquoted('PA_SOEXT')) +default_conf.set10('HAVE_AF_UNIX', cc.has_header('sys/un.h')) +default_conf.set10('OS_IS_WIN32', host_machine.system() == 'windows') +default_conf.set10('HAVE_MKFIFO', cc.has_function('mkfifo')) +# We don't support the deprecated GConf option in meson +default_conf.set10('HAVE_GCONF', 0) + +default_template_file = configure_file( + input : 'default.pa.in', + output : 'default.pa.tmp', + configuration : default_conf, +) + +custom_target('default.pa', + input : default_template_file, + output : 'default.pa', + capture : true, + command : [m4, '@INPUT@'], + build_by_default : true, + install : true, + install_dir : pulsesysconfdir, +) + +system_conf = configuration_data() +system_conf.merge_from(cdata) +system_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY')) +system_conf.set('PA_SOEXT', cdata.get_unquoted('PA_SOEXT')) + +system_template_file = configure_file( + input : 'system.pa.in', + output : 'system.pa.tmp', + configuration : system_conf, +) + +custom_target('system.pa', + input : system_template_file, + output : 'system.pa', + capture : true, + command : [m4, '@INPUT@'], + build_by_default : true, + install : true, + install_dir : pulsesysconfdir, +) + +if dbus_dep.found() + install_data('pulseaudio-system.conf', + install_dir : join_paths(sysconfdir, 'dbus-1', 'system.d') + ) +endif + +if systemd_dep.found() + sd_user_service_conf = configuration_data() + sd_user_service_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY')) + + sd_user_service_file = configure_file( + input : 'systemd/user/pulseaudio.service.in', + output : 'pulseaudio.service', + configuration : sd_user_service_conf, + install : true, + install_dir : systemduserunitdir, + ) + + install_data('systemd/user/pulseaudio.socket', + install_dir: systemduserunitdir, + ) +endif diff --git a/src/daemon/pulseaudio-system.conf b/src/daemon/pulseaudio-system.conf new file mode 100644 index 0000000..2eb342e --- /dev/null +++ b/src/daemon/pulseaudio-system.conf @@ -0,0 +1,31 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- +This file is part of PulseAudio. + +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/>. +--> + +<busconfig> + + <!-- System-wide PulseAudio runs as 'pulse' user. This fragment is + not necessary for user PulseAudio instances. --> + + <policy user="pulse"> + <allow own="org.pulseaudio.Server"/> + </policy> + +</busconfig> diff --git a/src/daemon/pulseaudio.desktop.in b/src/daemon/pulseaudio.desktop.in new file mode 100644 index 0000000..2e85202 --- /dev/null +++ b/src/daemon/pulseaudio.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=PulseAudio Sound System +Comment=Start the PulseAudio Sound System +Exec=start-pulseaudio-x11 +Terminal=false +Type=Application +X-GNOME-Autostart-Phase=Initialization +X-KDE-autostart-phase=1 diff --git a/src/daemon/server-lookup.c b/src/daemon/server-lookup.c new file mode 100644 index 0000000..ca390c7 --- /dev/null +++ b/src/daemon/server-lookup.c @@ -0,0 +1,492 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/client-conf.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/macro.h> +#include <pulsecore/protocol-dbus.h> + +#include "server-lookup.h" + +#define OBJECT_PATH "/org/pulseaudio/server_lookup1" +#define INTERFACE "org.PulseAudio.ServerLookup1" + +struct pa_dbusobj_server_lookup { + pa_core *core; + pa_dbus_connection *conn; + bool path_registered; +}; + +static const char introspection[] = + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "<node>" + " <!-- If you are looking for documentation make sure to check out\n" + " http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/ -->\n" + " <interface name=\"" INTERFACE "\">\n" + " <property name=\"Address\" type=\"s\" access=\"read\"/>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + "</node>\n"; + +static void unregister_cb(DBusConnection *conn, void *user_data) { + pa_dbusobj_server_lookup *sl = user_data; + + pa_assert(sl); + pa_assert(sl->path_registered); + + sl->path_registered = false; +} + +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char *i = introspection; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +enum get_address_result_t { + SUCCESS, + SERVER_FROM_TYPE_FAILED +}; + +/* Caller frees the returned address. */ +static enum get_address_result_t get_address(pa_server_type_t server_type, char **address) { + enum get_address_result_t r = SUCCESS; + pa_client_conf *conf = pa_client_conf_new(); + + *address = NULL; + + pa_client_conf_load(conf, false, false); + + if (conf->default_dbus_server) + *address = pa_xstrdup(conf->default_dbus_server); + else if (!(*address = pa_get_dbus_address_from_server_type(server_type))) { + r = SERVER_FROM_TYPE_FAILED; + goto finish; + } + +finish: + pa_client_conf_free(conf); + return r; +} + +static DBusHandlerResult handle_get_address(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + DBusMessage *reply = NULL; + char *address = NULL; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + switch (get_address(sl->core->server_type, &address)) { + case SUCCESS: + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + dbus_message_iter_init_append(reply, &msg_iter); + if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&msg_iter, &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case SERVER_FROM_TYPE_FAILED: + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + default: + pa_assert_not_reached(); + } + +finish: + pa_xfree(address); + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_get(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char* interface; + const char* property; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (*interface && !pa_streq(interface, INTERFACE)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + + if (!pa_streq(property, "Address")) { + if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + r = handle_get_address(conn, msg, sl); + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_set(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char* interface; + const char* property; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (*interface && !pa_streq(interface, INTERFACE)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + + if (!pa_streq(property, "Address")) { + if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (!(reply = dbus_message_new_error_printf(msg, DBUS_ERROR_ACCESS_DENIED, "%s: Property not settable", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_get_all(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + DBusMessage *reply = NULL; + const char *property = "Address"; + char *interface = NULL; + char *address = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter variant_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + switch (get_address(sl->core->server_type, &address)) { + case SUCCESS: + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + dbus_message_iter_init_append(reply, &msg_iter); + if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&dict_entry_iter, &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&msg_iter, &dict_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case SERVER_FROM_TYPE_FAILED: + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + default: + pa_assert_not_reached(); + } + +finish: + pa_xfree(address); + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult message_cb(DBusConnection *conn, DBusMessage *msg, void *user_data) { + pa_dbusobj_server_lookup *sl = user_data; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + /* pa_log("Got message! type = %s path = %s iface = %s member = %s dest = %s", dbus_message_type_to_string(dbus_message_get_type(msg)), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), dbus_message_get_destination(msg)); */ + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, "Introspect") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Introspect"))) + return handle_introspect(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Get") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Get"))) + return handle_get(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Set") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Set"))) + return handle_set(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "GetAll") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "GetAll"))) + return handle_get_all(conn, msg, sl); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusObjectPathVTable vtable = { + .unregister_function = unregister_cb, + .message_function = message_cb, + .dbus_internal_pad1 = NULL, + .dbus_internal_pad2 = NULL, + .dbus_internal_pad3 = NULL, + .dbus_internal_pad4 = NULL +}; + +pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c) { + pa_dbusobj_server_lookup *sl; + DBusError error; + + dbus_error_init(&error); + + sl = pa_xnew(pa_dbusobj_server_lookup, 1); + sl->core = c; + sl->path_registered = false; + + if (!(sl->conn = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_register_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH, &vtable, sl)) { + pa_log("dbus_connection_register_object_path() failed for " OBJECT_PATH "."); + goto fail; + } + + sl->path_registered = true; + + return sl; + +fail: + dbus_error_free(&error); + + pa_dbusobj_server_lookup_free(sl); + + return NULL; +} + +void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl) { + pa_assert(sl); + + if (sl->path_registered) { + pa_assert(sl->conn); + if (!dbus_connection_unregister_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH)) + pa_log_debug("dbus_connection_unregister_object_path() failed for " OBJECT_PATH "."); + } + + if (sl->conn) + pa_dbus_connection_unref(sl->conn); + + pa_xfree(sl); +} diff --git a/src/daemon/server-lookup.h b/src/daemon/server-lookup.h new file mode 100644 index 0000000..935baf6 --- /dev/null +++ b/src/daemon/server-lookup.h @@ -0,0 +1,38 @@ +#ifndef fooserverlookuphfoo +#define fooserverlookuphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/* This object implements the D-Bus object at path + * /org/pulseaudio/server_lookup. Implemented interfaces + * are org.pulseaudio.ServerLookup and org.freedesktop.DBus.Introspectable. + * + * See http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/ConnectingToServer/ + * for the ServerLookup interface documentation. + */ + +#include <pulsecore/core.h> + +typedef struct pa_dbusobj_server_lookup pa_dbusobj_server_lookup; + +pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c); +void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl); + +#endif diff --git a/src/daemon/start-pulseaudio-x11.in b/src/daemon/start-pulseaudio-x11.in new file mode 100755 index 0000000..0e84315 --- /dev/null +++ b/src/daemon/start-pulseaudio-x11.in @@ -0,0 +1,37 @@ +#!/bin/sh + +# This file is part of PulseAudio. +# +# 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +set -e + +if [ x"$DISPLAY" != x ] ; then + + @PACTL_BINARY@ load-module module-x11-publish "display=$DISPLAY xauthority=$XAUTHORITY" > /dev/null + @PACTL_BINARY@ load-module module-x11-cork-request "display=$DISPLAY xauthority=$XAUTHORITY" > /dev/null + + # KDE plasma versions older than 5.17.0 use module-device-manager's routing API. + # Check for current plasma version and load module if it's necessary. + if [ x"$KDE_FULL_SESSION" = x"true" ]; then + plasmaversion="$(plasmashell -v 2>/dev/null | sed -n 's/^plasmashell \([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\)/\1*1000000+\2*1000+\3/p' | head -1)" + if [ -n "$plasmaversion" ] && [ "$(($plasmaversion))" -lt "5017000" ]; then + @PACTL_BINARY@ load-module module-device-manager "do_routing=1" > /dev/null + fi + fi + + if [ x"$SESSION_MANAGER" != x ] ; then + @PACTL_BINARY@ load-module module-x11-xsmp "display=$DISPLAY xauthority=$XAUTHORITY session_manager=$SESSION_MANAGER" > /dev/null + fi +fi diff --git a/src/daemon/system.pa.in b/src/daemon/system.pa.in new file mode 100755 index 0000000..73e39ec --- /dev/null +++ b/src/daemon/system.pa.in @@ -0,0 +1,62 @@ +#!@PA_BINARY@ -nF +# +# This file is part of PulseAudio. +# +# 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +# This startup script is used only if PulseAudio is started in system +# mode. +changequote(`[', `]')dnl Set up m4 quoting + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Automatically load driver modules depending on the hardware available +ifelse(@HAVE_UDEV@, 1, [dnl +.ifexists module-udev-detect@PA_SOEXT@ +load-module module-udev-detect +.else +], @HAVE_COREAUDIO@, 1, [dnl +.ifexists module-coreaudio-detect@PA_SOEXT@ +load-module module-coreaudio-detect +.else +], [dnl +.ifexists module-detect@PA_SOEXT@ +])dnl +### Use the static hardware detection module (for systems that lack udev/hal support) +load-module module-detect +.endif + +### Load several protocols +.ifexists module-esound-protocol-unix@PA_SOEXT@ +load-module module-esound-protocol-unix +.endif +load-module module-native-protocol-unix + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### Enable positioned event sounds +load-module module-position-event-sounds diff --git a/src/daemon/systemd/user/pulseaudio.service.in b/src/daemon/systemd/user/pulseaudio.service.in new file mode 100644 index 0000000..ae0caf8 --- /dev/null +++ b/src/daemon/systemd/user/pulseaudio.service.in @@ -0,0 +1,34 @@ +[Unit] +Description=Sound Service + +# We require pulseaudio.socket to be active before starting the daemon, because +# while it is possible to use the service without the socket, it is not clear +# why it would be desirable. +# +# A user installing pulseaudio and doing `systemctl --user start pulseaudio` +# will not get the socket started, which might be confusing and problematic if +# the server is to be restarted later on, as the client autospawn feature +# might kick in. Also, a start of the socket unit will fail, adding to the +# confusion. +# +# After=pulseaudio.socket is not needed, as it is already implicit in the +# socket-service relationship, see systemd.socket(5). +Requires=pulseaudio.socket +ConditionUser=!root + +[Service] +ExecStart=@PA_BINARY@ --daemonize=no --log-target=journal +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +Restart=on-failure +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +# Note that notify will only work if --daemonize=no +Type=notify +UMask=0077 + +[Install] +Also=pulseaudio.socket +WantedBy=default.target diff --git a/src/daemon/systemd/user/pulseaudio.socket b/src/daemon/systemd/user/pulseaudio.socket new file mode 100644 index 0000000..98c1002 --- /dev/null +++ b/src/daemon/systemd/user/pulseaudio.socket @@ -0,0 +1,11 @@ +[Unit] +Description=Sound System +ConditionUser=!root + +[Socket] +Priority=6 +Backlog=5 +ListenStream=%t/pulse/native + +[Install] +WantedBy=sockets.target |