From 8001c9ad537d43eb587c5a325296bdaebd565708 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sat, 27 May 2023 20:02:07 +0200 Subject: improve launchd integration --- src/log.h | 10 +++++ src/service.h | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- src/skhd.c | 4 +- 3 files changed, 123 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/log.h b/src/log.h index 2e8167b..d3cf6f1 100644 --- a/src/log.h +++ b/src/log.h @@ -33,4 +33,14 @@ error(const char *format, ...) exit(EXIT_FAILURE); } +static inline void +require(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(EXIT_SUCCESS); +} + #endif diff --git a/src/service.h b/src/service.h index bb43e8e..ae97084 100644 --- a/src/service.h +++ b/src/service.h @@ -29,13 +29,16 @@ " RunAtLoad\n" \ " \n" \ " KeepAlive\n" \ - " \n" \ + " \n" \ + " SuccessfulExit\n" \ + " \n" \ + " Crashed\n" \ + " \n" \ + " \n" \ " StandardOutPath\n" \ " /tmp/skhd_%s.out.log\n" \ " StandardErrorPath\n" \ " /tmp/skhd_%s.err.log\n" \ - " ThrottleInterval\n" \ - " 30\n" \ " ProcessType\n" \ " Interactive\n" \ " Nice\n" \ @@ -43,12 +46,27 @@ "\n" \ "" -static bool file_exists(char *filename); +// +// NOTE(koekeishiya): A launchd service has the following states: +// +// 1. Installed / Uninstalled +// 2. Active (Enable / Disable) +// 3. Bootstrapped (Load / Unload) +// 4. Running (Start / Stop) +// -static int safe_exec(char *const argv[]) +static int safe_exec(char *const argv[], bool suppress_output) { pid_t pid; - int status = posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL); + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + + if (suppress_output) { + posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0); + posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0); + } + + int status = posix_spawn(&pid, argv[0], &actions, NULL, argv, NULL); if (status) return 1; while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR)) { @@ -183,6 +201,8 @@ static int service_install_internal(char *skhd_plist_path) return result; } +static bool file_exists(char *filename); + static int service_install(void) { char *skhd_plist_path = populate_plist_path(); @@ -208,7 +228,6 @@ static int service_uninstall(void) static int service_start(void) { char *skhd_plist_path = populate_plist_path(); - if (!file_exists(skhd_plist_path)) { warn("skhd: service file '%s' is not installed! attempting installation..\n", skhd_plist_path); @@ -218,35 +237,110 @@ static int service_start(void) } } - const char *const args[] = { _PATH_LAUNCHCTL, "load", "-w", skhd_plist_path, NULL }; - return safe_exec((char *const*)args); + char service_target[MAXLEN]; + snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST); + + char domain_target[MAXLEN]; + snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid()); + + // + // NOTE(koekeishiya): Check if service is bootstrapped + // + + const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL }; + int is_bootstrapped = safe_exec((char *const*)args, true); + + if (is_bootstrapped != 0) { + + // + // NOTE(koekeishiya): Service is not bootstrapped and could be disabled. + // There is no way to query if the service is disabled, and we cannot + // bootstrap a disabled service. Try to enable the service. This will be + // a no-op if the service is already enabled. + // + + const char *const args[] = { _PATH_LAUNCHCTL, "enable", service_target, NULL }; + safe_exec((char *const*)args, false); + + // + // NOTE(koekeishiya): Bootstrap service into the target domain. + // This will also start the program **iff* RunAtLoad is set to true. + // + + const char *const args2[] = { _PATH_LAUNCHCTL, "bootstrap", domain_target, skhd_plist_path, NULL }; + return safe_exec((char *const*)args2, false); + } else { + + // + // NOTE(koekeishiya): The service has already been bootstrapped. + // Tell the bootstrapped service to launch immediately; it is an + // error to bootstrap a service that has already been bootstrapped. + // + + const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", service_target, NULL }; + return safe_exec((char *const*)args, false); + } } static int service_restart(void) { char *skhd_plist_path = populate_plist_path(); - if (!file_exists(skhd_plist_path)) { error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path); } - char skhd_service_id[MAXLEN]; - snprintf(skhd_service_id, sizeof(skhd_service_id), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST); + char service_target[MAXLEN]; + snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST); - const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", skhd_service_id, NULL }; - return safe_exec((char *const*)args); + const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", service_target, NULL }; + return safe_exec((char *const*)args, false); } static int service_stop(void) { char *skhd_plist_path = populate_plist_path(); - if (!file_exists(skhd_plist_path)) { error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path); } - const char *const args[] = { _PATH_LAUNCHCTL, "unload", "-w", skhd_plist_path, NULL }; - return safe_exec((char *const*)args); + char service_target[MAXLEN]; + snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST); + + char domain_target[MAXLEN]; + snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid()); + + // + // NOTE(koekeishiya): Check if service is bootstrapped + // + + const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL }; + int is_bootstrapped = safe_exec((char *const*)args, true); + + if (is_bootstrapped != 0) { + + // + // NOTE(koekeishiya): Service is not bootstrapped, but the program + // could still be running an instance that was started **while the service + // was bootstrapped**, so we tell it to stop said service. + // + + const char *const args[] = { _PATH_LAUNCHCTL, "kill", "SIGTERM", service_target, NULL }; + return safe_exec((char *const*)args, false); + } else { + + // + // NOTE(koekeishiya): Service is bootstrapped; we stop a potentially + // running instance of the program and unload the service, making it + // not trigger automatically in the future. + // + // This is NOT the same as disabling the service, which will prevent + // it from being boostrapped in the future (without explicitly re-enabling + // it first). + // + + const char *const args[] = { _PATH_LAUNCHCTL, "bootout", domain_target, skhd_plist_path, NULL }; + return safe_exec((char *const*)args, false); + } } #endif diff --git a/src/skhd.c b/src/skhd.c index 6da4456..b8f5c2f 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -459,7 +459,7 @@ static GLOBAL_CONNECTION_CALLBACK(connection_handler) int main(int argc, char **argv) { if (getuid() == 0 || geteuid() == 0) { - error("skhd: running as root is not allowed! abort..\n"); + require("skhd: running as root is not allowed! abort..\n"); } if (parse_arguments(argc, argv)) { @@ -475,7 +475,7 @@ int main(int argc, char **argv) } if (!check_privileges()) { - error("skhd: must be run with accessibility access! abort..\n"); + require("skhd: must be run with accessibility access! abort..\n"); } if (!initialize_keycode_map()) { -- cgit v1.2.3