]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
busctl: add wait verb to wait for signals
authorRonan Pigott <ronan@rjp.ie>
Thu, 26 Sep 2024 01:42:59 +0000 (18:42 -0700)
committerRonan Pigott <ronan@rjp.ie>
Sun, 13 Oct 2024 02:30:56 +0000 (19:30 -0700)
It's like busctl call, but it waits for a signal rather than a reply to
a method call.

man/busctl.xml
shell-completion/zsh/_busctl
src/busctl/busctl.c

index c313d29955a43dc03dfbb633fff27d2b1fc6c5c4..5cf6058dc3e2dcd36102394f1fe370620d0e8a9d 100644 (file)
         <xi:include href="version-info.xml" xpointer="v242"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><command>wait</command> <arg choice="opt"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>SIGNAL</replaceable></arg></term>
+
+        <listitem><para>Wait for a signal. Takes an object path, interface name, and signal name. To suppress
+        output of the returned data, use the <option>--quiet</option> option. The service name may be
+        omitted, in which case busctl will match signals from any sender.</para>
+
+        <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>PROPERTY</replaceable></arg></term>
 
index 0018cf662233a4c93010ac486496d0db53a5fa7b..5e16d0f1a1450394b4ee2e8c4aa67e9d1ed51f50 100644 (file)
@@ -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
index beff530b924cb4a187c297f736c6195d2450b891..365ac44b4993ddb22bc5ef59cdd379b03ba7cafe 100644 (file)
@@ -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      },