/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* dbus-bash-completion-helper.c Bash Completion helper routines
*
* Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <glib.h>
#include "dbus-gparser.h"
static void
print_services (DBusConnection *connection)
{
DBusMessage *message;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter;
DBusMessageIter iter_array;
const char *name;
/* list both active and activatable names (the shell will sort and
* uniquify them) - also avoid names that are not well-known
* (e.g. :1.42).
*/
message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"ListNames");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_recurse (&iter, &iter_array);
while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
dbus_message_iter_get_basic (&iter_array, &name);
if (name[0] != ':')
printf ("%s \n", name);
dbus_message_iter_next (&iter_array);
}
dbus_message_unref (reply);
message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"ListActivatableNames");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_recurse (&iter, &iter_array);
while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
dbus_message_iter_get_basic (&iter_array, &name);
printf ("%s \n", name);
dbus_message_iter_next (&iter_array);
}
dbus_message_unref (reply);
fail:
dbus_error_free(&error);
}
static gboolean
have_option (char **tokens, const char *option)
{
int n;
for (n = 0; tokens[n] != NULL; n++)
if (strcmp (tokens[n], option) == 0)
return TRUE;
return FALSE;
}
static gboolean
have_option_with_value (char **tokens, const char *option, const char **value)
{
int n;
for (n = 0; tokens[n] != NULL; n++)
{
if (g_str_has_prefix (tokens[n], option))
{
if (strlen (tokens[n]) > strlen (option))
*value = tokens[n] + strlen (option);
return TRUE;
}
}
return FALSE;
}
static void
print_objects (DBusConnection *connection, const char *service_name, const char *cur)
{
DBusMessage *message;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter;
const char *introspection_xml;
NodeInfo *root;
GSList *nodes;
GSList *l;
if (cur == NULL)
cur = "/";
message = dbus_message_new_method_call (service_name,
cur,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_get_basic (&iter, &introspection_xml);
root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL);
nodes = node_info_get_nodes (root);
if (g_slist_length (node_info_get_interfaces (root)) > 0)
printf ("%s \n", cur);
for (l = nodes; l != NULL; l = l->next)
{
NodeInfo *node = (NodeInfo *) l->data;
const char *name;
char *new_path;
name = node_info_get_name (node);
if (strcmp (cur, "/") == 0)
new_path = g_strdup_printf ("/%s", name);
else
new_path = g_strdup_printf ("%s/%s", cur, name);
print_objects (connection, service_name, new_path);
g_free (new_path);
}
node_info_unref (root);
dbus_message_unref (reply);
fail:
dbus_error_free(&error);
}
static gboolean
is_object_path_with_interfaces (DBusConnection *connection, const char *service_name, const char *object_path)
{
DBusMessage *message;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter;
const char *introspection_xml;
NodeInfo *root;
gboolean ret;
ret = FALSE;
message = dbus_message_new_method_call (service_name,
object_path,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_get_basic (&iter, &introspection_xml);
root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL);
if (g_slist_length (node_info_get_interfaces (root)) > 0)
ret = TRUE;
node_info_unref (root);
dbus_message_unref (reply);
fail:
dbus_error_free(&error);
return ret;
}
static void
print_methods (DBusConnection *connection, const char *service_name, const char *object_path)
{
DBusMessage *message;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter;
const char *introspection_xml;
NodeInfo *root;
GSList *interfaces;
GSList *l;
message = dbus_message_new_method_call (service_name,
object_path,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_get_basic (&iter, &introspection_xml);
root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL);
interfaces = node_info_get_interfaces (root);
for (l = interfaces; l != NULL; l = l->next)
{
InterfaceInfo *interface = (InterfaceInfo *) l->data;
GSList *methods;
GSList *ll;
methods = interface_info_get_methods (interface);
for (ll = methods; ll != NULL; ll = ll->next)
{
MethodInfo *method = (MethodInfo *) ll->data;
printf ("%s.%s \n", interface_info_get_name (interface), method_info_get_name (method));
}
}
node_info_unref (root);
dbus_message_unref (reply);
fail:
dbus_error_free(&error);
}
static void
print_signature (DBusConnection *connection, const char *service_name, const char *object_path, const char *method)
{
DBusMessage *message;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter;
const char *introspection_xml;
NodeInfo *root;
GSList *interfaces;
GSList *l;
char *s;
char *method_name;
char *interface_name;
int n;
method_name = NULL;
interface_name = NULL;
s = strrchr (method, '.');
if (s == NULL || strlen (s) < 2 || s - method < 1)
goto fail;
method_name = g_strdup (s + 1);
interface_name = g_strndup (method, s - method);
printf (" \n");
message = dbus_message_new_method_call (service_name,
object_path,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect");
dbus_error_init (&error);
reply = dbus_connection_send_with_reply_and_block (connection,
message,
-1,
&error);
dbus_message_unref (message);
if (reply == NULL) {
goto fail;
}
dbus_message_iter_init (reply, &iter);
dbus_message_iter_get_basic (&iter, &introspection_xml);
root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL);
interfaces = node_info_get_interfaces (root);
for (l = interfaces; l != NULL; l = l->next)
{
InterfaceInfo *interface = (InterfaceInfo *) l->data;
if (strcmp (interface_name, interface_info_get_name (interface)) == 0)
{
GSList *methods;
GSList *ll;
methods = interface_info_get_methods (interface);
for (ll = methods; ll != NULL; ll = ll->next)
{
MethodInfo *method = (MethodInfo *) ll->data;
if (strcmp (method_name, method_info_get_name (method)) == 0)
{
GSList *args;
GSList *lll;
args = method_info_get_args (method);
for (lll = args, n = 0; lll != NULL; lll = lll->next, n++)
{
ArgInfo *arg = (ArgInfo *) lll->data;
printf ("# %s: arg %d: %s (%s) \n",
arg_info_get_direction (arg) == ARG_IN ? " IN" : "OUT",
n,
arg_info_get_name (arg),
arg_info_get_type (arg));
}
break;
}
}
}
}
node_info_unref (root);
dbus_message_unref (reply);
fail:
dbus_error_free(&error);
g_free (method_name);
g_free (interface_name);
}
static int
complete_dbus_send (char *str)
{
int ret;
char **tokens;
int num_tokens;
const char *cur;
gboolean have_system;
gboolean have_session;
gboolean have_print_reply;
gboolean have_dest;
DBusConnection *connection;
DBusBusType bus_type;
DBusError error;
const char *target_service;
const char *object_path;
const char *method;
int n;
int object_path_index;
ret = 1;
connection = NULL;
target_service = NULL;
tokens = g_strsplit (str, " ", 0);
num_tokens = g_strv_length (tokens);
if (num_tokens >= 1) {
cur = tokens[num_tokens - 1];
} else {
cur = "";
}
have_system = have_option (tokens, "--system");
have_session = have_option (tokens, "--session");
have_print_reply = have_option (tokens, "--print-reply");
have_dest = have_option_with_value (tokens, "--dest=", &target_service);
if (!have_print_reply)
printf ("--print-reply \n");
if (!have_system && !have_session)
{
printf ("--system \n");
printf ("--session \n");
goto done;
}
if (!have_dest && !g_str_has_prefix (cur, "--dest="))
{
printf ("--dest=\n");
goto done;
}
if (have_system || have_session)
{
bus_type = have_system ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION;
dbus_error_init (&error);
connection = dbus_bus_get (bus_type, &error);
if (connection == NULL)
{
fprintf (stderr, "Failed to open connection to %s message bus: %s: %s\n",
(bus_type == DBUS_BUS_SYSTEM) ? "system" : "session",
error.name, error.message);
dbus_error_free (&error);
goto fail;
}
}
if (connection != NULL && g_str_has_prefix (cur, "--dest="))
{
print_services (connection);
goto done;
}
/* see if we have an object path */
object_path = NULL;
object_path_index = 0;
if (connection != NULL && target_service != NULL)
{
for (n = 0; tokens[n] != NULL; n++)
{
if (tokens[n] == cur)
continue;
if (*(tokens[n]) == '/')
{
if (is_object_path_with_interfaces (connection, target_service, tokens[n]))
{
object_path = tokens[n];
object_path_index = n;
}
}
}
}
/* if we have a connection and a destination but no object path, go ahead and list the object paths */
if (connection != NULL && target_service != NULL && object_path == NULL)
{
print_objects (connection, target_service, NULL);
goto done;
}
/* see if we have a method; it's directly after the object_path */
method = NULL;
if (connection != NULL && target_service != NULL && object_path != NULL)
{
if ((object_path_index + 1 < num_tokens - 1) &&
(strlen (tokens[object_path_index + 1]) > 0) &&
!(strcmp (cur, tokens[object_path_index + 1]) == 0))
method = tokens[object_path_index + 1];
}
/* if we have connection, destination and object path but no method yet, list the methods */
if (connection != NULL && target_service != NULL && object_path != NULL && method == NULL)
{
print_methods (connection, target_service, object_path);
goto done;
}
/* print signature as comment */
if (connection != NULL && target_service != NULL && object_path != NULL && method != NULL)
{
print_signature (connection, target_service, object_path, method);
}
done:
ret = 0;
fail:
g_strfreev (tokens);
if (connection != NULL)
dbus_connection_unref (connection);
return ret;
}
int
main (int argc, char *argv[])
{
int ret;
char *cur;
gboolean dbus_send;
ret = 1;
dbus_send = FALSE;
if (argc != 3)
{
fprintf (stderr, "invalid use\n");
goto out;
}
if (strcmp (argv[1], "dbus-send") == 0)
{
dbus_send = TRUE;
}
else
{
fprintf (stderr, "unknown program '%s'\n", argv[1]);
goto out;
}
if (strlen (argv[2]) < strlen (argv[1]) + 1)
{
fprintf (stderr, "error");
goto out;
}
cur = argv[2] + strlen (argv[1]) + 1;
if (dbus_send)
ret = complete_dbus_send (cur);
out:
return ret;
}