diff options
Diffstat (limited to 'src/daemon')
l--------- | src/daemon/Makefile | 1 | ||||
-rw-r--r-- | src/daemon/PulseAudio.policy | 49 | ||||
-rw-r--r-- | src/daemon/caps.c | 152 | ||||
-rw-r--r-- | src/daemon/caps.h | 31 | ||||
-rw-r--r-- | src/daemon/cmdline.c | 369 | ||||
-rw-r--r-- | src/daemon/cmdline.h | 37 | ||||
-rw-r--r-- | src/daemon/cpulimit.c | 247 | ||||
-rw-r--r-- | src/daemon/cpulimit.h | 36 | ||||
-rw-r--r-- | src/daemon/daemon-conf.c | 597 | ||||
-rw-r--r-- | src/daemon/daemon-conf.h | 124 | ||||
-rw-r--r-- | src/daemon/daemon.conf.in | 69 | ||||
-rwxr-xr-x | src/daemon/default.pa.in | 100 | ||||
-rw-r--r-- | src/daemon/default.pa.win32 | 43 | ||||
-rw-r--r-- | src/daemon/dumpmodules.c | 158 | ||||
-rw-r--r-- | src/daemon/dumpmodules.h | 33 | ||||
-rwxr-xr-x | src/daemon/esdcompat.in | 98 | ||||
-rw-r--r-- | src/daemon/ltdl-bind-now.c | 197 | ||||
-rw-r--r-- | src/daemon/ltdl-bind-now.h | 32 | ||||
-rw-r--r-- | src/daemon/main.c | 850 | ||||
-rw-r--r-- | src/daemon/polkit.c | 223 | ||||
-rw-r--r-- | src/daemon/polkit.h | 29 | ||||
-rw-r--r-- | src/daemon/pulseaudio-module-xsmp.desktop | 10 |
22 files changed, 3485 insertions, 0 deletions
diff --git a/src/daemon/Makefile b/src/daemon/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/daemon/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/daemon/PulseAudio.policy b/src/daemon/PulseAudio.policy new file mode 100644 index 00000000..cf9499ee --- /dev/null +++ b/src/daemon/PulseAudio.policy @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?><!--*-nxml-*--> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> + +<!-- $Id$ --> + +<!-- +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, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA. +--> + +<policyconfig> + + <action id="org.pulseaudio.acquire-real-time"> + <description>Real-time scheduling for the PulseAudio daemon</description> + <message>System policy prevents PulseAudio from acquiring real-time scheduling.</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>no</allow_active> + </defaults> + </action> + + <action id="org.pulseaudio.acquire-high-priority"> + <description>High-priority scheduling (negative Unix nice level) for the PulseAudio daemon</description> + <message>System policy prevents PulseAudio from acquiring high-priority scheduling.</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>no</allow_active> + </defaults> + </action> + +</policyconfig> diff --git a/src/daemon/caps.c b/src/daemon/caps.c new file mode 100644 index 00000000..44ee355e --- /dev/null +++ b/src/daemon/caps.c @@ -0,0 +1,152 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <pulsecore/macro.h> + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <pulsecore/core-error.h> + +#include <pulsecore/log.h> + +#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 + +#ifdef HAVE_GETUID + +/* Drop root rights when called SUID root */ +void pa_drop_root(void) { + uid_t uid = getuid(); + + if (uid == 0 || geteuid() != 0) + return; + + pa_log_info("Dropping root priviliges."); + +#if defined(HAVE_SETRESUID) + pa_assert_se(setresuid(uid, uid, uid) >= 0); +#elif defined(HAVE_SETREUID) + pa_assert_se(setreuid(uid, uid) >= 0); +#else + pa_assert_se(setuid(uid) >= 0); + pa_assert_se(seteuid(uid) >= 0); +#endif + + pa_assert_se(getuid() == uid); + pa_assert_se(geteuid() == uid); +} + +#else + +void pa_drop_root(void) { +} + +#endif + +#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H) + +/* Limit permitted capabilities set to CAPSYS_NICE */ +int pa_limit_caps(void) { + int r = -1; + cap_t caps; + cap_value_t nice_cap = CAP_SYS_NICE; + + caps = cap_init(); + pa_assert(caps); + cap_clear(caps); + cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET); + + if (cap_set_proc(caps) < 0) + goto fail; + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) + goto fail; + + pa_log_info("Dropped capabilities successfully."); + + r = 1; + +fail: + cap_free(caps); + + return r; +} + +/* Drop all capabilities, effectively becoming a normal user */ +int pa_drop_caps(void) { + cap_t caps; + int r = -1; + + caps = cap_init(); + pa_assert(caps); + + cap_clear(caps); + + prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); + + if (cap_set_proc(caps) < 0) { + pa_log("Failed to drop capabilities: %s", pa_cstrerror(errno)); + goto fail; + } + + r = 0; + +fail: + cap_free(caps); + + return r; +} + +#else + +/* NOOPs in case capabilities are not available. */ +int pa_limit_caps(void) { + return 0; +} + +int pa_drop_caps(void) { + pa_drop_root(); + return 0; +} + +#endif diff --git a/src/daemon/caps.h b/src/daemon/caps.h new file mode 100644 index 00000000..4cd09578 --- /dev/null +++ b/src/daemon/caps.h @@ -0,0 +1,31 @@ +#ifndef foocapshfoo +#define foocapshfoo + +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +void pa_drop_root(void); +int pa_limit_caps(void); +int pa_drop_caps(void); + +#endif diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c new file mode 100644 index 00000000..f1e1282c --- /dev/null +++ b/src/daemon/cmdline.c @@ -0,0 +1,369 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <sys/stat.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.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_EXIT_IDLE_TIME, + ARG_MODULE_IDLE_TIME, + ARG_SCACHE_IDLE_TIME, + ARG_LOG_TARGET, + 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_DUMP_RESAMPLE_METHODS, + ARG_SYSTEM, + ARG_CLEANUP_SHM +}; + +/* Tabel 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}, + {"exit-idle-time", 2, 0, ARG_EXIT_IDLE_TIME}, + {"module-idle-time", 2, 0, ARG_MODULE_IDLE_TIME}, + {"scache-idle-time", 2, 0, ARG_SCACHE_IDLE_TIME}, + {"log-target", 1, 0, ARG_LOG_TARGET}, + {"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}, + {"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}, + {"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) { + const char *e; + + pa_assert(argv0); + + if ((e = strrchr(argv0, '/'))) + e++; + else + e = 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" + " -k --kill Kill a running daemon\n" + " --check Check for a running daemon\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 module loading after startup\n" + " --exit-idle-time=SECS Terminate the daemon when idle and this\n" + " time passed\n" + " --module-idle-time=SECS Unload autoloaded modules when idle and\n" + " this 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 Increase the verbosity level\n" + " --log-target={auto,syslog,stderr} Specify the log target\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\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", e); +} + +int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) { + pa_strbuf *buf = NULL; + int c; + + 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_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 ((conf->daemonize = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--daemonize expects boolean argument"); + goto fail; + } + break; + + case ARG_FAIL: + if ((conf->fail = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--fail expects boolean argument"); + goto fail; + } + 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 ((conf->high_priority = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--high-priority expects boolean argument"); + goto fail; + } + break; + + case ARG_REALTIME: + if ((conf->realtime_scheduling = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--realtime expects boolean argument"); + goto fail; + } + break; + + case ARG_DISALLOW_MODULE_LOADING: + if ((conf->disallow_module_loading = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--disallow-module-loading expects boolean argument"); + goto fail; + } + break; + + case ARG_USE_PID_FILE: + if ((conf->use_pid_file = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--use-pid-file expects boolean argument"); + goto fail; + } + break; + + case 'p': + case ARG_DL_SEARCH_PATH: + pa_xfree(conf->dl_search_path); + conf->dl_search_path = *optarg ? pa_xstrdup(optarg) : NULL; + break; + + case 'n': + pa_xfree(conf->default_script_file); + conf->default_script_file = NULL; + break; + + case ARG_LOG_TARGET: + if (pa_daemon_conf_set_log_target(conf, optarg) < 0) { + pa_log("Invalid log target: use either 'syslog', 'stderr' or 'auto'."); + goto fail; + } + break; + + case ARG_EXIT_IDLE_TIME: + conf->exit_idle_time = atoi(optarg); + break; + + case ARG_MODULE_IDLE_TIME: + conf->module_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 ((conf->system_instance = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--system expects boolean argument"); + goto fail; + } + break; + + case ARG_NO_CPU_LIMIT: + if ((conf->no_cpu_limit = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--no-cpu-limit expects boolean argument"); + goto fail; + } + break; + + case ARG_DISABLE_SHM: + if ((conf->disable_shm = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--disable-shm expects boolean argument"); + goto fail; + } + break; + + default: + goto fail; + } + } + + pa_xfree(conf->script_commands); + conf->script_commands = pa_strbuf_tostring_free(buf); + + if (!conf->script_commands) { + pa_xfree(conf->script_commands); + conf->script_commands = NULL; + } + + *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 00000000..18418894 --- /dev/null +++ b/src/daemon/cmdline.h @@ -0,0 +1,37 @@ +#ifndef foocmdlinehfoo +#define foocmdlinehfoo + +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "daemon-conf.h" + +/* Parese 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 00000000..b77dd443 --- /dev/null +++ b/src/daemon/cpulimit.c @@ -0,0 +1,247 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/error.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 <sys/time.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 time_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 int installed = 0; + +/* 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 = 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) { + time_t now; + +#ifdef PRINT_CPU_LOAD + char t[256]; +#endif + + time(&now); + +#ifdef PRINT_CPU_LOAD + pa_snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100); + write_err(t); +#endif + + if (CPUTIME_INTERVAL_SOFT >= ((now-last_time)*(double)CPUTIME_PERCENT/100)) { + static const char c = 'X'; + + write_err("Soft CPU time limit exhausted, terminating.\n"); + + /* Try a soft cleanup */ + write(the_pipe[1], &c, sizeof(c)); + 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"); + _exit(1); /* 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_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); + + time(&last_time); + + /* Prepare the main loop pipe */ + if (pipe(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]); + pa_make_fd_cloexec(the_pipe[0]); + pa_make_fd_cloexec(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 = 1; + + 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 = 0; + } +} + +#else /* HAVE_SIGXCPU */ + +int pa_cpu_limit_init(PA_GCC_UNUSED 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 00000000..271109b4 --- /dev/null +++ b/src/daemon/cpulimit.h @@ -0,0 +1,36 @@ +#ifndef foocpulimithfoo +#define foocpulimithfoo + +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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 00000000..c98c0218 --- /dev/null +++ b/src/daemon/daemon-conf.c @@ -0,0 +1,597 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sched.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.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_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 = FALSE, + .realtime_priority = 5, /* Half of JACK's default rtprio */ + .disallow_module_loading = FALSE, + .exit_idle_time = -1, + .module_idle_time = 20, + .scache_idle_time = 20, + .auto_log_target = 1, + .script_commands = NULL, + .dl_search_path = NULL, + .default_script_file = NULL, + .log_target = PA_LOG_SYSLOG, + .log_level = PA_LOG_NOTICE, + .resample_method = PA_RESAMPLER_AUTO, + .disable_remixing = FALSE, + .config_file = NULL, + .use_pid_file = TRUE, + .system_instance = FALSE, + .no_cpu_limit = FALSE, + .disable_shm = FALSE, + .default_n_fragments = 4, + .default_fragment_size_msec = 25, + .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 } +#ifdef HAVE_SYS_RESOURCE_H + , .rlimit_as = { .value = 0, .is_set = FALSE }, + .rlimit_core = { .value = 0, .is_set = FALSE }, + .rlimit_data = { .value = 0, .is_set = FALSE }, + .rlimit_fsize = { .value = 0, .is_set = FALSE }, + .rlimit_nofile = { .value = 256, .is_set = TRUE }, + .rlimit_stack = { .value = 0, .is_set = FALSE } +#ifdef RLIMIT_NPROC + , .rlimit_nproc = { .value = 0, .is_set = FALSE } +#endif +#ifdef RLIMIT_MEMLOCK + , .rlimit_memlock = { .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 +#endif +}; + +pa_daemon_conf* pa_daemon_conf_new(void) { + FILE *f; + pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); + + if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file, "r"))) + fclose(f); + + c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); + 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); + pa_xfree(c->config_file); + pa_xfree(c); +} + +int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) { + pa_assert(c); + pa_assert(string); + + if (!strcmp(string, "auto")) + c->auto_log_target = 1; + else if (!strcmp(string, "syslog")) { + c->auto_log_target = 0; + c->log_target = PA_LOG_SYSLOG; + } else if (!strcmp(string, "stderr")) { + c->auto_log_target = 0; + c->log_target = PA_LOG_STDERR; + } else + return -1; + + 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; +} + +static int parse_log_target(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_log_target(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid log target '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_log_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_log_level(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid log level '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_resample_method(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_resample_method(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid resample method '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_rlimit(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { +#ifdef HAVE_SYS_RESOURCE_H + struct pa_rlimit *r = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(r); + + if (rvalue[strspn(rvalue, "\t ")] == 0) { + /* Empty string */ + r->is_set = 0; + r->value = 0; + } else { + int32_t k; + if (pa_atoi(rvalue, &k) < 0) { + pa_log("[%s:%u] Invalid rlimit '%s'.", filename, line, rvalue); + return -1; + } + r->is_set = k >= 0; + r->value = k >= 0 ? (rlim_t) k : 0; + } +#else + pa_log_warn("[%s:%u] rlimit not supported on this platform.", filename, line); +#endif + + return 0; +} + +static int parse_sample_format(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + pa_sample_format_t f; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if ((f = pa_parse_sample_format(rvalue)) < 0) { + pa_log("[%s:%u] Invalid sample format '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.format = f; + return 0; +} + +static int parse_sample_rate(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t r; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &r) < 0 || r > PA_RATE_MAX || r <= 0) { + pa_log("[%s:%u] Invalid sample rate '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.rate = r; + return 0; +} + +static int parse_sample_channels(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n > PA_CHANNELS_MAX || n <= 0) { + pa_log("[%s:%u] Invalid sample channels '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.channels = (uint8_t) n; + return 0; +} + +static int parse_fragments(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n < 2) { + pa_log("[%s:%u] Invalid number of fragments '%s'.", filename, line, rvalue); + return -1; + } + + c->default_n_fragments = (unsigned) n; + return 0; +} + +static int parse_fragment_size_msec(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n < 1) { + pa_log("[%s:%u] Invalid fragment size '%s'.", filename, line, rvalue); + return -1; + } + + c->default_fragment_size_msec = (unsigned) n; + return 0; +} + +static int parse_nice_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t level; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &level) < 0 || level < -20 || level > 19) { + pa_log("[%s:%u] Invalid nice level '%s'.", filename, line, rvalue); + return -1; + } + + c->nice_level = (int) level; + return 0; +} + +static int parse_rtprio(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t rtprio; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(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'.", filename, line, rvalue); + return -1; + } + + c->realtime_priority = (int) rtprio; + return 0; +} + +int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { + int r = -1; + FILE *f = NULL; + + pa_config_item table[] = { + { "daemonize", pa_config_parse_bool, NULL }, + { "fail", pa_config_parse_bool, NULL }, + { "high-priority", pa_config_parse_bool, NULL }, + { "realtime-scheduling", pa_config_parse_bool, NULL }, + { "disallow-module-loading", pa_config_parse_bool, NULL }, + { "use-pid-file", pa_config_parse_bool, NULL }, + { "system-instance", pa_config_parse_bool, NULL }, + { "no-cpu-limit", pa_config_parse_bool, NULL }, + { "disable-shm", pa_config_parse_bool, NULL }, + { "exit-idle-time", pa_config_parse_int, NULL }, + { "module-idle-time", pa_config_parse_int, NULL }, + { "scache-idle-time", pa_config_parse_int, NULL }, + { "realtime-priority", parse_rtprio, NULL }, + { "dl-search-path", pa_config_parse_string, NULL }, + { "default-script-file", pa_config_parse_string, NULL }, + { "log-target", parse_log_target, NULL }, + { "log-level", parse_log_level, NULL }, + { "verbose", parse_log_level, NULL }, + { "resample-method", parse_resample_method, NULL }, + { "default-sample-format", parse_sample_format, NULL }, + { "default-sample-rate", parse_sample_rate, NULL }, + { "default-sample-channels", parse_sample_channels, NULL }, + { "default-fragments", parse_fragments, NULL }, + { "default-fragment-size-msec", parse_fragment_size_msec, NULL }, + { "nice-level", parse_nice_level, NULL }, + { "disable-remixing", pa_config_parse_bool, NULL }, +#ifdef HAVE_SYS_RESOURCE_H + { "rlimit-as", parse_rlimit, NULL }, + { "rlimit-core", parse_rlimit, NULL }, + { "rlimit-data", parse_rlimit, NULL }, + { "rlimit-fsize", parse_rlimit, NULL }, + { "rlimit-nofile", parse_rlimit, NULL }, + { "rlimit-stack", parse_rlimit, NULL }, +#ifdef RLIMIT_NPROC + { "rlimit-nproc", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_MEMLOCK + { "rlimit-memlock", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_NICE + { "rlimit-nice", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_RTPRIO + { "rlimit-rtprio", parse_rlimit, NULL }, +#endif +#endif + { NULL, NULL, NULL }, + }; + + table[0].data = &c->daemonize; + table[1].data = &c->fail; + table[2].data = &c->high_priority; + table[3].data = &c->realtime_scheduling; + table[4].data = &c->disallow_module_loading; + table[5].data = &c->use_pid_file; + table[6].data = &c->system_instance; + table[7].data = &c->no_cpu_limit; + table[8].data = &c->disable_shm; + table[9].data = &c->exit_idle_time; + table[10].data = &c->module_idle_time; + table[11].data = &c->scache_idle_time; + table[12].data = c; + table[13].data = &c->dl_search_path; + table[14].data = &c->default_script_file; + table[15].data = c; + table[16].data = c; + table[17].data = c; + table[18].data = c; + table[19].data = c; + table[20].data = c; + table[21].data = c; + table[22].data = c; + table[23].data = c; + table[24].data = c; + table[25].data = &c->disable_remixing; +#ifdef HAVE_SYS_RESOURCE_H + table[26].data = &c->rlimit_as; + table[27].data = &c->rlimit_core; + table[28].data = &c->rlimit_data; + table[29].data = &c->rlimit_fsize; + table[30].data = &c->rlimit_nofile; + table[31].data = &c->rlimit_stack; +#ifdef RLIMIT_NPROC + table[32].data = &c->rlimit_nproc; +#endif +#ifdef RLIMIT_MEMLOCK +#ifndef RLIMIT_NPROC +#error "Houston, we have a numbering problem!" +#endif + table[33].data = &c->rlimit_memlock; +#endif +#ifdef RLIMIT_NICE +#ifndef RLIMIT_MEMLOCK +#error "Houston, we have a numbering problem!" +#endif + table[34].data = &c->rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO +#ifndef RLIMIT_NICE +#error "Houston, we have a numbering problem!" +#endif + table[35].data = &c->rlimit_rtprio; +#endif +#endif + + pa_xfree(c->config_file); + c->config_file = NULL; + + f = filename ? + fopen(c->config_file = pa_xstrdup(filename), "r") : + pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file, "r"); + + if (!f && errno != ENOENT) { + pa_log_warn("Failed to open configuration file '%s': %s", c->config_file, pa_cstrerror(errno)); + goto finish; + } + + r = f ? pa_config_parse(c->config_file, f, table, NULL) : 0; + +finish: + if (f) + fclose(f); + + return r; +} + +int pa_daemon_conf_env(pa_daemon_conf *c) { + char *e; + + 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; +} + +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" +}; + +char *pa_daemon_conf_dump(pa_daemon_conf *c) { + pa_strbuf *s; + + 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); + + 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, "disallow-module-loading = %s\n", pa_yes_no(c->disallow_module_loading)); + 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)); + pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit)); + pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm)); + pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time); + pa_strbuf_printf(s, "module-idle-time = %i\n", c->module_idle_time); + pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time); + pa_strbuf_printf(s, "dl-search-path = %s\n", c->dl_search_path ? c->dl_search_path : ""); + pa_strbuf_printf(s, "default-script-file = %s\n", c->default_script_file); + pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr")); + 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, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing)); + 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, "default-sample-channels = %u\n", c->default_sample_spec.channels); + 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); +#ifdef HAVE_SYS_RESOURCE_H + pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1); + pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.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-fsize = %li\n", c->rlimit_fsize.is_set ? (long int) c->rlimit_fsize.value : -1); + pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1); + pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1); +#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_MEMLOCK + pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1); +#endif +#ifdef RLIMIT_NICE + pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_nice.value : -1); +#endif +#ifdef RLIMIT_RTPRIO + pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_rtprio.value : -1); +#endif +#endif + + return pa_strbuf_tostring_free(s); +} diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h new file mode 100644 index 00000000..3dcafbfe --- /dev/null +++ b/src/daemon/daemon-conf.h @@ -0,0 +1,124 @@ +#ifndef foodaemonconfhfoo +#define foodaemonconfhfoo + +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulse/sample.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_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; + pa_bool_t 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; + pa_bool_t daemonize, + fail, + high_priority, + realtime_scheduling, + disallow_module_loading, + use_pid_file, + system_instance, + no_cpu_limit, + disable_shm, + disable_remixing; + int exit_idle_time, + module_idle_time, + scache_idle_time, + auto_log_target, + realtime_priority, + nice_level, + resample_method; + char *script_commands, *dl_search_path, *default_script_file; + pa_log_target_t log_target; + pa_log_level_t log_level; + char *config_file; + +#ifdef HAVE_SYS_RESOURCE_H + pa_rlimit rlimit_as, rlimit_core, rlimit_data, rlimit_fsize, rlimit_nofile, rlimit_stack; +#ifdef RLIMIT_NPROC + pa_rlimit rlimit_nproc; +#endif +#ifdef RLIMIT_MEMLOCK + pa_rlimit rlimit_memlock; +#endif +#ifdef RLIMIT_NICE + pa_rlimit rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO + pa_rlimit rlimit_rtprio; +#endif +#endif + + unsigned default_n_fragments, default_fragment_size_msec; + pa_sample_spec default_sample_spec; +} 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); + +#endif diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in new file mode 100644 index 00000000..d664962e --- /dev/null +++ b/src/daemon/daemon.conf.in @@ -0,0 +1,69 @@ +# $Id$ +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for +## more information. Default values a commented out. Use either ; or # for +## commenting. + +; daemonize = no +; fail = yes +; disallow-module-loading = no +; use-pid-file = yes +; system-instance = no +; disable-shm = no + +; high-priority = yes +; nice-level = -11 + +; realtime-scheduling = no +; realtime-priority = 5 + +; exit-idle-time = -1 +; module-idle-time = 20 +; scache-idle-time = 20 + +; dl-search-path = @PA_DLSEARCHPATH@ + +; default-script-file = @PA_DEFAULT_CONFIG_FILE@ + +; log-target = auto +; log-level = notice + +; resample-method = speex-float-3 +; disable-remixing = no + +; no-cpu-limit = no + +; rlimit-as = -1 +; rlimit-core = -1 +; rlimit-data = -1 +; rlimit-fsize = -1 +; rlimit-nofile = 256 +; rlimit-stack = -1 +; rlimit-nproc = -1 +; rlimit-memlock = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 + +; default-sample-format = s16le +; default-sample-rate = 44100 +; default-sample-channels = 2 + +; default-fragments = 4 +; default-fragment-size-msec = 25 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in new file mode 100755 index 00000000..597993c4 --- /dev/null +++ b/src/daemon/default.pa.in @@ -0,0 +1,100 @@ +#!@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, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +.nofail + +### Load something into the sample cache +#load-sample-lazy x11-bell /usr/share/sounds/gtk-events/activate.wav +load-sample-lazy pulse-hotplug /usr/share/sounds/startup3.wav +#load-sample-lazy pulse-coldplug /usr/share/sounds/startup3.wav +#load-sample-lazy pulse-access /usr/share/sounds/generic.wav + +.fail + +### Load audio drivers statically (it's probably better to not load +### these drivers manually, but instead use module-hal-detect -- +### see below -- for doing this automatically) +#load-module module-alsa-sink +#load-module module-alsa-source device=hw:1,0 +#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 +#load-module module-null-sink +#load-module module-pipe-sink + +### Automatically load driver modules depending on the hardware available +.ifexists @PA_DLSEARCHPATH@/module-hal-detect@PA_SOEXT@ +load-module module-hal-detect +.else +### Alternatively use the static hardware detection module (for systems that +### lack HAL support) +load-module module-detect +.endif + +### Load several protocols +load-module module-esound-protocol-unix +load-module module-native-protocol-unix + +### 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 +#load-module module-zeroconf-publish + +### Load the RTP reciever 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 description="RTP Multicast Sink" +#load-module module-rtp-send source=rtp.monitor + +### Automatically restore the volume of playback streams +load-module module-volume-restore + +### Automatically restore the default sink/source when changed by the user during runtime +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### Load X11 bell module +#load-module module-x11-bell sample=x11-bell + +### Publish connection data in the X11 root window +.ifexists @PA_DLSEARCHPATH@/module-x11-publish@PA_SOEXT@ +load-module module-x11-publish +.endif + +### Register ourselves in the X11 session manager +# Deactivated by default, to avoid deadlock when PA is started as esd from gnome-session +# Instead we load this via /etc/xdg/autostart/ and "pactl load-module" now +# load-module module-x11-xsmp + +### 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 @PA_DLSEARCHPATH@/module-gconf@PA_SOEXT@ +load-module module-gconf +.endif + +### Make some devices default +#set-default-sink output +#set-default-source input diff --git a/src/daemon/default.pa.win32 b/src/daemon/default.pa.win32 new file mode 100644 index 00000000..d5a1e183 --- /dev/null +++ b/src/daemon/default.pa.win32 @@ -0,0 +1,43 @@ +#
+# 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, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+# Load audio drivers statically
+
+load-module module-waveout sink_name=output source_name=input
+load-module module-null-sink
+
+# Load audio drivers automatically on access
+
+#add-autoload-sink output module-waveout sink_name=output source_name=input
+#add-autoload-source input module-waveout sink_name=output source_name=input
+
+# Load several protocols
+#load-module module-esound-protocol-tcp
+#load-module module-native-protocol-tcp
+#load-module module-simple-protocol-tcp
+#load-module module-cli-protocol-tcp
+
+# Make some devices default
+set-default-sink output
+set-default-source input
+
+.nofail
+
+# Load something to the sample cache
+load-sample x11-bell %WINDIR%\Media\ding.wav
+load-sample-dir-lazy %WINDIR%\Media\*.wav
diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c new file mode 100644 index 00000000..68236c70 --- /dev/null +++ b/src/daemon/dumpmodules.c @@ -0,0 +1,158 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <getopt.h> +#include <stdio.h> +#include <ltdl.h> + +#include <pulse/util.h> + +#include <pulsecore/modinfo.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "dumpmodules.h" + +#define PREFIX "module-" + +static void short_info(const char *name, PA_GCC_UNUSED 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 (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); + } +} + +extern const lt_dlsymlist lt_preloaded_symbols[]; + +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 (!strcmp(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 00000000..ab2ddb64 --- /dev/null +++ b/src/daemon/dumpmodules.h @@ -0,0 +1,33 @@ +#ifndef foodumpmoduleshfoo +#define foodumpmoduleshfoo + +/* $Id*/ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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 00000000..942389d2 --- /dev/null +++ b/src/daemon/esdcompat.in @@ -0,0 +1,98 @@ +#!/bin/sh + +# $Id$ +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +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 00000000..6915fe0c --- /dev/null +++ b/src/daemon/ltdl-bind-now.c @@ -0,0 +1,197 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#if HAVE_SYS_DL_H +#include <sys/dl.h> +#endif + +#ifndef HAVE_STRUCT_LT_USER_DLLOADER +/* Only used with ltdl 2.2 */ +#include <string.h> +#endif + +#include <ltdl.h> + +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.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 + +static pa_mutex *libtool_mutex = NULL; + +PA_STATIC_TLS_DECLARE_NO_FREE(libtool_tls); + +static void libtool_lock(void) { + pa_mutex_lock(libtool_mutex); +} + +static void libtool_unlock(void) { + pa_mutex_unlock(libtool_mutex); +} + +static void libtool_set_error(const char *error) { + PA_STATIC_TLS_SET(libtool_tls, (char*) error); +} + +static const char *libtool_get_error(void) { + return PA_STATIC_TLS_GET(libtool_tls); +} + +#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 pulsaudio binary. +*/ + +#ifndef HAVE_LT_DLADVISE +static lt_module bind_now_open(lt_user_data d, const char *fname) { +#else + static lt_module bind_now_open(lt_user_data d, const char *fname, lt_dladvise advise) { +#endif + lt_module m; + + pa_assert(fname); + + if (!(m = dlopen(fname, PA_BIND_NOW))) { + libtool_set_error(dlerror()); + return NULL; + } + + return m; +} + +static int bind_now_close(lt_user_data d, lt_module m) { + + pa_assert(m); + + if (dlclose(m) != 0){ + libtool_set_error(dlerror()); + 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))) { + libtool_set_error(dlerror()); + return NULL; + } + + return ptr; +} + +#endif + +void pa_ltdl_init(void) { + +#ifdef PA_BIND_NOW +# ifdef HAVE_STRUCT_LT_USER_DLLOADER + lt_dlloader *place; + static const struct lt_user_dlloader loader = { + .module_open = bind_now_open, + .module_close = bind_now_close, + .find_sym = bind_now_find_sym + }; +# else + static const lt_dlvtable *dlopen_loader; + static lt_dlvtable bindnow_loader; +# endif +#endif + + pa_assert_se(lt_dlinit() == 0); + pa_assert_se(libtool_mutex = pa_mutex_new(TRUE, FALSE)); +#ifdef HAVE_LT_DLMUTEX_REGISTER + pa_assert_se(lt_dlmutex_register(libtool_lock, libtool_unlock, libtool_set_error, libtool_get_error) == 0); +#endif + +#ifdef PA_BIND_NOW +# ifdef HAVE_STRUCT_LT_USER_DLLOADER + + if (!(place = lt_dlloader_find("dlopen"))) + place = lt_dlloader_next(NULL); + + /* Add our BIND_NOW loader as the default module loader. */ + if (lt_dlloader_add(place, &loader, "bind-now-loader") != 0) + pa_log_warn("Failed to add bind-now-loader."); +# else + /* Already initialised */ + if ( dlopen_loader != NULL ) return; + + if (!(dlopen_loader = lt_dlloader_find("dlopen"))) { + pa_log_warn("Failed to find original dlopen 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."); +# endif +#endif +} + +void pa_ltdl_done(void) { + pa_assert_se(lt_dlexit() == 0); + pa_mutex_free(libtool_mutex); + libtool_mutex = NULL; +} + diff --git a/src/daemon/ltdl-bind-now.h b/src/daemon/ltdl-bind-now.h new file mode 100644 index 00000000..e19c7bc1 --- /dev/null +++ b/src/daemon/ltdl-bind-now.h @@ -0,0 +1,32 @@ +#ifndef foopulsecoreltdlbindnowhfoo +#define foopulsecoreltdlbindnowhfoo + +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + + +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 00000000..7823180a --- /dev/null +++ b/src/daemon/main.c @@ -0,0 +1,850 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <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 <fcntl.h> +#include <unistd.h> +#include <locale.h> +#include <sys/types.h> + +#include <liboil/liboil.h> + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.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 + +#include <pulse/mainloop.h> +#include <pulse/mainloop-signal.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core.h> +#include <pulsecore/memblock.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/namereg.h> +#include <pulsecore/random.h> +#include <pulsecore/rtsig.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/once.h> +#include <pulsecore/shm.h> + +#include "cmdline.h" +#include "cpulimit.h" +#include "daemon-conf.h" +#include "dumpmodules.h" +#include "caps.h" +#include "ltdl-bind-now.h" +#include "polkit.h" + +#ifdef HAVE_LIBWRAP +/* Only one instance of these variables */ +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#ifdef HAVE_OSS +/* 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 + +#ifdef OS_IS_WIN32 + +static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + MSG msg; + struct timeval tvnext; + + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + raise(SIGTERM); + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + pa_timeval_add(pa_gettimeofday(&tvnext), 100000); + a->time_restart(e, &tvnext); +} + +#endif + +static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, int sig, void *userdata) { + pa_log_info("Got signal %s.", pa_sig2str(sig)); + + switch (sig) { +#ifdef SIGUSR1 + case SIGUSR1: + pa_module_load(userdata, "module-cli", NULL); + break; +#endif + +#ifdef SIGUSR2 + case SIGUSR2: + pa_module_load(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, 1); + break; + } +} + +#define set_env(key, value) putenv(pa_sprintf_malloc("%s=%s", (key), (value))) + +#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 (strcmp(pw->pw_dir, PA_SYSTEM_RUNTIME_PATH) != 0) + pa_log_warn("Warning: 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) < 0) { + pa_log("Failed to create '%s': %s", PA_SYSTEM_RUNTIME_PATH, pa_cstrerror(errno)); + return -1; + } + + 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 priviliges" +#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 priviliges" +#endif + + if (r < 0) { + pa_log("Failed to change UID: %s", pa_cstrerror(errno)); + return -1; + } + + set_env("USER", PA_SYSTEM_USER); + set_env("LOGNAME", PA_SYSTEM_GROUP); + set_env("HOME", PA_SYSTEM_RUNTIME_PATH); + + /* Relevant for pa_runtime_path() */ + set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); + set_env("PULSE_CONFIG_PATH", PA_SYSTEM_RUNTIME_PATH); + + pa_log_info("Successfully dropped root privileges."); + + 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 */ + +static int create_runtime_dir(void) { + char fn[PATH_MAX]; + + pa_runtime_path(NULL, fn, sizeof(fn)); + + /* This function is called only when the daemon is started in + * per-user mode. We create the runtime directory somewhere in + * /tmp/ with the current UID/GID */ + + if (pa_make_secure_dir(fn, 0700, (uid_t)-1, (gid_t)-1) < 0) { + pa_log("Failed to create '%s': %s", fn, pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#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_warn("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_as, RLIMIT_AS, "RLIMIT_AS"); + set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE"); + set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA"); + set_one_rlimit(&conf->rlimit_fsize, RLIMIT_FSIZE, "RLIMIT_FSIZE"); + set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE"); + set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK"); +#ifdef RLIMIT_NPROC + set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC"); +#endif +#ifdef RLIMIT_MEMLOCK + set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK"); +#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 +} +#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; + int r = 0, retval = 1, d = 0; + int daemon_pipe[2] = { -1, -1 }; + pa_bool_t suid_root, real_root; + int valid_pid_file = 0; + gid_t gid = (gid_t) -1; + pa_bool_t allow_realtime, allow_high_priority; + pa_bool_t ltdl_init = FALSE; + +#ifdef OS_IS_WIN32 + pa_time_event *timer; + struct timeval tv; +#endif + + +#if 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. + */ + + if (!getenv("LD_BIND_NOW")) { + char *rp; + + /* We have to execute ourselves, because the libc caches the + * value of $LD_BIND_NOW on initialization. */ + + putenv(pa_xstrdup("LD_BIND_NOW=1")); + pa_assert_se(rp = pa_readlink("/proc/self/exe")); + pa_assert_se(execv(rp, argv) == 0); + } +#endif + +#ifdef HAVE_GETUID + real_root = getuid() == 0; + suid_root = !real_root && geteuid() == 0; +#else + real_root = FALSE; + suid_root = FALSE; +#endif + + if (suid_root) { + /* Drop all capabilities except CAP_SYS_NICE */ + pa_limit_caps(); + + /* Drop priviliges, but keep CAP_SYS_NICE */ + pa_drop_root(); + + /* After dropping root, the effective set is reset, hence, + * let's raise it again */ + pa_limit_caps(); + + /* When capabilities are not supported we will not be able to + * aquire RT sched anymore. But yes, that's the way it is. It + * is just too risky tun let PA run as root all the time. */ + } + + /* At this point, we are a normal user, possibly with CAP_NICE if + * we were started SUID. If we are started as normal root, than we + * still are normal root. */ + + setlocale(LC_ALL, ""); + pa_log_set_maximal_level(PA_LOG_INFO); + pa_log_set_ident("pulseaudio"); + + 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; + } + + pa_log_set_maximal_level(conf->log_level); + pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL); + + if (suid_root) { + /* Ok, we're suid root, so let's better not enable high prio + * or RT by default */ + + allow_high_priority = allow_realtime = FALSE; + +#ifdef HAVE_POLKIT + if (conf->high_priority) { + if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) { + pa_log_info("PolicyKit grants us acquire-high-priority privilige."); + allow_high_priority = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-high-priority privilige."); + } + + if (conf->realtime_scheduling) { + if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) { + pa_log_info("PolicyKit grants us acquire-real-time privilige."); + allow_realtime = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-real-time privilige."); + } +#endif + + if ((conf->high_priority || conf->realtime_scheduling) && pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { + pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time and high-priority scheduling."); + allow_realtime = conf->realtime_scheduling; + allow_high_priority = conf->high_priority; + } + + if (!allow_high_priority && !allow_realtime) { + + /* OK, there's no further need to keep CAP_NICE. Hence + * let's give it up early */ + + pa_drop_caps(); + pa_drop_root(); + suid_root = real_root = FALSE; + + if (conf->high_priority || conf->realtime_scheduling) + pa_log_notice("Called SUID root and real-time/high-priority scheduling was requested in the configuration. However, we lack the necessary priviliges:\n" + "We are not in group '"PA_REALTIME_GROUP"' and PolicyKit refuse to grant us priviliges. Dropping SUID again.\n" + "For enabling real-time scheduling please acquire the appropriate PolicyKit priviliges, or become a member of '"PA_REALTIME_GROUP"', or increase the RLIMIT_NICE/RLIMIT_RTPRIO resource limits for this user."); + } + + } else { + + /* OK, we're a normal user, so let's allow the user evrything + * he asks for, it's now the kernel's job to enforce limits, + * not ours anymore */ + allow_high_priority = allow_realtime = TRUE; + } + + if (conf->high_priority && !allow_high_priority) { + pa_log_info("High-priority scheduling enabled in configuration but now allowed by policy. Disabling forcibly."); + conf->high_priority = FALSE; + } + + if (conf->realtime_scheduling && !allow_realtime) { + pa_log_info("Real-time scheduling enabled in configuration but now allowed by policy. Disabling forcibly."); + conf->realtime_scheduling = FALSE; + } + + if (conf->high_priority && conf->cmd == PA_CMD_DAEMON) + pa_raise_priority(conf->nice_level); + + if (suid_root) { + pa_bool_t drop; + + drop = conf->cmd != PA_CMD_DAEMON || !conf->realtime_scheduling; + +#ifdef RLIMIT_RTPRIO + if (!drop) { + + /* At this point we still have CAP_NICE if we were loaded + * SUID root. If possible let's acquire RLIMIT_RTPRIO + * instead and give CAP_NICE up. */ + + const pa_rlimit rl = { 9, TRUE }; + + if (set_one_rlimit(&rl, RLIMIT_RTPRIO, "RLIMIT_RTPRIO") >= 0) { + pa_log_info("Successfully increased RLIMIT_RTPRIO, giving up CAP_NICE."); + drop = TRUE; + } else + pa_log_warn("RLIMIT_RTPRIO failed: %s", pa_cstrerror(errno)); + } +#endif + + if (drop) { + pa_drop_caps(); + pa_drop_root(); + suid_root = real_root = FALSE; + } + } + + 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: { + s = pa_daemon_conf_dump(conf); + fputs(s, stdout); + pa_xfree(s); + retval = 0; + goto finish; + } + + case PA_CMD_DUMP_RESAMPLE_METHODS: { + int i; + + for (i = 0; i < PA_RESAMPLER_MAX; i++) + if (pa_resample_method_supported(i)) + printf("%s\n", pa_resample_method_to_string(i)); + + goto finish; + } + + case PA_CMD_HELP : + pa_cmdline_help(argv[0]); + retval = 0; + goto finish; + + case PA_CMD_VERSION : + printf(PACKAGE_NAME" "PACKAGE_VERSION"\n"); + retval = 0; + goto finish; + + case PA_CMD_CHECK: { + pid_t pid; + + 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 (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0) + pa_log("Failed to kill daemon."); + else + retval = 0; + + goto finish; + + case PA_CMD_CLEANUP_SHM: + + if (pa_shm_cleanup() >= 0) + retval = 0; + + goto finish; + + default: + pa_assert(conf->cmd == PA_CMD_DAEMON); + } + + if (real_root && !conf->system_instance) + pa_log_warn("This program is not intended to be run as root (unless --system is specified)."); + else if (!real_root && conf->system_instance) { + pa_log("Root priviliges required."); + goto finish; + } + + if (conf->daemonize) { + pid_t child; + int tty_fd; + + if (pa_stdio_acquire() < 0) { + pa_log("Failed to acquire stdio."); + goto finish; + } + +#ifdef HAVE_FORK + if (pipe(daemon_pipe) < 0) { + pa_log("Failed to create pipe."); + goto finish; + } + + if ((child = fork()) < 0) { + pa_log("fork() failed: %s", pa_cstrerror(errno)); + goto finish; + } + + if (child != 0) { + /* Father */ + + pa_assert_se(pa_close(daemon_pipe[1]) == 0); + daemon_pipe[1] = -1; + + if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL) != sizeof(retval)) { + 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; + } + + pa_assert_se(pa_close(daemon_pipe[0]) == 0); + daemon_pipe[0] = -1; +#endif + + if (conf->auto_log_target) + pa_log_set_target(PA_LOG_SYSLOG, NULL); + +#ifdef HAVE_SETSID + setsid(); +#endif +#ifdef HAVE_SETPGID + setpgid(0,0); +#endif + +#ifndef OS_IS_WIN32 + pa_close(0); + pa_close(1); + pa_close(2); + + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); +#else + FreeConsole(); +#endif + +#ifdef SIGTTOU + signal(SIGTTOU, SIG_IGN); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + +#ifdef TIOCNOTTY + if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) { + ioctl(tty_fd, TIOCNOTTY, (char*) 0); + pa_assert_se(pa_close(tty_fd) == 0); + } +#endif + } + + pa_assert_se(chdir("/") == 0); + umask(0022); + + if (conf->system_instance) { + if (change_user() < 0) + goto finish; + } else if (create_runtime_dir() < 0) + goto finish; + + if (conf->use_pid_file) { + if (pa_pid_file_create() < 0) { + pa_log("pa_pid_file_create() failed."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + goto finish; + } + + valid_pid_file = 1; + } + +#ifdef HAVE_SYS_RESOURCE_H + set_all_rlimits(conf); +#endif + +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + pa_log_info("This is PulseAudio " PACKAGE_VERSION); + pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE); + + if (pa_rtclock_hrtimer()) + pa_log_info("Fresh high-resolution timers available! Bon appetit!"); + else + pa_log_info("Dude, your kernel stinks! The chef's recommendation today is Linux with high-resolution timers enabled!"); + +#ifdef SIGRTMIN + /* Valgrind uses SIGRTMAX. To easy debugging we don't use it here */ + pa_rtsig_configure(SIGRTMIN, SIGRTMAX-1); +#endif + + pa_assert_se(mainloop = pa_mainloop_new()); + + if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm))) { + pa_log("pa_core_new() failed."); + goto finish; + } + + c->is_system_instance = !!conf->system_instance; + c->default_sample_spec = conf->default_sample_spec; + c->default_n_fragments = conf->default_n_fragments; + c->default_fragment_size_msec = conf->default_fragment_size_msec; + c->exit_idle_time = conf->exit_idle_time; + c->module_idle_time = conf->module_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->disable_remixing = !!conf->disable_remixing; + + 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 + +#ifdef OS_IS_WIN32 + pa_assert_se(timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&tv), message_cb, NULL)); +#endif + + if (conf->daemonize) + c->running_as_daemon = TRUE; + + oil_init(); + + if (!conf->no_cpu_limit) + pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0); + + buf = pa_strbuf_new(); + if (conf->default_script_file) + r = pa_cli_command_execute_file(c, conf->default_script_file, buf, &conf->fail); + + if (r >= 0) + r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail); + pa_log_error("%s", s = pa_strbuf_tostring_free(buf)); + pa_xfree(s); + + /* We completed the initial module loading, so let's disable it + * from now on, if requested */ + c->disallow_module_loading = !!conf->disallow_module_loading; + + if (r < 0 && conf->fail) { + pa_log("failed to initialize daemon."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + } else if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log("daemon startup without any loaded modules, refusing to work."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + } else { + + retval = 0; +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + + if (c->default_sink_name && + pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, 1) == NULL) { + pa_log_error("%s : Fatal error. Default sink name (%s) does not exist in name register.", __FILE__, c->default_sink_name); + retval = 1; + } else { + pa_log_info("Daemon startup complete."); + if (pa_mainloop_run(mainloop, &retval) < 0) + retval = 1; + pa_log_info("Daemon shutdown initiated."); + } + } + +#ifdef OS_IS_WIN32 + pa_mainloop_get_api(mainloop)->time_free(timer); +#endif + + pa_core_unref(c); + + if (!conf->no_cpu_limit) + pa_cpu_limit_done(); + + pa_signal_done(); + + pa_log_info("Daemon terminated."); + +finish: + + if (mainloop) + pa_mainloop_free(mainloop); + + if (conf) + pa_daemon_conf_free(conf); + + if (valid_pid_file) + pa_pid_file_remove(); + + pa_close_pipe(daemon_pipe); + +#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/polkit.c b/src/daemon/polkit.c new file mode 100644 index 00000000..362c5194 --- /dev/null +++ b/src/daemon/polkit.c @@ -0,0 +1,223 @@ +/* $Id$ */ + +/*** + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> + +#include <dbus/dbus.h> +#include <polkit-dbus/polkit-dbus.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "polkit.h" + +static pa_bool_t show_grant_dialog(const char *action_id) { + DBusError dbus_error; + DBusConnection *bus = NULL; + DBusMessage *m = NULL, *reply = NULL; + pa_bool_t r = FALSE; + uint32_t xid = 0; + int verdict; + + dbus_error_init(&dbus_error); + + if (!(bus = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error))) { + pa_log_error("Cannot connect to session bus: %s", dbus_error.message); + goto finish; + } + + if (!(m = dbus_message_new_method_call("org.gnome.PolicyKit", "/org/gnome/PolicyKit/Manager", "org.gnome.PolicyKit.Manager", "ShowDialog"))) { + pa_log_error("Failed to allocate D-Bus message."); + goto finish; + } + + if (!(dbus_message_append_args(m, DBUS_TYPE_STRING, &action_id, DBUS_TYPE_UINT32, &xid, DBUS_TYPE_INVALID))) { + pa_log_error("Failed to append arguments to D-Bus message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &dbus_error))) { + pa_log_warn("Failed to show grant dialog: %s", dbus_error.message); + goto finish; + } + + if (!(dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_BOOLEAN, &verdict, DBUS_TYPE_INVALID))) { + pa_log_warn("Malformed response from grant manager: %s", dbus_error.message); + goto finish; + } + + r = !!verdict; + +finish: + + if (bus) + dbus_connection_unref(bus); + + dbus_error_free(&dbus_error); + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +int pa_polkit_check(const char *action_id) { + int ret = -1; + DBusError dbus_error; + DBusConnection *bus = NULL; + PolKitCaller *caller = NULL; + PolKitAction *action = NULL; + PolKitContext *context = NULL; + PolKitError *polkit_error = NULL; + PolKitSession *session = NULL; + PolKitResult polkit_result; + + dbus_error_init(&dbus_error); + + if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error))) { + pa_log_error("Cannot connect to system bus: %s", dbus_error.message); + goto finish; + } + + if (!(caller = polkit_caller_new_from_pid(bus, getpid(), &dbus_error))) { + pa_log_error("Cannot get caller from PID: %s", dbus_error.message); + goto finish; + } + + /* This function is called when PulseAudio is called SUID root. We + * want to authenticate the real user that called us and not the + * effective user we gained through being SUID root. Hence we + * overwrite the UID caller data here explicitly, just for + * paranoia. In fact PolicyKit should fill in the UID here anyway + * -- an not the EUID or any other user id. */ + + if (!(polkit_caller_set_uid(caller, getuid()))) { + pa_log_error("Cannot set UID on caller object."); + goto finish; + } + + if (!(polkit_caller_get_ck_session(caller, &session))) { + pa_log_error("Failed to get CK session."); + goto finish; + } + + /* We need to overwrite the UID in both the caller and the session + * object */ + + if (!(polkit_session_set_uid(session, getuid()))) { + pa_log_error("Cannot set UID on session object."); + goto finish; + } + + if (!(action = polkit_action_new())) { + pa_log_error("Cannot allocate PolKitAction."); + goto finish; + } + + if (!polkit_action_set_action_id(action, action_id)) { + pa_log_error("Cannot set action_id"); + goto finish; + } + + if (!(context = polkit_context_new())) { + pa_log_error("Cannot allocate PolKitContext."); + goto finish; + } + + if (!polkit_context_init(context, &polkit_error)) { + pa_log_error("Cannot initialize PolKitContext: %s", polkit_error_get_error_message(polkit_error)); + goto finish; + } + + for (;;) { + +#ifdef HAVE_POLKIT_CONTEXT_IS_CALLER_AUTHORIZED + polkit_result = polkit_context_is_caller_authorized(context, action, caller, TRUE, &polkit_error); + + if (polkit_error_is_set(polkit_error)) { + pa_log_error("Could not determine whether caller is authorized: %s", polkit_error_get_error_message(polkit_error)); + goto finish; + } +#else + + polkit_result = polkit_context_can_caller_do_action(context, action, caller); + +#endif + + if (polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH || + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_SESSION || + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_ALWAYS || +#ifdef POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT || +#endif + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH || + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_SESSION || + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_ALWAYS +#ifdef POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT + || polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT +#endif + ) { + + if (show_grant_dialog(action_id)) + continue; + } + + break; + } + + if (polkit_result != POLKIT_RESULT_YES && polkit_result != POLKIT_RESULT_NO) + pa_log_warn("PolicyKit responded with '%s'", polkit_result_to_string_representation(polkit_result)); + + ret = polkit_result == POLKIT_RESULT_YES; + +finish: + + if (caller) + polkit_caller_unref(caller); + + if (action) + polkit_action_unref(action); + + if (context) + polkit_context_unref(context); + + if (bus) + dbus_connection_unref(bus); + + dbus_error_free(&dbus_error); + + if (polkit_error) + polkit_error_free(polkit_error); + + return ret; +} diff --git a/src/daemon/polkit.h b/src/daemon/polkit.h new file mode 100644 index 00000000..cbcf6a6a --- /dev/null +++ b/src/daemon/polkit.h @@ -0,0 +1,29 @@ +#ifndef foopolkithfoo +#define foopolkithfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2007 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +int pa_polkit_check(const char *action); + +#endif diff --git a/src/daemon/pulseaudio-module-xsmp.desktop b/src/daemon/pulseaudio-module-xsmp.desktop new file mode 100644 index 00000000..fa719a73 --- /dev/null +++ b/src/daemon/pulseaudio-module-xsmp.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=PulseAudio Session Management +Comment=Load module-x11-xsmp into PulseAudio +Exec=pactl load-module module-x11-xsmp +Terminal=false +Type=Application +Categories= +GenericName= |