Blame ell/dbus-filter.c

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