From 30465af656a10c124e7fa9fb33f7ad8454e9af2e Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 25 Sep 2024 18:42:59 -0700 Subject: [PATCH] busctl: add wait verb to wait for signals It's like busctl call, but it waits for a signal rather than a reply to a method call. --- man/busctl.xml | 10 ++++ shell-completion/zsh/_busctl | 26 +++++++++ src/busctl/busctl.c | 101 +++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/man/busctl.xml b/man/busctl.xml index c313d29955a..5cf6058dc3e 100644 --- a/man/busctl.xml +++ b/man/busctl.xml @@ -144,6 +144,16 @@ + + wait SERVICE OBJECT INTERFACE SIGNAL + + Wait for a signal. Takes an object path, interface name, and signal name. To suppress + output of the returned data, use the option. The service name may be + omitted, in which case busctl will match signals from any sender. + + + + get-property SERVICE OBJECT INTERFACE PROPERTY diff --git a/shell-completion/zsh/_busctl b/shell-completion/zsh/_busctl index 0018cf66223..5e16d0f1a14 100644 --- a/shell-completion/zsh/_busctl +++ b/shell-completion/zsh/_busctl @@ -29,6 +29,7 @@ "tree:Show object tree of service" "introspect:Introspect object" "call:Call a method" + "wait:Wait for a signal" "get-property:Get property value" "set-property:Set property value" ) @@ -201,6 +202,31 @@ __dbus_matchspec() { esac } +(( $+functions[_busctl_wait] )) || _busctl_wait() +{ + local expl + case $CURRENT in + 2) + _wanted busname expl 'busname' \ + compadd "$@" - $(_busctl_get_service_names) + ;; + 3) + _wanted path expl 'path' \ + compadd "$@" - $(_busctl_get_objects $words[2]) + ;; + 4) + _wanted interface expl 'interface' \ + compadd "$@" - $(_busctl_get_interfaces $words[2,3]) + ;; + 5) + _wanted method expl 'method' \ + compadd "$@" - $(_busctl_get_members $words[2,4] "signal") + ;; + *) + _message "no more options" + esac +} + (( $+functions[_busctl_get-property] )) || _busctl_get-property() { local expl diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index beff530b924..365ac44b499 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -2281,6 +2281,104 @@ static int get_property(int argc, char **argv, void *userdata) { return 0; } +static int on_bus_signal_impl(sd_bus_message *msg) { + int r; + + assert(msg); + + r = sd_bus_message_is_empty(msg); + if (r < 0) + return bus_log_parse_error(r); + + if (r > 0 || arg_quiet) + return 0; + + if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + r = json_transform_message(msg, &v); + if (r < 0) + return r; + + sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL); + + } else if (arg_verbose) { + pager_open(arg_pager_flags); + + r = sd_bus_message_dump(msg, stdout, 0); + if (r < 0) + return log_error_errno(r, "Failed to dump dbus message: %m\n"); + } else { + + fputs(sd_bus_message_get_signature(msg, true), stdout); + fputc(' ', stdout); + + r = format_cmdline(msg, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + + return 0; +} + +static int on_bus_signal(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + sd_event *e = sd_bus_get_event(sd_bus_message_get_bus(ASSERT_PTR(msg))); + return sd_event_exit(e, on_bus_signal_impl(msg)); +} + +static int wait_signal(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + int argn = 1, r; + + const char *sender = argc == 5 ? argv[argn++] : NULL; + const char *path = argv[argn++]; + const char *interface = argv[argn++]; + const char *member = argv[argn++]; + + if (sender && !service_name_is_valid(sender)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid service name: %s", sender); + if (!object_path_is_valid(path)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid object path: %s", path); + if (!interface_name_is_valid(interface)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid interface name: %s", interface); + if (!member_name_is_valid(member)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid member name: %s", member); + + r = acquire_bus(/* set_monitor= */ false, &bus); + if (r < 0) + return r; + + r = sd_bus_match_signal(bus, NULL, sender, path, interface, member, on_bus_signal, NULL); + if (r < 0) + return log_error_errno(r, "Failed to match signal %s on interface %s: %m", member, interface); + + r = sd_event_new(&e); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m\n"); + + r = sd_bus_attach_event(bus, e, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach bus event: %m\n"); + + if (arg_timeout) { + r = sd_event_add_time_relative(e, &timer, CLOCK_MONOTONIC, arg_timeout, 0, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to schedule timeout: %m\n"); + } + + /* The match is installed and we're ready to observe the signal */ + sd_notify(/* unset_environment= */ false, "READY=1"); + + return sd_event_loop(e); +} + static int set_property(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; @@ -2352,6 +2450,8 @@ static int help(void) { " Call a method\n" " emit OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT...]]\n" " Emit a signal\n" + " wait OBJECT INTERFACE SIGNAL\n" + " Wait for a signal\n" " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" " Get property value\n" " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" @@ -2664,6 +2764,7 @@ static int busctl_main(int argc, char *argv[]) { { "introspect", 3, 4, 0, introspect }, { "call", 5, VERB_ANY, 0, call }, { "emit", 4, VERB_ANY, 0, emit_signal }, + { "wait", 4, 5, 0, wait_signal }, { "get-property", 5, VERB_ANY, 0, get_property }, { "set-property", 6, VERB_ANY, 0, set_property }, { "help", VERB_ANY, VERB_ANY, 0, verb_help }, -- 2.47.3