summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Gaskin <patrick@pgaskin.net>2021-05-30 15:28:30 -0400
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>2021-06-16 09:17:27 +0000
commit4f3ca10d9ec2db1d39b1da4bbc2c84ae5ade0aff (patch)
treee5b9fce261b54943cdd2486f6ecab20109d1a135
parent87b4d68978fc144b15a0b6746ee8126869f85f1c (diff)
daemon: Add support for running as a service on win32
* Minimal implementation of --system on win32. * Wrap main with a Windows Service on win32 (with a fallback to running it directly). * Update PA_SYSTEM_{RUNTIME,STATE,CONFIG}_PATH and HOME dynamically on Windows (overrides the build config, similar to the existing config path replacement logic). Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/549>
-rw-r--r--src/daemon/main.c134
-rw-r--r--src/pulse/context.c7
-rw-r--r--src/pulsecore/core-util.c10
-rw-r--r--src/pulsecore/core-util.h1
4 files changed, 151 insertions, 1 deletions
diff --git a/src/daemon/main.c b/src/daemon/main.c
index a424d9ef9..ec1ec3cac 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -60,6 +60,10 @@
#include <systemd/sd-daemon.h>
#endif
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
#include <pulse/client-conf.h>
#include <pulse/mainloop.h>
#include <pulse/mainloop-signal.h>
@@ -156,7 +160,44 @@ static void signal_callback(pa_mainloop_api* m, pa_signal_event *e, int sig, voi
}
}
-#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+
+#if defined(OS_IS_WIN32)
+
+static int change_user(void) {
+ pa_log_info("Overriding system runtime/config base dir to '%s'.", pa_win32_get_system_appdata());
+
+ /* On other platforms, these paths are compiled into PulseAudio. This isn't
+ * suitable on Windows. Firstly, Windows doesn't follow the FHS or use Unix
+ * paths and the build system can't handle Windows-style paths properly.
+ * Secondly, the idiomatic location for a service's state and shared data is
+ * ProgramData, and the location of special folders is dynamic on Windows.
+ * Also, this method of handling paths is consistent with how they are
+ * handled on Windows in other parts of PA. Note that this is only needed
+ * in system-wide mode since paths in user instances are already handled
+ * properly.
+ */
+
+ char *run_path = pa_sprintf_malloc("%s" PA_PATH_SEP "run", pa_win32_get_system_appdata());
+ char *lib_path = pa_sprintf_malloc("%s" PA_PATH_SEP "lib", pa_win32_get_system_appdata());
+
+ /* TODO: directory ACLs */
+
+ pa_set_env("HOME", run_path);
+ if (!getenv("PULSE_RUNTIME_PATH"))
+ pa_set_env("PULSE_RUNTIME_PATH", run_path);
+ if (!getenv("PULSE_CONFIG_PATH"))
+ pa_set_env("PULSE_CONFIG_PATH", lib_path);
+ if (!getenv("PULSE_STATE_PATH"))
+ pa_set_env("PULSE_STATE_PATH", lib_path);
+
+ pa_xfree(run_path);
+ pa_xfree(lib_path);
+
+ pa_log_info("Not changing user for system instance on Windows.");
+ return 0;
+}
+
+#elif defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
static int change_user(void) {
struct passwd *pw;
@@ -377,7 +418,45 @@ fail:
}
#endif
+#ifdef OS_IS_WIN32
+#define SVC_NAME "PulseAudio"
+static bool is_svc = true;
+static int argc;
+static char **argv;
+static int real_main(int s_argc, char *s_argv[]);
+static SERVICE_STATUS_HANDLE svc_status;
+
+DWORD svc_callback(DWORD ctl, DWORD evt, LPVOID data, LPVOID userdata) {
+ pa_mainloop **m = userdata;
+ switch (ctl) {
+ case SERVICE_CONTROL_STOP:
+ case SERVICE_CONTROL_SHUTDOWN:
+ if (m) {
+ pa_log_info("Exiting.");
+ pa_mainloop_get_api(*m)->quit(pa_mainloop_get_api(*m), 0);
+ }
+ return NO_ERROR;
+ case SERVICE_CONTROL_INTERROGATE:
+ return NO_ERROR;
+ }
+ return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+int main(int p_argc, char *p_argv[]) {
+ argc = p_argc;
+ argv = p_argv;
+ if (StartServiceCtrlDispatcherA((SERVICE_TABLE_ENTRYA[]){
+ {SVC_NAME, (LPSERVICE_MAIN_FUNCTIONA) real_main},
+ {0},
+ })) return 0;
+ is_svc = false;
+ return real_main(0, NULL);
+}
+
+static int real_main(int s_argc, char *s_argv[]) {
+#else
int main(int argc, char *argv[]) {
+#endif
pa_core *c = NULL;
pa_strbuf *buf = NULL;
pa_daemon_conf *conf = NULL;
@@ -402,6 +481,23 @@ int main(int argc, char *argv[]) {
bool start_server;
#endif
+#ifdef OS_IS_WIN32
+ if (is_svc && !(svc_status = RegisterServiceCtrlHandlerExA(SVC_NAME, (LPHANDLER_FUNCTION_EX) svc_callback, &mainloop))) {
+ pa_log("Failed to register service control handler.");
+ goto finish;
+ }
+
+ if (is_svc) {
+ SetServiceStatus(svc_status, &(SERVICE_STATUS){
+ .dwServiceType = SERVICE_WIN32,
+ .dwCurrentState = SERVICE_START_PENDING,
+ .dwControlsAccepted = 0,
+ .dwWin32ExitCode = NO_ERROR,
+ .dwWaitHint = 3000,
+ });
+ }
+#endif
+
pa_log_set_ident("pulseaudio");
pa_log_set_level(PA_LOG_NOTICE);
pa_log_set_flags(PA_LOG_COLORS|PA_LOG_PRINT_FILE|PA_LOG_PRINT_LEVEL, PA_LOG_RESET);
@@ -1172,6 +1268,18 @@ int main(int argc, char *argv[]) {
sd_notify(0, "READY=1");
#endif
+#ifdef OS_IS_WIN32
+ if (is_svc) {
+ SetServiceStatus(svc_status, &(SERVICE_STATUS){
+ .dwServiceType = SERVICE_WIN32,
+ .dwCurrentState = SERVICE_RUNNING,
+ .dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN,
+ .dwWin32ExitCode = NO_ERROR,
+ .dwWaitHint = 0,
+ });
+ }
+#endif
+
retval = 0;
if (pa_mainloop_run(mainloop, &retval) < 0)
goto finish;
@@ -1182,6 +1290,18 @@ int main(int argc, char *argv[]) {
sd_notify(0, "STOPPING=1");
#endif
+#ifdef OS_IS_WIN32
+ if (is_svc) {
+ SetServiceStatus(svc_status, &(SERVICE_STATUS){
+ .dwServiceType = SERVICE_WIN32,
+ .dwCurrentState = SERVICE_STOP_PENDING,
+ .dwControlsAccepted = 0,
+ .dwWin32ExitCode = NO_ERROR,
+ .dwWaitHint = 2000,
+ });
+ }
+#endif
+
finish:
#ifdef HAVE_DBUS
if (server_bus)
@@ -1249,5 +1369,17 @@ finish:
dbus_shutdown();
#endif
+#ifdef OS_IS_WIN32
+ if (is_svc) {
+ SetServiceStatus(svc_status, &(SERVICE_STATUS){
+ .dwServiceType = SERVICE_WIN32,
+ .dwCurrentState = SERVICE_STOPPED,
+ .dwControlsAccepted = 0,
+ .dwWin32ExitCode = retval ? ERROR_PROCESS_ABORTED : NO_ERROR,
+ .dwWaitHint = 0,
+ });
+ }
+#endif
+
return retval;
}
diff --git a/src/pulse/context.c b/src/pulse/context.c
index b0a838860..e535d044f 100644
--- a/src/pulse/context.c
+++ b/src/pulse/context.c
@@ -1035,7 +1035,14 @@ int pa_context_connect(
}
/* The system wide instance via PF_LOCAL */
+#ifndef OS_IS_WIN32
c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET);
+#else
+ /* see change_user in src/daemon/main.c */
+ char *run_path = pa_sprintf_malloc("%s" PA_PATH_SEP "run" PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET, pa_win32_get_system_appdata());
+ c->server_list = pa_strlist_prepend(c->server_list, run_path);
+ pa_xfree(run_path);
+#endif
/* The user instance via PF_LOCAL */
c->server_list = prepend_per_user(c->server_list);
diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
index 89e37d5f6..c383a61c0 100644
--- a/src/pulsecore/core-util.c
+++ b/src/pulsecore/core-util.c
@@ -83,6 +83,7 @@
#ifdef HAVE_WINDOWS_H
#include <windows.h>
+#include <shlobj.h>
#endif
#ifndef ENOTSUP
@@ -171,6 +172,15 @@ char *pa_win32_get_toplevel(HANDLE handle) {
return toplevel;
}
+char *pa_win32_get_system_appdata() {
+ static char appdata[MAX_PATH] = {0};
+
+ if (!*appdata && SHGetFolderPathAndSubDirA(NULL, CSIDL_COMMON_APPDATA|CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, "PulseAudio", appdata) != S_OK)
+ return NULL;
+
+ return appdata;
+}
+
#endif
static void set_nonblock(int fd, bool nonblock) {
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
index ed123c796..a69d53be8 100644
--- a/src/pulsecore/core-util.h
+++ b/src/pulsecore/core-util.h
@@ -310,6 +310,7 @@ bool pa_running_in_vm(void);
#ifdef OS_IS_WIN32
char *pa_win32_get_toplevel(HANDLE handle);
+char *pa_win32_get_system_appdata();
#endif
size_t pa_page_size(void);