From e205b25d65ccb380fa158711e24d55b6de5d9bc1 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 16 Feb 2006 19:19:58 +0000 Subject: Reorganised the source tree. We now have src/ with a couple of subdirs: * daemon/ - Contains the files specific to the polypaudio daemon. * modules/ - All loadable modules. * polyp/ - Files that are part of the public, application interface or are only used in libpolyp. * polypcore/ - All other shared files. * tests/ - Test programs. * utils/ - Utility programs. git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@487 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/daemon/caps.c | 131 +++++++++++++ src/daemon/caps.h | 29 +++ src/daemon/cmdline.c | 300 ++++++++++++++++++++++++++++++ src/daemon/cmdline.h | 35 ++++ src/daemon/cpulimit.c | 236 ++++++++++++++++++++++++ src/daemon/cpulimit.h | 34 ++++ src/daemon/daemon-conf.c | 297 +++++++++++++++++++++++++++++ src/daemon/daemon-conf.h | 80 ++++++++ src/daemon/dumpmodules.c | 99 ++++++++++ src/daemon/dumpmodules.h | 31 ++++ src/daemon/main.c | 472 +++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1744 insertions(+) create mode 100644 src/daemon/caps.c create mode 100644 src/daemon/caps.h create mode 100644 src/daemon/cmdline.c create mode 100644 src/daemon/cmdline.h create mode 100644 src/daemon/cpulimit.c create mode 100644 src/daemon/cpulimit.h create mode 100644 src/daemon/daemon-conf.c create mode 100644 src/daemon/daemon-conf.h create mode 100644 src/daemon/dumpmodules.c create mode 100644 src/daemon/dumpmodules.h create mode 100644 src/daemon/main.c (limited to 'src/daemon') diff --git a/src/daemon/caps.c b/src/daemon/caps.c new file mode 100644 index 00000000..8d429459 --- /dev/null +++ b/src/daemon/caps.c @@ -0,0 +1,131 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_SYS_CAPABILITY_H +#include +#endif + +#include +#include "caps.h" + +#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(__FILE__": dropping root rights.\n"); + +#if defined(HAVE_SETRESUID) + setresuid(uid, uid, uid); +#elif defined(HAVE_SETREUID) + setreuid(uid, uid); +#else + setuid(uid); + seteuid(uid); +#endif +} + +#else + +void pa_drop_root(void) { +} + +#endif + +#ifdef HAVE_SYS_CAPABILITY_H + +/* Limit 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(); + 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; + + pa_log_info(__FILE__": dropped capabilities successfully.\n"); + + r = 0; + +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(); + assert(caps); + + cap_clear(caps); + + if (cap_set_proc(caps) < 0) { + pa_log(__FILE__": failed to drop capabilities: %s\n", strerror(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..3bb861d1 --- /dev/null +++ b/src/daemon/caps.h @@ -0,0 +1,29 @@ +#ifndef foocapshfoo +#define foocapshfoo + +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; 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..0b5f9ec7 --- /dev/null +++ b/src/daemon/cmdline.c @@ -0,0 +1,300 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "cmdline.h" +#include +#include +#include + +/* 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_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 +}; + +/* Tabel for getopt_long() */ +static 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}, + {"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}, + {NULL, 0, 0, 0} +}; + +void pa_cmdline_help(const char *argv0) { + const char *e; + + 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" + " -k --kill Kill a running daemon\n" + " --check Check for a running daemon\n\n" + + "OPTIONS:\n" + " -D, --daemonize[=BOOL] Daemonize after startup\n" + " --fail[=BOOL] Quit when startup fails\n" + " --high-priority[=BOOL] Try to set high process priority\n" + " (only available as root)\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" + " (one of src-sinc-medium-quality,\n" + " src-sinc-best-quality,src-sinc-fastest\n" + " src-zero-order-hold,src-linear,trivial)\n" + " --use-pid-file[=BOOL] Create a PID file\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; + assert(conf && argc && 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 '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': + pa_strbuf_printf(buf, ".include %s\n", optarg); + break; + + case 'C': + pa_strbuf_puts(buf, "load-module module-cli\n"); + break; + + case ARG_DAEMONIZE: + case 'D': + if ((conf->daemonize = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(__FILE__": --daemonize expects boolean argument\n"); + goto fail; + } + break; + + case ARG_FAIL: + if ((conf->fail = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(__FILE__": --fail expects boolean argument\n"); + goto fail; + } + break; + + case 'v': + case ARG_LOG_LEVEL: + + if (optarg) { + if (pa_daemon_conf_set_log_level(conf, optarg) < 0) { + pa_log(__FILE__": --log-level expects log level argument (either numeric in range 0..4 or one of debug, info, notice, warn, error).\n"); + 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) : 1) < 0) { + pa_log(__FILE__": --high-priority expects boolean argument\n"); + goto fail; + } + break; + + case ARG_DISALLOW_MODULE_LOADING: + if ((conf->disallow_module_loading = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(__FILE__": --disallow-module-loading expects boolean argument\n"); + goto fail; + } + break; + + case ARG_USE_PID_FILE: + if ((conf->use_pid_file = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(__FILE__": --use-pid-file expects boolean argument\n"); + 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(__FILE__": Invalid log target: use either 'syslog', 'stderr' or 'auto'.\n"); + 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(__FILE__": Invalid resample method '%s'.\n", optarg); + 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..e2eaf0d2 --- /dev/null +++ b/src/daemon/cmdline.h @@ -0,0 +1,35 @@ +#ifndef foocmdlinehfoo +#define foocmdlinehfoo + +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; 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..6887796f --- /dev/null +++ b/src/daemon/cpulimit.c @@ -0,0 +1,236 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "cpulimit.h" +#include +#include + +#ifdef HAVE_SIGXCPU + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_RESOURCE_H +#include +#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 2s */ +#define CPUTIME_INTERVAL_HARD (2) + +/* 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) { + int r; + long n; + struct rlimit rl; + struct rusage ru; + + /* Get the current CPU time of the current process */ + r = getrusage(RUSAGE_SELF, &ru); + assert(r >= 0); + + n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t; + + r = getrlimit(RLIMIT_CPU, &rl); + assert(r >= 0); + + rl.rlim_cur = n; + r = setrlimit(RLIMIT_CPU, &rl); + assert(r >= 0); +} + +/* A simple, thread-safe puts() work-alike */ +static void write_err(const char *p) { + pa_loop_write(2, p, strlen(p)); +} + +/* The signal handler, called on every SIGXCPU */ +static void signal_handler(int sig) { + assert(sig == SIGXCPU); + + if (phase == PHASE_IDLE) { + time_t now; + +#ifdef PRINT_CPU_LOAD + char t[256]; +#endif + + time(&now); + +#ifdef PRINT_CPU_LOAD + 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 */ + } +} + +/* Callback for IO events on the FIFO */ +static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags f, void *userdata) { + char c; + assert(m && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == the_pipe[0]); + read(the_pipe[0], &c, sizeof(c)); + m->quit(m, 1); /* Quit the main loop */ +} + +/* Initializes CPU load limiter */ +int pa_cpu_limit_init(pa_mainloop_api *m) { + struct sigaction sa; + assert(m && !api && !io_event && the_pipe[0] == -1 && the_pipe[1] == -1 && !installed); + + time(&last_time); + + /* Prepare the main loop pipe */ + if (pipe(the_pipe) < 0) { + pa_log(__FILE__": pipe() failed: %s\n", strerror(errno)); + return -1; + } + + pa_make_nonblock_fd(the_pipe[0]); + pa_make_nonblock_fd(the_pipe[1]); + pa_fd_set_cloexec(the_pipe[0], 1); + pa_fd_set_cloexec(the_pipe[1], 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) { + int r; + + if (io_event) { + assert(api); + api->io_free(io_event); + io_event = NULL; + api = NULL; + } + + if (the_pipe[0] >= 0) + close(the_pipe[0]); + if (the_pipe[1] >= 0) + close(the_pipe[1]); + the_pipe[0] = the_pipe[1] = -1; + + if (installed) { + r = sigaction(SIGXCPU, &sigaction_prev, NULL); + assert(r >= 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..f3c5534d --- /dev/null +++ b/src/daemon/cpulimit.h @@ -0,0 +1,34 @@ +#ifndef foocpulimithfoo +#define foocpulimithfoo + +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +/* This kills the polypaudio 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..8fe3c4cc --- /dev/null +++ b/src/daemon/daemon-conf.c @@ -0,0 +1,297 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "daemon-conf.h" +#include +#include +#include +#include +#include + +#ifndef DEFAULT_CONFIG_DIR +# ifndef OS_IS_WIN32 +# define DEFAULT_CONFIG_DIR "/etc/polypaudio" +# else +# define DEFAULT_CONFIG_DIR "%POLYP_ROOT%" +# endif +#endif + +#ifndef OS_IS_WIN32 +# define PATH_SEP "/" +#else +# define PATH_SEP "\\" +#endif + +#define DEFAULT_SCRIPT_FILE DEFAULT_CONFIG_DIR PATH_SEP "default.pa" +#define DEFAULT_SCRIPT_FILE_USER ".polypaudio" PATH_SEP "default.pa" +#define DEFAULT_CONFIG_FILE DEFAULT_CONFIG_DIR PATH_SEP "daemon.conf" +#define DEFAULT_CONFIG_FILE_USER ".polypaudio" PATH_SEP "daemon.conf" + +#define ENV_SCRIPT_FILE "POLYP_SCRIPT" +#define ENV_CONFIG_FILE "POLYP_CONFIG" +#define ENV_DL_SEARCH_PATH "POLYP_DLPATH" + +static const pa_daemon_conf default_conf = { + .cmd = PA_CMD_DAEMON, + .daemonize = 0, + .fail = 1, + .high_priority = 0, + .disallow_module_loading = 0, + .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_SRC_SINC_FASTEST, + .config_file = NULL, + .use_pid_file = 1 +}; + +pa_daemon_conf* pa_daemon_conf_new(void) { + FILE *f; + pa_daemon_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf)); + + if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file))) + fclose(f); + +#ifdef DLSEARCHPATH + c->dl_search_path = pa_xstrdup(DLSEARCHPATH); +#endif + return c; +} + +void pa_daemon_conf_free(pa_daemon_conf *c) { + 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) { + assert(c && 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; + assert(c && 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; + assert(c && 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; + assert(filename && lvalue && rvalue && data); + + if (pa_daemon_conf_set_log_target(c, rvalue) < 0) { + pa_log(__FILE__": [%s:%u] Invalid log target '%s'.\n", 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; + assert(filename && lvalue && rvalue && data); + + if (pa_daemon_conf_set_log_level(c, rvalue) < 0) { + pa_log(__FILE__": [%s:%u] Invalid log level '%s'.\n", 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; + assert(filename && lvalue && rvalue && data); + + if (pa_daemon_conf_set_resample_method(c, rvalue) < 0) { + pa_log(__FILE__": [%s:%u] Inavalid resample method '%s'.\n", filename, line, rvalue); + return -1; + } + + 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 }, + { "disallow-module-loading", 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 }, + { "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 }, + { "use-pid-file", pa_config_parse_bool, NULL }, + { NULL, NULL, NULL }, + }; + + table[0].data = &c->daemonize; + table[1].data = &c->fail; + table[2].data = &c->high_priority; + table[3].data = &c->disallow_module_loading; + table[4].data = &c->exit_idle_time; + table[5].data = &c->module_idle_time; + table[6].data = &c->scache_idle_time; + table[7].data = &c->dl_search_path; + table[8].data = &c->default_script_file; + table[9].data = c; + table[10].data = c; + table[11].data = c; + table[12].data = c; + table[13].data = &c->use_pid_file; + + 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); + + if (!f && errno != ENOENT) { + pa_log(__FILE__": WARNING: failed to open configuration file '%s': %s\n", filename, strerror(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_strbuf_new(); + + if (c->config_file) + pa_strbuf_printf(s, "### Read from configuration file: %s ###\n", c->config_file); + + assert(c->log_level <= PA_LOG_LEVEL_MAX); + + pa_strbuf_printf(s, "daemonize = %i\n", !!c->daemonize); + pa_strbuf_printf(s, "fail = %i\n", !!c->fail); + pa_strbuf_printf(s, "high-priority = %i\n", !!c->high_priority); + pa_strbuf_printf(s, "disallow-module-loading = %i\n", !!c->disallow_module_loading); + 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, "use-pid-file = %i\n", c->use_pid_file); + + 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..d5131419 --- /dev/null +++ b/src/daemon/daemon-conf.h @@ -0,0 +1,80 @@ +#ifndef foodaemonconfhfoo +#define foodaemonconfhfoo + +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +/* 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_daemon_conf_cmd_t; + +/* A structure containing configuration data for the Polypaudio server . */ +typedef struct pa_daemon_conf { + pa_daemon_conf_cmd_t cmd; + int daemonize, + fail, + high_priority, + disallow_module_loading, + exit_idle_time, + module_idle_time, + scache_idle_time, + auto_log_target, + use_pid_file; + char *script_commands, *dl_search_path, *default_script_file; + pa_log_target_t log_target; + pa_log_level_t log_level; + int resample_method; + char *config_file; +} 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/dumpmodules.c b/src/daemon/dumpmodules.c new file mode 100644 index 00000000..8d8eb0b9 --- /dev/null +++ b/src/daemon/dumpmodules.c @@ -0,0 +1,99 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "dumpmodules.h" +#include +#include + +#define PREFIX "module-" + +static void short_info(const char *name, PA_GCC_UNUSED const char *path, pa_modinfo *i) { + assert(name && 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; + assert(name && 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); + } + + 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; + + if ((i = pa_modinfo_get_by_name(path ? path : name))) { + info(name, path, i); + pa_modinfo_free(i); + } +} + +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)) + 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[]) { + if (argc > 0) { + int i; + for (i = 0; i < argc; i++) + show_info(argv[i], NULL, long_info); + } else + lt_dlforeachfile(NULL, callback, c); +} diff --git a/src/daemon/dumpmodules.h b/src/daemon/dumpmodules.h new file mode 100644 index 00000000..968d2de9 --- /dev/null +++ b/src/daemon/dumpmodules.h @@ -0,0 +1,31 @@ +#ifndef foodumpmoduleshfoo +#define foodumpmoduleshfoo + +/* $Id*/ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; 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/main.c b/src/daemon/main.c new file mode 100644 index 00000000..6be83d8c --- /dev/null +++ b/src/daemon/main.c @@ -0,0 +1,472 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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. + + polypaudio 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 polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_LIBWRAP +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include "cmdline.h" +#include +#include +#include +#include +#include "cpulimit.h" +#include +#include "daemon-conf.h" +#include "dumpmodules.h" +#include "caps.h" +#include +#include +#include + +#ifdef HAVE_LIBWRAP +/* Only one instance of these variables */ +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#ifdef OS_IS_WIN32 + +static void message_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { + MSG msg; + + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + raise(SIGTERM); + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +#endif + +static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, int sig, void *userdata) { + pa_log_info(__FILE__": Got signal %s.\n", pa_strsignal(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(c); + pa_xfree(c); + return; + } +#endif + + case SIGINT: + case SIGTERM: + default: + pa_log_info(__FILE__": Exiting.\n"); + m->quit(m, 1); + break; + } +} + +static void close_pipe(int p[2]) { + if (p[0] != -1) + close(p[0]); + if (p[1] != -1) + close(p[1]); + p[0] = p[1] = -1; +} + +int main(int argc, char *argv[]) { + pa_core *c; + pa_strbuf *buf = NULL; + pa_daemon_conf *conf; + pa_mainloop *mainloop; + + char *s; + int r, retval = 1, d = 0; + int daemon_pipe[2] = { -1, -1 }; + int suid_root; + int valid_pid_file = 0; + +#ifdef HAVE_GETUID + gid_t gid = (gid_t) -1; +#endif + +#ifdef OS_IS_WIN32 + pa_defer_event *defer; +#endif + + pa_limit_caps(); + +#ifdef HAVE_GETUID + suid_root = getuid() != 0 && geteuid() == 0; + + if (suid_root && (pa_uid_in_group("realtime", &gid) <= 0 || gid >= 1000)) { + pa_log_warn(__FILE__": WARNING: called SUID root, but not in group 'realtime'.\n"); + pa_drop_root(); + } +#else + suid_root = 0; +#endif + + LTDL_SET_PRELOADED_SYMBOLS(); + + r = lt_dlinit(); + assert(r == 0); + +#ifdef OS_IS_WIN32 + { + WSADATA data; + WSAStartup(MAKEWORD(2, 0), &data); + } +#endif + + pa_log_set_ident("polypaudio"); + + 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(__FILE__": failed to parse command line.\n"); + 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 (conf->high_priority && conf->cmd == PA_CMD_DAEMON) + pa_raise_priority(); + + pa_drop_caps(); + + if (suid_root) + pa_drop_root(); + + if (conf->dl_search_path) + lt_dlsetsearchpath(conf->dl_search_path); + + 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_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) < 0) { + pa_log_info(__FILE__": daemon not running\n"); + } else { + pa_log_info(__FILE__": daemon running as PID %u\n", pid); + retval = 0; + } + + goto finish; + + } + case PA_CMD_KILL: + + if (pa_pid_file_kill(SIGINT, NULL) < 0) + pa_log(__FILE__": failed to kill daemon.\n"); + else + retval = 0; + + goto finish; + + default: + assert(conf->cmd == PA_CMD_DAEMON); + } + + if (conf->daemonize) { + pid_t child; + int tty_fd; + + if (pa_stdio_acquire() < 0) { + pa_log(__FILE__": failed to acquire stdio.\n"); + goto finish; + } + +#ifdef HAVE_FORK + if (pipe(daemon_pipe) < 0) { + pa_log(__FILE__": failed to create pipe.\n"); + goto finish; + } + + if ((child = fork()) < 0) { + pa_log(__FILE__": fork() failed: %s\n", strerror(errno)); + goto finish; + } + + if (child != 0) { + /* Father */ + + close(daemon_pipe[1]); + daemon_pipe[1] = -1; + + if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval)) != sizeof(retval)) { + pa_log(__FILE__": read() failed: %s\n", strerror(errno)); + retval = 1; + } + + if (retval) + pa_log(__FILE__": daemon startup failed.\n"); + else + pa_log_info(__FILE__": daemon startup successful.\n"); + + goto finish; + } + + close(daemon_pipe[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 + close(0); + close(1); + 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); + close(tty_fd); + } +#endif + } + + chdir("/"); + + if (conf->use_pid_file) { + if (pa_pid_file_create() < 0) { + pa_log(__FILE__": pa_pid_file_create() failed.\n"); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval)); +#endif + goto finish; + } + + valid_pid_file = 1; + } + + mainloop = pa_mainloop_new(); + assert(mainloop); + + c = pa_core_new(pa_mainloop_get_api(mainloop)); + assert(c); + + r = pa_signal_init(pa_mainloop_get_api(mainloop)); + assert(r == 0); + pa_signal_new(SIGINT, signal_callback, c); + pa_signal_new(SIGTERM, signal_callback, c); +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + +#ifdef OS_IS_WIN32 + defer = pa_mainloop_get_api(mainloop)->defer_new(pa_mainloop_get_api(mainloop), message_cb, NULL); + assert(defer); +#endif + + if (conf->daemonize) + c->running_as_daemon = 1; + +#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 + + oil_init(); + + r = pa_cpu_limit_init(pa_mainloop_get_api(mainloop)); + assert(r == 0); + + buf = pa_strbuf_new(); + assert(buf); + 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(s = pa_strbuf_tostring_free(buf)); + pa_xfree(s); + + if (r < 0 && conf->fail) { + pa_log(__FILE__": failed to initialize daemon.\n"); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval)); +#endif + } else if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log(__FILE__": daemon startup without any loaded modules, refusing to work.\n"); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval)); +#endif + } else { + + retval = 0; +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval)); +#endif + + c->disallow_module_loading = conf->disallow_module_loading; + 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; + + 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.\n", __FILE__, c->default_sink_name); + retval = 1; + } else { + pa_log_info(__FILE__": Daemon startup complete.\n"); + if (pa_mainloop_run(mainloop, &retval) < 0) + retval = 1; + pa_log_info(__FILE__": Daemon shutdown initiated.\n"); + } + } + +#ifdef OS_IS_WIN32 + pa_mainloop_get_api(mainloop)->defer_free(defer); +#endif + + pa_core_free(c); + + pa_cpu_limit_done(); + pa_signal_done(); + pa_mainloop_free(mainloop); + + pa_log_info(__FILE__": Daemon terminated.\n"); + +finish: + + if (conf) + pa_daemon_conf_free(conf); + + if (valid_pid_file) + pa_pid_file_remove(); + + close_pipe(daemon_pipe); + +#ifdef OS_IS_WIN32 + WSACleanup(); +#endif + + lt_dlexit(); + + return retval; +} -- cgit