586 lines
16 KiB
Diff
586 lines
16 KiB
Diff
From: Daniel Mack <daniel@zonque.org>
|
|
Date: Thu, 11 Sep 2014 19:00:43 +0200
|
|
Subject: [PATCH] kdbus: add policy database implementation
|
|
|
|
This patch adds the policy database implementation.
|
|
|
|
A policy database restricts the possibilities of connections to own,
|
|
see and talk to well-known names. It can be associated with a bus
|
|
(through a policy holder connection) or a custom endpoint.
|
|
|
|
By default, buses have an empty policy database that is augmented on
|
|
demand when a policy holder connection is instantiated.
|
|
|
|
Policies are set through KDBUS_CMD_HELLO (when creating a policy
|
|
holder connection), KDBUS_CMD_CONN_UPDATE (when updating a policy
|
|
holder connection), KDBUS_CMD_EP_MAKE (creating a custom endpoint)
|
|
or KDBUS_CMD_EP_UPDATE (updating a custom endpoint). In all cases,
|
|
the name and policy access information is stored in items of type
|
|
KDBUS_ITEM_NAME and KDBUS_ITEM_POLICY_ACCESS.
|
|
|
|
See kdbus.policy(7) for more details.
|
|
|
|
Signed-off-by: Daniel Mack <daniel@zonque.org>
|
|
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
|
|
Signed-off-by: Djalal Harouni <tixxdz@opendz.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
ipc/kdbus/policy.c | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
ipc/kdbus/policy.h | 51 ++++++
|
|
2 files changed, 540 insertions(+)
|
|
create mode 100644 ipc/kdbus/policy.c
|
|
create mode 100644 ipc/kdbus/policy.h
|
|
|
|
diff --git a/ipc/kdbus/policy.c b/ipc/kdbus/policy.c
|
|
new file mode 100644
|
|
index 000000000000..dd7fffaafa84
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/policy.c
|
|
@@ -0,0 +1,489 @@
|
|
+/*
|
|
+ * Copyright (C) 2013-2015 Kay Sievers
|
|
+ * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
+ * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
|
|
+ * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
|
|
+ * Copyright (C) 2013-2015 Linux Foundation
|
|
+ * Copyright (C) 2014-2015 Djalal Harouni
|
|
+ *
|
|
+ * kdbus 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.
|
|
+ */
|
|
+
|
|
+#include <linux/dcache.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uaccess.h>
|
|
+
|
|
+#include "bus.h"
|
|
+#include "connection.h"
|
|
+#include "domain.h"
|
|
+#include "item.h"
|
|
+#include "names.h"
|
|
+#include "policy.h"
|
|
+
|
|
+#define KDBUS_POLICY_HASH_SIZE 64
|
|
+
|
|
+/**
|
|
+ * struct kdbus_policy_db_entry_access - a database entry access item
|
|
+ * @type: One of KDBUS_POLICY_ACCESS_* types
|
|
+ * @access: Access to grant. One of KDBUS_POLICY_*
|
|
+ * @uid: For KDBUS_POLICY_ACCESS_USER, the global uid
|
|
+ * @gid: For KDBUS_POLICY_ACCESS_GROUP, the global gid
|
|
+ * @list: List entry item for the entry's list
|
|
+ *
|
|
+ * This is the internal version of struct kdbus_policy_db_access.
|
|
+ */
|
|
+struct kdbus_policy_db_entry_access {
|
|
+ u8 type; /* USER, GROUP, WORLD */
|
|
+ u8 access; /* OWN, TALK, SEE */
|
|
+ union {
|
|
+ kuid_t uid; /* global uid */
|
|
+ kgid_t gid; /* global gid */
|
|
+ };
|
|
+ struct list_head list;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct kdbus_policy_db_entry - a policy database entry
|
|
+ * @name: The name to match the policy entry against
|
|
+ * @hentry: The hash entry for the database's entries_hash
|
|
+ * @access_list: List head for keeping tracks of the entry's
|
|
+ * access items.
|
|
+ * @owner: The owner of this entry. Can be a kdbus_conn or
|
|
+ * a kdbus_ep object.
|
|
+ * @wildcard: The name is a wildcard, such as ending on '.*'
|
|
+ */
|
|
+struct kdbus_policy_db_entry {
|
|
+ char *name;
|
|
+ struct hlist_node hentry;
|
|
+ struct list_head access_list;
|
|
+ const void *owner;
|
|
+ bool wildcard:1;
|
|
+};
|
|
+
|
|
+static void kdbus_policy_entry_free(struct kdbus_policy_db_entry *e)
|
|
+{
|
|
+ struct kdbus_policy_db_entry_access *a, *tmp;
|
|
+
|
|
+ list_for_each_entry_safe(a, tmp, &e->access_list, list) {
|
|
+ list_del(&a->list);
|
|
+ kfree(a);
|
|
+ }
|
|
+
|
|
+ kfree(e->name);
|
|
+ kfree(e);
|
|
+}
|
|
+
|
|
+static unsigned int kdbus_strnhash(const char *str, size_t len)
|
|
+{
|
|
+ unsigned long hash = init_name_hash();
|
|
+
|
|
+ while (len--)
|
|
+ hash = partial_name_hash(*str++, hash);
|
|
+
|
|
+ return end_name_hash(hash);
|
|
+}
|
|
+
|
|
+static const struct kdbus_policy_db_entry *
|
|
+kdbus_policy_lookup(struct kdbus_policy_db *db, const char *name, u32 hash)
|
|
+{
|
|
+ struct kdbus_policy_db_entry *e;
|
|
+ const char *dot;
|
|
+ size_t len;
|
|
+
|
|
+ /* find exact match */
|
|
+ hash_for_each_possible(db->entries_hash, e, hentry, hash)
|
|
+ if (strcmp(e->name, name) == 0 && !e->wildcard)
|
|
+ return e;
|
|
+
|
|
+ /* find wildcard match */
|
|
+
|
|
+ dot = strrchr(name, '.');
|
|
+ if (!dot)
|
|
+ return NULL;
|
|
+
|
|
+ len = dot - name;
|
|
+ hash = kdbus_strnhash(name, len);
|
|
+
|
|
+ hash_for_each_possible(db->entries_hash, e, hentry, hash)
|
|
+ if (e->wildcard && !strncmp(e->name, name, len) &&
|
|
+ !e->name[len])
|
|
+ return e;
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_db_clear - release all memory from a policy db
|
|
+ * @db: The policy database
|
|
+ */
|
|
+void kdbus_policy_db_clear(struct kdbus_policy_db *db)
|
|
+{
|
|
+ struct kdbus_policy_db_entry *e;
|
|
+ struct hlist_node *tmp;
|
|
+ unsigned int i;
|
|
+
|
|
+ /* purge entries */
|
|
+ down_write(&db->entries_rwlock);
|
|
+ hash_for_each_safe(db->entries_hash, i, tmp, e, hentry) {
|
|
+ hash_del(&e->hentry);
|
|
+ kdbus_policy_entry_free(e);
|
|
+ }
|
|
+ up_write(&db->entries_rwlock);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_db_init() - initialize a new policy database
|
|
+ * @db: The location of the database
|
|
+ *
|
|
+ * This initializes a new policy-db. The underlying memory must have been
|
|
+ * cleared to zero by the caller.
|
|
+ */
|
|
+void kdbus_policy_db_init(struct kdbus_policy_db *db)
|
|
+{
|
|
+ hash_init(db->entries_hash);
|
|
+ init_rwsem(&db->entries_rwlock);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_query_unlocked() - Query the policy database
|
|
+ * @db: Policy database
|
|
+ * @cred: Credentials to test against
|
|
+ * @name: Name to query
|
|
+ * @hash: Hash value of @name
|
|
+ *
|
|
+ * Same as kdbus_policy_query() but requires the caller to lock the policy
|
|
+ * database against concurrent writes.
|
|
+ *
|
|
+ * Return: The highest KDBUS_POLICY_* access type found, or -EPERM if none.
|
|
+ */
|
|
+int kdbus_policy_query_unlocked(struct kdbus_policy_db *db,
|
|
+ const struct cred *cred, const char *name,
|
|
+ unsigned int hash)
|
|
+{
|
|
+ struct kdbus_policy_db_entry_access *a;
|
|
+ const struct kdbus_policy_db_entry *e;
|
|
+ int i, highest = -EPERM;
|
|
+
|
|
+ e = kdbus_policy_lookup(db, name, hash);
|
|
+ if (!e)
|
|
+ return -EPERM;
|
|
+
|
|
+ list_for_each_entry(a, &e->access_list, list) {
|
|
+ if ((int)a->access <= highest)
|
|
+ continue;
|
|
+
|
|
+ switch (a->type) {
|
|
+ case KDBUS_POLICY_ACCESS_USER:
|
|
+ if (uid_eq(cred->euid, a->uid))
|
|
+ highest = a->access;
|
|
+ break;
|
|
+ case KDBUS_POLICY_ACCESS_GROUP:
|
|
+ if (gid_eq(cred->egid, a->gid)) {
|
|
+ highest = a->access;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < cred->group_info->ngroups; i++) {
|
|
+ kgid_t gid = GROUP_AT(cred->group_info, i);
|
|
+
|
|
+ if (gid_eq(gid, a->gid)) {
|
|
+ highest = a->access;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ break;
|
|
+ case KDBUS_POLICY_ACCESS_WORLD:
|
|
+ highest = a->access;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* OWN is the highest possible policy */
|
|
+ if (highest >= KDBUS_POLICY_OWN)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return highest;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_query() - Query the policy database
|
|
+ * @db: Policy database
|
|
+ * @cred: Credentials to test against
|
|
+ * @name: Name to query
|
|
+ * @hash: Hash value of @name
|
|
+ *
|
|
+ * Query the policy database @db for the access rights of @cred to the name
|
|
+ * @name. The access rights of @cred are returned, or -EPERM if no access is
|
|
+ * granted.
|
|
+ *
|
|
+ * This call effectively searches for the highest access-right granted to
|
|
+ * @cred. The caller should really cache those as policy lookups are rather
|
|
+ * expensive.
|
|
+ *
|
|
+ * Return: The highest KDBUS_POLICY_* access type found, or -EPERM if none.
|
|
+ */
|
|
+int kdbus_policy_query(struct kdbus_policy_db *db, const struct cred *cred,
|
|
+ const char *name, unsigned int hash)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ down_read(&db->entries_rwlock);
|
|
+ ret = kdbus_policy_query_unlocked(db, cred, name, hash);
|
|
+ up_read(&db->entries_rwlock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __kdbus_policy_remove_owner(struct kdbus_policy_db *db,
|
|
+ const void *owner)
|
|
+{
|
|
+ struct kdbus_policy_db_entry *e;
|
|
+ struct hlist_node *tmp;
|
|
+ int i;
|
|
+
|
|
+ hash_for_each_safe(db->entries_hash, i, tmp, e, hentry)
|
|
+ if (e->owner == owner) {
|
|
+ hash_del(&e->hentry);
|
|
+ kdbus_policy_entry_free(e);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_remove_owner() - remove all entries related to a connection
|
|
+ * @db: The policy database
|
|
+ * @owner: The connection which items to remove
|
|
+ */
|
|
+void kdbus_policy_remove_owner(struct kdbus_policy_db *db,
|
|
+ const void *owner)
|
|
+{
|
|
+ down_write(&db->entries_rwlock);
|
|
+ __kdbus_policy_remove_owner(db, owner);
|
|
+ up_write(&db->entries_rwlock);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Convert user provided policy access to internal kdbus policy
|
|
+ * access
|
|
+ */
|
|
+static struct kdbus_policy_db_entry_access *
|
|
+kdbus_policy_make_access(const struct kdbus_policy_access *uaccess)
|
|
+{
|
|
+ int ret;
|
|
+ struct kdbus_policy_db_entry_access *a;
|
|
+
|
|
+ a = kzalloc(sizeof(*a), GFP_KERNEL);
|
|
+ if (!a)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ ret = -EINVAL;
|
|
+ switch (uaccess->access) {
|
|
+ case KDBUS_POLICY_SEE:
|
|
+ case KDBUS_POLICY_TALK:
|
|
+ case KDBUS_POLICY_OWN:
|
|
+ a->access = uaccess->access;
|
|
+ break;
|
|
+ default:
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ switch (uaccess->type) {
|
|
+ case KDBUS_POLICY_ACCESS_USER:
|
|
+ a->uid = make_kuid(current_user_ns(), uaccess->id);
|
|
+ if (!uid_valid(a->uid))
|
|
+ goto err;
|
|
+
|
|
+ break;
|
|
+ case KDBUS_POLICY_ACCESS_GROUP:
|
|
+ a->gid = make_kgid(current_user_ns(), uaccess->id);
|
|
+ if (!gid_valid(a->gid))
|
|
+ goto err;
|
|
+
|
|
+ break;
|
|
+ case KDBUS_POLICY_ACCESS_WORLD:
|
|
+ break;
|
|
+ default:
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ a->type = uaccess->type;
|
|
+
|
|
+ return a;
|
|
+
|
|
+err:
|
|
+ kfree(a);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_policy_set() - set a connection's policy rules
|
|
+ * @db: The policy database
|
|
+ * @items: A list of kdbus_item elements that contain both
|
|
+ * names and access rules to set.
|
|
+ * @items_size: The total size of the items.
|
|
+ * @max_policies: The maximum number of policy entries to allow.
|
|
+ * Pass 0 for no limit.
|
|
+ * @allow_wildcards: Boolean value whether wildcard entries (such
|
|
+ * ending on '.*') should be allowed.
|
|
+ * @owner: The owner of the new policy items.
|
|
+ *
|
|
+ * This function sets a new set of policies for a given owner. The names and
|
|
+ * access rules are gathered by walking the list of items passed in as
|
|
+ * argument. An item of type KDBUS_ITEM_NAME is expected before any number of
|
|
+ * KDBUS_ITEM_POLICY_ACCESS items. If there are more repetitions of this
|
|
+ * pattern than denoted in @max_policies, -EINVAL is returned.
|
|
+ *
|
|
+ * In order to allow atomic replacement of rules, the function first removes
|
|
+ * all entries that have been created for the given owner previously.
|
|
+ *
|
|
+ * Callers to this function must make sur that the owner is a custom
|
|
+ * endpoint, or if the endpoint is a default endpoint, then it must be
|
|
+ * either a policy holder or an activator.
|
|
+ *
|
|
+ * Return: 0 on success, negative errno on failure.
|
|
+ */
|
|
+int kdbus_policy_set(struct kdbus_policy_db *db,
|
|
+ const struct kdbus_item *items,
|
|
+ size_t items_size,
|
|
+ size_t max_policies,
|
|
+ bool allow_wildcards,
|
|
+ const void *owner)
|
|
+{
|
|
+ struct kdbus_policy_db_entry_access *a;
|
|
+ struct kdbus_policy_db_entry *e, *p;
|
|
+ const struct kdbus_item *item;
|
|
+ struct hlist_node *tmp;
|
|
+ HLIST_HEAD(entries);
|
|
+ HLIST_HEAD(restore);
|
|
+ size_t count = 0;
|
|
+ int i, ret = 0;
|
|
+ u32 hash;
|
|
+
|
|
+ /* Walk the list of items and look for new policies */
|
|
+ e = NULL;
|
|
+ KDBUS_ITEMS_FOREACH(item, items, items_size) {
|
|
+ switch (item->type) {
|
|
+ case KDBUS_ITEM_NAME: {
|
|
+ size_t len;
|
|
+
|
|
+ if (max_policies && ++count > max_policies) {
|
|
+ ret = -E2BIG;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (!kdbus_name_is_valid(item->str, true)) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ e = kzalloc(sizeof(*e), GFP_KERNEL);
|
|
+ if (!e) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ INIT_LIST_HEAD(&e->access_list);
|
|
+ e->owner = owner;
|
|
+ hlist_add_head(&e->hentry, &entries);
|
|
+
|
|
+ e->name = kstrdup(item->str, GFP_KERNEL);
|
|
+ if (!e->name) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If a supplied name ends with an '.*', cut off that
|
|
+ * part, only store anything before it, and mark the
|
|
+ * entry as wildcard.
|
|
+ */
|
|
+ len = strlen(e->name);
|
|
+ if (len > 2 &&
|
|
+ e->name[len - 3] == '.' &&
|
|
+ e->name[len - 2] == '*') {
|
|
+ if (!allow_wildcards) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ e->name[len - 3] = '\0';
|
|
+ e->wildcard = true;
|
|
+ }
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ case KDBUS_ITEM_POLICY_ACCESS:
|
|
+ if (!e) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ a = kdbus_policy_make_access(&item->policy_access);
|
|
+ if (IS_ERR(a)) {
|
|
+ ret = PTR_ERR(a);
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ list_add_tail(&a->list, &e->access_list);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ down_write(&db->entries_rwlock);
|
|
+
|
|
+ /* remember previous entries to restore in case of failure */
|
|
+ hash_for_each_safe(db->entries_hash, i, tmp, e, hentry)
|
|
+ if (e->owner == owner) {
|
|
+ hash_del(&e->hentry);
|
|
+ hlist_add_head(&e->hentry, &restore);
|
|
+ }
|
|
+
|
|
+ hlist_for_each_entry_safe(e, tmp, &entries, hentry) {
|
|
+ /* prevent duplicates */
|
|
+ hash = kdbus_strhash(e->name);
|
|
+ hash_for_each_possible(db->entries_hash, p, hentry, hash)
|
|
+ if (strcmp(e->name, p->name) == 0 &&
|
|
+ e->wildcard == p->wildcard) {
|
|
+ ret = -EEXIST;
|
|
+ goto restore;
|
|
+ }
|
|
+
|
|
+ hlist_del(&e->hentry);
|
|
+ hash_add(db->entries_hash, &e->hentry, hash);
|
|
+ }
|
|
+
|
|
+restore:
|
|
+ /* if we failed, flush all entries we added so far */
|
|
+ if (ret < 0)
|
|
+ __kdbus_policy_remove_owner(db, owner);
|
|
+
|
|
+ /* if we failed, restore entries, otherwise release them */
|
|
+ hlist_for_each_entry_safe(e, tmp, &restore, hentry) {
|
|
+ hlist_del(&e->hentry);
|
|
+ if (ret < 0) {
|
|
+ hash = kdbus_strhash(e->name);
|
|
+ hash_add(db->entries_hash, &e->hentry, hash);
|
|
+ } else {
|
|
+ kdbus_policy_entry_free(e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ up_write(&db->entries_rwlock);
|
|
+
|
|
+exit:
|
|
+ hlist_for_each_entry_safe(e, tmp, &entries, hentry) {
|
|
+ hlist_del(&e->hentry);
|
|
+ kdbus_policy_entry_free(e);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
diff --git a/ipc/kdbus/policy.h b/ipc/kdbus/policy.h
|
|
new file mode 100644
|
|
index 000000000000..15dd7bc12068
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/policy.h
|
|
@@ -0,0 +1,51 @@
|
|
+/*
|
|
+ * Copyright (C) 2013-2015 Kay Sievers
|
|
+ * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
+ * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
|
|
+ * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
|
|
+ * Copyright (C) 2013-2015 Linux Foundation
|
|
+ *
|
|
+ * kdbus 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_POLICY_H
|
|
+#define __KDBUS_POLICY_H
|
|
+
|
|
+#include <linux/hashtable.h>
|
|
+#include <linux/rwsem.h>
|
|
+
|
|
+struct kdbus_conn;
|
|
+struct kdbus_item;
|
|
+
|
|
+/**
|
|
+ * struct kdbus_policy_db - policy database
|
|
+ * @entries_hash: Hashtable of entries
|
|
+ * @entries_rwlock: Mutex to protect the database's access entries
|
|
+ */
|
|
+struct kdbus_policy_db {
|
|
+ DECLARE_HASHTABLE(entries_hash, 6);
|
|
+ struct rw_semaphore entries_rwlock;
|
|
+};
|
|
+
|
|
+void kdbus_policy_db_init(struct kdbus_policy_db *db);
|
|
+void kdbus_policy_db_clear(struct kdbus_policy_db *db);
|
|
+
|
|
+int kdbus_policy_query_unlocked(struct kdbus_policy_db *db,
|
|
+ const struct cred *cred, const char *name,
|
|
+ unsigned int hash);
|
|
+int kdbus_policy_query(struct kdbus_policy_db *db, const struct cred *cred,
|
|
+ const char *name, unsigned int hash);
|
|
+
|
|
+void kdbus_policy_remove_owner(struct kdbus_policy_db *db,
|
|
+ const void *owner);
|
|
+int kdbus_policy_set(struct kdbus_policy_db *db,
|
|
+ const struct kdbus_item *items,
|
|
+ size_t items_size,
|
|
+ size_t max_policies,
|
|
+ bool allow_wildcards,
|
|
+ const void *owner);
|
|
+
|
|
+#endif
|