blob: fd61b7cb29219f54a5ce5d7939f877e2f7946ce8 [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2016 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include "util.h"
#include "queue.h"
#include "hashmap.h"
#include "string.h"
#include "dbus.h"
#include "dbus-private.h"
#include "gvariant-private.h"
#include "private.h"
#include "useful.h"
#define NODE_TYPE_CALLBACK L_DBUS_MATCH_NONE
struct filter_node {
enum l_dbus_match_type type;
union {
struct {
char *value;
struct filter_node *children;
bool remote_rule;
} match;
struct {
l_dbus_message_func_t func;
void *user_data;
} callback;
};
unsigned int id;
struct filter_node *next;
};
struct _dbus_filter {
struct l_dbus *dbus;
struct filter_node *root;
unsigned int signal_id;
unsigned int last_id;
const struct _dbus_filter_ops *driver;
struct _dbus_name_cache *name_cache;
};
static void filter_subtree_free(struct filter_node *node)
{
struct filter_node *child, *next;
if (node->type == NODE_TYPE_CALLBACK) {
l_free(node);
return;
}
next = node->match.children;
l_free(node->match.value);
l_free(node);
while (next) {
child = next;
next = child->next;
filter_subtree_free(child);
}
}
static void dbus_filter_destroy(void *data)
{
struct _dbus_filter *filter = data;
if (filter->root)
filter_subtree_free(filter->root);
l_free(filter);
}
static void filter_dispatch_match_recurse(struct _dbus_filter *filter,
struct filter_node *node,
struct l_dbus_message *message)
{
const char *value = NULL;
const char *alt_value = NULL;
struct filter_node *child;
switch ((int) node->type) {
case NODE_TYPE_CALLBACK:
node->callback.func(message, node->callback.user_data);
return;
case L_DBUS_MATCH_SENDER:
value = l_dbus_message_get_sender(message);
break;
case L_DBUS_MATCH_TYPE:
value = _dbus_message_get_type_as_string(message);
break;
case L_DBUS_MATCH_PATH:
value = l_dbus_message_get_path(message);
break;
case L_DBUS_MATCH_INTERFACE:
value = l_dbus_message_get_interface(message);
break;
case L_DBUS_MATCH_MEMBER:
value = l_dbus_message_get_member(message);
break;
case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63):
value = _dbus_message_get_nth_string_argument(message,
node->type - L_DBUS_MATCH_ARG0);
break;
}
if (!value)
return;
if (node->type == L_DBUS_MATCH_SENDER && filter->name_cache)
alt_value = _dbus_name_cache_lookup(filter->name_cache,
node->match.value);
if (strcmp(value, node->match.value) &&
(!alt_value || strcmp(value, alt_value)))
return;
for (child = node->match.children; child; child = child->next)
filter_dispatch_match_recurse(filter, child, message);
}
void _dbus_filter_dispatch(struct l_dbus_message *message, void *user_data)
{
struct _dbus_filter *filter = user_data;
filter_dispatch_match_recurse(filter, filter->root, message);
}
struct _dbus_filter *_dbus_filter_new(struct l_dbus *dbus,
const struct _dbus_filter_ops *driver,
struct _dbus_name_cache *name_cache)
{
struct _dbus_filter *filter;
filter = l_new(struct _dbus_filter, 1);
filter->dbus = dbus;
filter->driver = driver;
filter->name_cache = name_cache;
if (!filter->driver->skip_register)
filter->signal_id = l_dbus_register(dbus, _dbus_filter_dispatch,
filter,
dbus_filter_destroy);
return filter;
}
void _dbus_filter_free(struct _dbus_filter *filter)
{
if (!filter)
return;
if (!filter->driver->skip_register)
l_dbus_unregister(filter->dbus, filter->signal_id);
else
dbus_filter_destroy(filter);
}
static int condition_compare(const void *a, const void *b)
{
const struct _dbus_filter_condition *condition_a = a, *condition_b = b;
return condition_a->type - condition_b->type;
}
static bool remove_recurse(struct _dbus_filter *filter,
struct filter_node **node, unsigned int id)
{
struct filter_node *tmp;
for (; *node; node = &(*node)->next) {
if ((*node)->type == NODE_TYPE_CALLBACK && (*node)->id == id)
break;
if ((*node)->type != NODE_TYPE_CALLBACK &&
remove_recurse(filter, &(*node)->match.children,
id))
break;
}
if (!*node)
return false;
if ((*node)->type == NODE_TYPE_CALLBACK || !(*node)->match.children) {
tmp = *node;
*node = tmp->next;
if (tmp->match.remote_rule)
filter->driver->remove_match(filter->dbus, tmp->id);
if (tmp->type == L_DBUS_MATCH_SENDER && filter->name_cache &&
!_dbus_parse_unique_name(tmp->match.value,
NULL))
_dbus_name_cache_remove(filter->name_cache,
tmp->match.value);
filter_subtree_free(tmp);
}
return true;
}
unsigned int _dbus_filter_add_rule(struct _dbus_filter *filter,
const struct _dbus_filter_condition *rule,
int rule_len,
l_dbus_message_func_t signal_func,
void *user_data)
{
struct filter_node **node_ptr = &filter->root;
struct filter_node *node;
struct filter_node *parent = filter->root;
bool remote_rule = false;
struct _dbus_filter_condition sorted[rule_len];
struct _dbus_filter_condition *unused;
struct _dbus_filter_condition *condition;
struct _dbus_filter_condition *end = sorted + rule_len;
memcpy(sorted, rule, sizeof(sorted));
qsort(sorted, rule_len, sizeof(*condition), condition_compare);
/*
* Find or create a path in the tree with a node for each
* condition in the rule, loop until all conditions have been
* used.
*/
unused = sorted;
while (unused < end) {
/*
* Find a child of the node that matches any unused
* condition. Note there could be multiple matches, we're
* happy with the first we can find.
*/
while (*node_ptr) {
node = *node_ptr;
for (condition = unused; condition < end; condition++) {
if (condition->type > node->type) {
condition = end;
break;
}
if (condition->type < node->type ||
condition->type ==
L_DBUS_MATCH_NONE)
continue;
if (!strcmp(node->match.value,
condition->value))
break;
}
if (condition < end)
break;
node_ptr = &node->next;
}
/* Add a node */
if (!*node_ptr) {
condition = unused;
node = l_new(struct filter_node, 1);
node->type = condition->type;
node->match.value = l_strdup(condition->value);
*node_ptr = node;
if (node->type == L_DBUS_MATCH_SENDER &&
filter->name_cache &&
!_dbus_parse_unique_name(
node->match.value,
NULL))
_dbus_name_cache_add(filter->name_cache,
node->match.value);
}
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
/*
* Mark the condition used. We do this by setting
* condition->type to an invalid value unless it is the
* first condition left in which case we can push the
* rule start. Another option is to always push the rule
* start and memmove the still unused conditions by one
* if necessary.
*/
condition->type = L_DBUS_MATCH_NONE;
while (unused < end && unused[0].type == L_DBUS_MATCH_NONE)
unused++;
node_ptr = &node->match.children;
parent = node;
/*
* Only have to call AddMatch if none of the parent nodes
* have yet created an AddMatch rule on the server.
*/
remote_rule |= node->match.remote_rule;
_Pragma("GCC diagnostic pop")
}
node = l_new(struct filter_node, 1);
node->type = NODE_TYPE_CALLBACK;
node->callback.func = signal_func;
node->callback.user_data = user_data;
node->id = ++filter->last_id;
node->next = *node_ptr;
*node_ptr = node;
if (!remote_rule) {
if (!filter->driver->add_match(filter->dbus, node->id,
rule, rule_len))
goto err;
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
parent->id = node->id;
parent->match.remote_rule = true;
_Pragma("GCC diagnostic pop")
}
return node->id;
err:
/* Remove all the nodes we may have added */
node->id = (unsigned int) -1;
remove_recurse(filter, &filter->root, node->id);
return 0;
}
bool _dbus_filter_remove_rule(struct _dbus_filter *filter, unsigned int id)
{
return remove_recurse(filter, &filter->root, id);
}
char *_dbus_filter_rule_to_str(const struct _dbus_filter_condition *rule,
int rule_len)
{
struct l_string *str = l_string_new(63);
char *key, arg_buf[6];
const char *value, *endp;
for (; rule_len; rule++, rule_len--) {
switch ((int) rule->type) {
case L_DBUS_MATCH_SENDER:
key = "sender";
break;
case L_DBUS_MATCH_TYPE:
key = "type";
break;
case L_DBUS_MATCH_PATH:
key = "path";
break;
case L_DBUS_MATCH_INTERFACE:
key = "interface";
break;
case L_DBUS_MATCH_MEMBER:
key = "member";
break;
case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63):
key = arg_buf;
snprintf(arg_buf, sizeof(arg_buf), "arg%i",
rule->type - L_DBUS_MATCH_ARG0);
break;
default:
l_string_free(str);
return NULL;
}
l_string_append(str, key);
l_string_append(str, "='");
/* We only need to escape single-quotes in the values */
value = rule->value;
while ((endp = strchr(value, '\''))) {
l_string_append_fixed(str, value, endp - value);
l_string_append(str, "'\\''");
value = endp + 1;
}
l_string_append(str, value);
l_string_append_c(str, '\'');
if (rule_len > 1)
l_string_append_c(str, ',');
}
return l_string_unwrap(str);
}