diff --git a/Makefile.am b/Makefile.am index 336fbd5a7a..98b717021e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -415,6 +415,7 @@ libnm_core_lib_h_pub_real = \ libnm_core_lib_h_pub_mkenums = \ libnm-core/nm-core-enum-types.h libnm_core_lib_h_priv = \ + shared/nm-utils/nm-dedup-multi.h \ shared/nm-utils/nm-enum-utils.h \ shared/nm-utils/nm-shared-utils.h \ shared/nm-utils/nm-udev-utils.h \ @@ -429,6 +430,7 @@ libnm_core_lib_h_priv = \ libnm-core/nm-setting-private.h \ libnm-core/nm-utils-private.h libnm_core_lib_c_real = \ + shared/nm-utils/nm-dedup-multi.c \ shared/nm-utils/nm-enum-utils.c \ shared/nm-utils/nm-shared-utils.c \ shared/nm-utils/nm-udev-utils.c \ @@ -1271,6 +1273,7 @@ $(src_libsystemd_nm_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) src_libNetworkManagerBase_la_CPPFLAGS = $(src_cppflags) src_libNetworkManagerBase_la_SOURCES = \ + \ src/nm-core-utils.c \ src/nm-core-utils.h \ src/nm-logging.c \ diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index b8c3dc8e12..a897fe5a4e 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -60,6 +60,7 @@ #include "nm-setting-wireless-security.h" #include "nm-simple-connection.h" #include "nm-keyfile-internal.h" +#include "nm-utils/nm-dedup-multi.h" #include "test-general-enums.h" @@ -75,6 +76,308 @@ G_STATIC_ASSERT (sizeof (bool) <= sizeof (int)); /*****************************************************************************/ +typedef struct { + NMDedupMultiObj parent; + int ref_count; + guint val; + guint other; +} DedupObj; + +static const NMDedupMultiObjClass dedup_obj_class; + +static DedupObj * +_dedup_obj_assert (const NMDedupMultiObj *obj) +{ + DedupObj *o; + + g_assert (obj); + o = (DedupObj *) obj; + g_assert (o->parent.klass == &dedup_obj_class); + g_assert (o->ref_count > 0); + g_assert (o->val > 0); + return o; +} + +static NMDedupMultiObj * +_dedup_obj_get_ref (const NMDedupMultiObj *obj) +{ + DedupObj *o, *o2; + + o = _dedup_obj_assert (obj); + if (o->ref_count == NM_OBJ_REF_COUNT_STACKINIT) { + o2 = g_slice_new0 (DedupObj); + o2->parent.klass = &dedup_obj_class; + o2->ref_count = 1; + o2->val = o->val; + o2->other = o->other; + return (NMDedupMultiObj *) o2; + } + + o->ref_count++; + return (NMDedupMultiObj *) o; +} + +static void +_dedup_obj_put_ref (NMDedupMultiObj *obj) +{ + DedupObj *o; + + o = _dedup_obj_assert (obj); + g_assert (o->ref_count != NM_OBJ_REF_COUNT_STACKINIT); + if (--o->ref_count == 0) + g_slice_free (DedupObj, o); +} + +static guint +_dedup_obj_full_hash (const NMDedupMultiObj *obj) +{ + const DedupObj *o; + + o = _dedup_obj_assert (obj); + return (o->val * 33) + o->other; +} + +static gboolean +_dedup_obj_full_equal (const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b) +{ + const DedupObj *o_a = _dedup_obj_assert (obj_a); + const DedupObj *o_b = _dedup_obj_assert (obj_b); + + return o_a->val == o_b->val + && o_a->other == o_b->other; +} + +static const NMDedupMultiObjClass dedup_obj_class = { + .obj_get_ref = _dedup_obj_get_ref, + .obj_put_ref = _dedup_obj_put_ref, + .obj_full_equality_allows_different_class = FALSE, + .obj_full_hash = _dedup_obj_full_hash, + .obj_full_equal = _dedup_obj_full_equal, +}; + +#define DEDUP_OBJ_INIT(val_val, other_other) \ + (&((DedupObj) { \ + .parent = { \ + .klass = &dedup_obj_class, \ + }, \ + .ref_count = NM_OBJ_REF_COUNT_STACKINIT, \ + .val = (val_val), \ + .other = (other_other), \ + })) + +typedef struct { + NMDedupMultiIdxType parent; + guint partition_size; + guint val_mod; +} DedupIdxType; + +static const NMDedupMultiIdxTypeClass dedup_idx_type_class; + +static const DedupIdxType * +_dedup_idx_assert (const NMDedupMultiIdxType *idx_type) +{ + DedupIdxType *t; + + g_assert (idx_type); + t = (DedupIdxType *) idx_type; + g_assert (t->parent.klass == &dedup_idx_type_class); + g_assert (t->partition_size > 0); + g_assert (t->val_mod > 0); + return t; +} + +static guint +_dedup_idx_obj_id_hash (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + const DedupIdxType *t; + const DedupObj *o; + guint h; + + t = _dedup_idx_assert (idx_type); + o = _dedup_obj_assert (obj); + + h = o->val / t->partition_size; + h = (h * 33) + (o->val % t->val_mod); + return h; +} + +static gboolean +_dedup_idx_obj_id_equal (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b) +{ + const DedupIdxType *t; + const DedupObj *o_a; + const DedupObj *o_b; + + t = _dedup_idx_assert (idx_type); + o_a = _dedup_obj_assert (obj_a); + o_b = _dedup_obj_assert (obj_b); + + return (o_a->val / t->partition_size) == (o_b->val / t->partition_size) + && (o_a->val % t->val_mod) == (o_b->val % t->val_mod); +} + +static guint +_dedup_idx_obj_partition_hash (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + const DedupIdxType *t; + const DedupObj *o; + + t = _dedup_idx_assert (idx_type); + o = _dedup_obj_assert (obj); + + return o->val / t->partition_size; +} + +static gboolean +_dedup_idx_obj_partition_equal (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b) +{ + const DedupIdxType *t; + const DedupObj *o_a; + const DedupObj *o_b; + + t = _dedup_idx_assert (idx_type); + o_a = _dedup_obj_assert (obj_a); + o_b = _dedup_obj_assert (obj_b); + + return (o_a->val / t->partition_size) == (o_b->val / t->partition_size); +} + +static const NMDedupMultiIdxTypeClass dedup_idx_type_class = { + .idx_obj_id_hash = _dedup_idx_obj_id_hash, + .idx_obj_id_equal = _dedup_idx_obj_id_equal, + .idx_obj_partition_hash = _dedup_idx_obj_partition_hash, + .idx_obj_partition_equal = _dedup_idx_obj_partition_equal, +}; + +static const DedupIdxType * +DEDUP_IDX_TYPE_INIT (DedupIdxType *idx_type, guint partition_size, guint val_mod) +{ + nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type, &dedup_idx_type_class); + idx_type->val_mod = val_mod; + idx_type->partition_size = partition_size; + return idx_type; +} + +static gboolean +_dedup_idx_add (NMDedupMultiIndex *idx, const DedupIdxType *idx_type, const DedupObj *obj, NMDedupMultiIdxMode mode, const NMDedupMultiEntry **out_entry) +{ + g_assert (idx); + _dedup_idx_assert ((NMDedupMultiIdxType *) idx_type); + if (obj) + _dedup_obj_assert ((NMDedupMultiObj *) obj); + return nm_dedup_multi_index_add (idx, (NMDedupMultiIdxType *) idx_type, + obj, mode, out_entry, NULL); +} + +static void +_dedup_head_entry_assert (const NMDedupMultiHeadEntry *entry) +{ + g_assert (entry); + g_assert (entry->len > 0); + g_assert (entry->len == c_list_length (&entry->lst_entries_head)); + g_assert (entry->idx_type); + g_assert (entry->is_head); +} + +static const DedupObj * +_dedup_entry_assert (const NMDedupMultiEntry *entry) +{ + g_assert (entry); + g_assert (!c_list_is_empty (&entry->lst_entries)); + g_assert (entry->head); + g_assert (!entry->is_head); + g_assert (entry->head != (gpointer) entry); + _dedup_head_entry_assert (entry->head); + return _dedup_obj_assert (entry->box->obj); +} + +static const DedupIdxType * +_dedup_entry_get_idx_type (const NMDedupMultiEntry *entry) +{ + _dedup_entry_assert (entry); + + g_assert (entry->head); + g_assert (entry->head->idx_type); + return _dedup_idx_assert (entry->head->idx_type); +} + +static void +_dedup_entry_assert_all (const NMDedupMultiEntry *entry, gssize expected_idx, const DedupObj *const*expected_obj) +{ + gsize n, i; + CList *iter; + + g_assert (entry); + _dedup_entry_assert (entry); + + g_assert (expected_obj); + n = NM_PTRARRAY_LEN (expected_obj); + + g_assert (n == c_list_length (&entry->lst_entries)); + + g_assert (expected_idx >= -1 && expected_idx < n); + g_assert (entry->head); + if (expected_idx == -1) + g_assert (entry->head == (gpointer) entry); + else + g_assert (entry->head != (gpointer) entry); + + i = 0; + c_list_for_each (iter, &entry->head->lst_entries_head) { + const NMDedupMultiEntry *entry_current = c_list_entry (iter, NMDedupMultiEntry, lst_entries); + const DedupObj *obj_current; + const DedupIdxType *idx_type = _dedup_entry_get_idx_type (entry_current); + + obj_current = _dedup_entry_assert (entry_current); + g_assert (obj_current); + g_assert (i < n); + if (expected_idx == i) + g_assert (entry_current == entry); + g_assert (idx_type->parent.klass->idx_obj_partition_equal (&idx_type->parent, + entry_current->box->obj, + c_list_entry (entry->head->lst_entries_head.next, NMDedupMultiEntry, lst_entries)->box->obj)); + i++; + } +} +#define _dedup_entry_assert_all(entry, expected_idx, ...) _dedup_entry_assert_all (entry, expected_idx, (const DedupObj *const[]) { __VA_ARGS__, NULL }) + +static void +test_dedup_multi (void) +{ + NMDedupMultiIndex *idx; + DedupIdxType IDX_20_3_a_stack; + const DedupIdxType *const IDX_20_3_a = DEDUP_IDX_TYPE_INIT (&IDX_20_3_a_stack, 20, 3); + const NMDedupMultiEntry *entry1; + + idx = nm_dedup_multi_index_new (); + + g_assert (_dedup_idx_add (idx, IDX_20_3_a, DEDUP_OBJ_INIT (1, 1), NM_DEDUP_MULTI_IDX_MODE_APPEND, &entry1)); + _dedup_entry_assert_all (entry1, 0, DEDUP_OBJ_INIT (1, 1)); + + g_assert (nm_dedup_multi_box_find (idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT (1, 1))); + g_assert (!nm_dedup_multi_box_find (idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT (1, 2))); + + g_assert (_dedup_idx_add (idx, IDX_20_3_a, DEDUP_OBJ_INIT (1, 2), NM_DEDUP_MULTI_IDX_MODE_APPEND, &entry1)); + _dedup_entry_assert_all (entry1, 0, DEDUP_OBJ_INIT (1, 2)); + + g_assert (!nm_dedup_multi_box_find (idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT (1, 1))); + g_assert (nm_dedup_multi_box_find (idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT (1, 2))); + + g_assert (_dedup_idx_add (idx, IDX_20_3_a, DEDUP_OBJ_INIT (2, 2), NM_DEDUP_MULTI_IDX_MODE_APPEND, &entry1)); + _dedup_entry_assert_all (entry1, 1, DEDUP_OBJ_INIT (1, 2), DEDUP_OBJ_INIT (2, 2)); + + nm_dedup_multi_index_unref (idx); +} + +/*****************************************************************************/ + static NMConnection * _connection_new_from_dbus (GVariant *dict, GError **error) { @@ -5782,7 +6085,7 @@ int main (int argc, char **argv) { nmtst_init (&argc, &argv, TRUE); - /* The tests */ + g_test_add_func ("/core/general/test_dedup_multi", test_dedup_multi); g_test_add_func ("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe); g_test_add_func ("/core/general/test_nm_in_set", test_nm_in_set); g_test_add_func ("/core/general/test_nm_in_strset", test_nm_in_strset); diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c new file mode 100644 index 0000000000..6e949f9066 --- /dev/null +++ b/shared/nm-utils/nm-dedup-multi.c @@ -0,0 +1,988 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * 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 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 Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dedup-multi.h" + +/*****************************************************************************/ + +typedef struct { + NMDedupMultiBox parent; + int ref_count; +} Box; + +typedef struct { + /* the stack-allocated lookup entry. It has a compatible + * memory layout with NMDedupMultiEntry and NMDedupMultiHeadEntry. + * + * It is recognizable by having lst_entries_sentinel.next set to NULL. + * Contrary to the other entries, which have lst_entries.next + * always non-NULL. + * */ + CList lst_entries_sentinel; + const NMDedupMultiObj *obj; + const NMDedupMultiIdxType *idx_type; + bool lookup_head; +} LookupEntry; + +struct _NMDedupMultiIndex { + int ref_count; + GHashTable *idx_entries; + GHashTable *idx_box; +}; + +/*****************************************************************************/ + +static void _box_unref (NMDedupMultiIndex *self, + Box *box); + +/*****************************************************************************/ + +static void +ASSERT_idx_type (const NMDedupMultiIdxType *idx_type) +{ + nm_assert (idx_type); +#if NM_MORE_ASSERTS > 10 + nm_assert (idx_type->klass); + nm_assert (idx_type->klass->idx_obj_id_hash); + nm_assert (idx_type->klass->idx_obj_id_equal); + nm_assert (!!idx_type->klass->idx_obj_partition_hash == !!idx_type->klass->idx_obj_partition_equal); + nm_assert (idx_type->lst_idx_head.next); +#endif +} + +void +nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type, + const NMDedupMultiIdxTypeClass *klass) +{ + nm_assert (idx_type); + nm_assert (klass); + + memset (idx_type, 0, sizeof (*idx_type)); + idx_type->klass = klass; + c_list_init (&idx_type->lst_idx_head); + + ASSERT_idx_type (idx_type); +} + +/*****************************************************************************/ + +static NMDedupMultiEntry * +_entry_lookup_obj (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + const LookupEntry stack_entry = { + .obj = obj, + .idx_type = idx_type, + .lookup_head = FALSE, + }; + + ASSERT_idx_type (idx_type); + return g_hash_table_lookup (self->idx_entries, &stack_entry); +} + +static NMDedupMultiHeadEntry * +_entry_lookup_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + NMDedupMultiHeadEntry *head_entry; + const LookupEntry stack_entry = { + .obj = obj, + .idx_type = idx_type, + .lookup_head = TRUE, + }; + + ASSERT_idx_type (idx_type); + + if (!idx_type->klass->idx_obj_partition_equal) { + if (c_list_is_empty (&idx_type->lst_idx_head)) + head_entry = NULL; + else { + nm_assert (c_list_length (&idx_type->lst_idx_head) == 1); + head_entry = c_list_entry (idx_type->lst_idx_head.next, NMDedupMultiHeadEntry, lst_idx); + } + nm_assert (head_entry == g_hash_table_lookup (self->idx_entries, &stack_entry)); + return head_entry; + } + + return g_hash_table_lookup (self->idx_entries, &stack_entry); +} + +static void +_entry_unpack (const NMDedupMultiEntry *entry, + const NMDedupMultiIdxType **out_idx_type, + const NMDedupMultiObj **out_obj, + gboolean *out_lookup_head) +{ + const NMDedupMultiHeadEntry *head_entry; + const LookupEntry *lookup_entry; + + nm_assert (entry); + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (LookupEntry, lst_entries_sentinel) == G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, lst_entries_head)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, box) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, idx_type)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, is_head) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, is_head)); + + if (!entry->lst_entries.next) { + /* the entry is stack-allocated by _entry_lookup(). */ + lookup_entry = (LookupEntry *) entry; + *out_obj = lookup_entry->obj; + *out_idx_type = lookup_entry->idx_type; + *out_lookup_head = lookup_entry->lookup_head; + } else if (entry->is_head) { + head_entry = (NMDedupMultiHeadEntry *) entry; + nm_assert (!c_list_is_empty (&head_entry->lst_entries_head)); + *out_obj = c_list_entry (head_entry->lst_entries_head.next, NMDedupMultiEntry, lst_entries)->box->obj; + *out_idx_type = head_entry->idx_type; + *out_lookup_head = TRUE; + } else { + *out_obj = entry->box->obj; + *out_idx_type = entry->head->idx_type; + *out_lookup_head = FALSE; + } + + nm_assert (NM_IN_SET (*out_lookup_head, FALSE, TRUE)); + ASSERT_idx_type (*out_idx_type); +} + +static guint +_dict_idx_entries_hash (const NMDedupMultiEntry *entry) +{ + const NMDedupMultiIdxType *idx_type; + const NMDedupMultiObj *obj; + gboolean lookup_head; + guint h; + + _entry_unpack (entry, &idx_type, &obj, &lookup_head); + + if (idx_type->klass->idx_obj_partition_hash) { + nm_assert (obj); + h = idx_type->klass->idx_obj_partition_hash (idx_type, obj); + } else + h = 1914869417; + + if (!lookup_head) + h = idx_type->klass->idx_obj_id_hash (idx_type, obj); + + h = NM_HASH_COMBINE (h, GPOINTER_TO_UINT (idx_type)); + h = NM_HASH_COMBINE (h, lookup_head); + return h; +} + +static gboolean +_dict_idx_entries_equal (const NMDedupMultiEntry *entry_a, + const NMDedupMultiEntry *entry_b) +{ + const NMDedupMultiIdxType *idx_type_a, *idx_type_b; + const NMDedupMultiObj *obj_a, *obj_b; + gboolean lookup_head_a, lookup_head_b; + + _entry_unpack (entry_a, &idx_type_a, &obj_a, &lookup_head_a); + _entry_unpack (entry_b, &idx_type_b, &obj_b, &lookup_head_b); + + if ( idx_type_a != idx_type_b + || lookup_head_a != lookup_head_b) + return FALSE; + if (!nm_dedup_multi_idx_type_partition_equal (idx_type_a, obj_a, obj_b)) + return FALSE; + if ( !lookup_head_a + && !nm_dedup_multi_idx_type_id_equal (idx_type_a, obj_a, obj_b)) + return FALSE; + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj, + NMDedupMultiEntry *entry, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiBox *box_existing, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiBox **out_old_box) +{ + NMDedupMultiHeadEntry *head_entry; + const NMDedupMultiBox *box, *box_new; + gboolean add_head_entry = FALSE; + + nm_assert (self); + ASSERT_idx_type (idx_type); + nm_assert (obj); + nm_assert (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE)); + nm_assert (!box_existing || box_existing == nm_dedup_multi_box_find (self, obj)); + nm_assert (!head_existing || head_existing->idx_type == idx_type); + nm_assert (({ + const NMDedupMultiHeadEntry *_h; + gboolean _ok = TRUE; + if (head_existing) { + _h = nm_dedup_multi_index_lookup_head (self, idx_type, obj); + if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING) + _ok = (_h == NULL); + else + _ok = (_h == head_existing); + } + _ok; + })); + + if (entry) { + nm_dedup_multi_entry_set_dirty (entry, FALSE); + + nm_assert (!head_existing || entry->head == head_existing); + + if (entry_order) { + nm_assert (entry_order->head == entry->head); + nm_assert (c_list_contains (&entry->lst_entries, &entry_order->lst_entries)); + nm_assert (c_list_contains (&entry_order->lst_entries, &entry->lst_entries)); + } + + switch (mode) { + case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: + if (entry_order) { + if ( entry_order != entry + && entry->lst_entries.next != &entry_order->lst_entries) { + c_list_unlink (&entry->lst_entries); + c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries); + } + } else { + if (entry->lst_entries.prev != &entry->head->lst_entries_head) { + c_list_unlink (&entry->lst_entries); + c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + } + } + break; + case NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE: + if (entry_order) { + if ( entry_order != entry + && entry->lst_entries.prev != &entry_order->lst_entries) { + c_list_unlink (&entry->lst_entries); + c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries); + } + } else { + if (entry->lst_entries.next != &entry->head->lst_entries_head) { + c_list_unlink (&entry->lst_entries); + c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + } + } + break; + case NM_DEDUP_MULTI_IDX_MODE_PREPEND: + case NM_DEDUP_MULTI_IDX_MODE_APPEND: + break; + }; + + if ( obj == entry->box->obj + || obj->klass->obj_full_equal (obj, + entry->box->obj)) { + NM_SET_OUT (out_entry, entry); + NM_SET_OUT (out_old_box, nm_dedup_multi_box_ref (entry->box)); + return FALSE; + } + + if (box_existing) + box_new = nm_dedup_multi_box_ref (box_existing); + else + box_new = nm_dedup_multi_box_new (self, obj); + + box = entry->box; + entry->box = box_new; + + NM_SET_OUT (out_entry, entry); + if (out_old_box) + *out_old_box = box; + else + _box_unref (self, (Box *) box); + return TRUE; + } + + if ( idx_type->klass->idx_obj_partitionable + && !idx_type->klass->idx_obj_partitionable (idx_type, obj)) { + /* this object cannot be partitioned by this idx_type. */ + nm_assert (!head_existing || head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING); + NM_SET_OUT (out_entry, NULL); + NM_SET_OUT (out_old_box, NULL); + return FALSE; + } + + if (box_existing) + box_new = nm_dedup_multi_box_ref (box_existing); + else + box_new = nm_dedup_multi_box_new (self, obj); + obj = box_new->obj; + + if (!head_existing) + head_entry = _entry_lookup_head (self, idx_type, obj); + else if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING) + head_entry = NULL; + else + head_entry = head_existing; + + if (!head_entry) { + head_entry = g_slice_new0 (NMDedupMultiHeadEntry); + head_entry->is_head = TRUE; + head_entry->idx_type = idx_type; + c_list_init (&head_entry->lst_entries_head); + c_list_link_tail (&idx_type->lst_idx_head, &head_entry->lst_idx); + add_head_entry = TRUE; + } else + nm_assert (c_list_contains (&idx_type->lst_idx_head, &head_entry->lst_idx)); + + if (entry_order) { + nm_assert (!add_head_entry); + nm_assert (entry_order->head == head_entry); + nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry_order->lst_entries)); + nm_assert (c_list_contains (&entry_order->lst_entries, &head_entry->lst_entries_head)); + } + + entry = g_slice_new0 (NMDedupMultiEntry); + entry->box = box_new; + entry->head = head_entry; + + switch (mode) { + case NM_DEDUP_MULTI_IDX_MODE_PREPEND: + case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: + if (entry_order) + c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries); + else + c_list_link_front (&head_entry->lst_entries_head, &entry->lst_entries); + break; + default: + if (entry_order) + c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries); + else + c_list_link_tail (&head_entry->lst_entries_head, &entry->lst_entries); + break; + }; + + idx_type->len++; + head_entry->len++; + + if ( add_head_entry + && !nm_g_hash_table_add (self->idx_entries, head_entry)) + nm_assert_not_reached (); + + if (!nm_g_hash_table_add (self->idx_entries, entry)) + nm_assert_not_reached (); + + NM_SET_OUT (out_entry, entry); + NM_SET_OUT (out_old_box, NULL); + return TRUE; +} + +gboolean +nm_dedup_multi_index_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiBox **out_old_box) +{ + NMDedupMultiEntry *entry; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + g_return_val_if_fail (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE), + FALSE); + + entry = _entry_lookup_obj (self, idx_type, obj); + return _add (self, idx_type, obj, + entry, mode, NULL, + NULL, NULL, + out_entry, out_old_box); +} + +/* nm_dedup_multi_index_add_full: + * @self: the index instance. + * @idx_type: the index handle for storing @obj. + * @obj: the NMDedupMultiObj instance to add. + * @mode: whether to append or prepend the new item. If @entry_order is given, + * the entry will be sorted after/before, instead of appending/prepending to + * the entire list. If a comparable object is already tracked, then it may + * still be resorted by specifying one of the "FORCE" modes. + * @entry_order: if not NULL, the new entry will be sorted before or after @entry_order. + * If given, @entry_order MUST be tracked by @self, and the object it points to MUST + * be in the same partition tracked by @idx_type. That is, they must have the same + * head_entry and it means, you must ensure that @entry_order and the created/modified + * entry will share the same head. + * @entry_existing: if not NULL, it safes a hash lookup of the entry where the + * object will be placed in. You can omit this, and it will be automatically + * detected (at the expense of an additional hash lookup). + * Basically, this is the result of nm_dedup_multi_index_lookup_obj(), + * with the pecularity that if you know that @obj is not yet tracked, + * you may specify %NM_DEDUP_MULTI_ENTRY_MISSING. + * @head_existing: an optional argument to safe a lookup for the head. If specified, + * it must be identical to nm_dedup_multi_index_lookup_head(), with the pecularity + * that if the head is not yet tracked, you may specify %NM_DEDUP_MULTI_HEAD_ENTRY_MISSING + * @box_existing: optional argument to safe the box lookup. If given, @obj and the boxed + * object must be identical, and @box_existing must be tracked by @self. This is to safe + * the additional lookup. + * @out_entry: if give, return the added entry. This entry may have already exists (update) + * or be newly created. If @obj is not partitionable according to @idx_type, @obj + * is not to be added and it returns %NULL. + * @out_old_box: if given, return the previously contained boxed object. It only + * returns a boxed object, if a matching entry was tracked previously, not if a + * new entry was created. Note that when passing @out_old_box you obtain a reference + * to the boxed object and MUST return it with nm_dedup_multi_box_unref(). + * + * Adds and object to the index. + * + * Return: %TRUE if anything changed, %FALSE if nothing changed. + */ +gboolean +nm_dedup_multi_index_add_full (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + const NMDedupMultiEntry *entry_existing, + const NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiBox *box_existing, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiBox **out_old_box) +{ + NMDedupMultiEntry *entry; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + g_return_val_if_fail (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE), + FALSE); + + if (entry_existing == NULL) + entry = _entry_lookup_obj (self, idx_type, obj); + else if (entry_existing == NM_DEDUP_MULTI_ENTRY_MISSING) { + nm_assert (!_entry_lookup_obj (self, idx_type, obj)); + entry = NULL; + } else { + nm_assert (entry_existing == _entry_lookup_obj (self, idx_type, obj)); + entry = (NMDedupMultiEntry *) entry_existing; + } + return _add (self, idx_type, obj, + entry, + mode, entry_order, + (NMDedupMultiHeadEntry *) head_existing, + box_existing, + out_entry, out_old_box); +} + +/*****************************************************************************/ + +static void +_remove_entry (NMDedupMultiIndex *self, + NMDedupMultiEntry *entry, + gboolean *out_head_entry_removed) +{ + Box *box; + NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIdxType *idx_type; + + nm_assert (self); + nm_assert (entry); + nm_assert (entry->box); + nm_assert (entry->head); + nm_assert (!c_list_is_empty (&entry->lst_entries)); + nm_assert (g_hash_table_lookup (self->idx_entries, entry) == entry); + + head_entry = (NMDedupMultiHeadEntry *) entry->head; + box = (Box *) entry->box; + + nm_assert (head_entry); + nm_assert (head_entry->len > 0); + nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry); + + idx_type = (NMDedupMultiIdxType *) head_entry->idx_type; + ASSERT_idx_type (idx_type); + + nm_assert (idx_type->len >= head_entry->len); + if (--head_entry->len > 0) { + nm_assert (idx_type->len > 1); + idx_type->len--; + head_entry = NULL; + } + + NM_SET_OUT (out_head_entry_removed, head_entry != NULL); + + if (!g_hash_table_remove (self->idx_entries, entry)) + nm_assert_not_reached (); + + if ( head_entry + && !g_hash_table_remove (self->idx_entries, head_entry)) + nm_assert_not_reached (); + + c_list_unlink (&entry->lst_entries); + g_slice_free (NMDedupMultiEntry, entry); + + if (head_entry) { + nm_assert (c_list_is_empty (&head_entry->lst_entries_head)); + c_list_unlink (&head_entry->lst_idx); + g_slice_free (NMDedupMultiHeadEntry, head_entry); + } + + _box_unref (self, box); +} + +static guint +_remove_head (NMDedupMultiIndex *self, + NMDedupMultiHeadEntry *head_entry, + gboolean remove_all /* otherwise just dirty ones */, + gboolean mark_survivors_dirty) +{ + guint n; + gboolean head_entry_removed; + CList *iter_entry, *iter_entry_safe; + + nm_assert (self); + nm_assert (head_entry); + nm_assert (head_entry->len > 0); + nm_assert (head_entry->len == c_list_length (&head_entry->lst_entries_head)); + nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry); + + n = 0; + c_list_for_each_safe (iter_entry, iter_entry_safe, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + if ( remove_all + || entry->dirty) { + _remove_entry (self, + entry, + &head_entry_removed); + n++; + if (head_entry_removed) + break; + } else if (mark_survivors_dirty) + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } + + return n; +} + +static guint +_remove_idx_entry (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean remove_all /* otherwise just dirty ones */, + gboolean mark_survivors_dirty) +{ + guint n; + CList *iter_idx, *iter_idx_safe; + + nm_assert (self); + ASSERT_idx_type (idx_type); + + n = 0; + c_list_for_each_safe (iter_idx, iter_idx_safe, &idx_type->lst_idx_head) { + n += _remove_head (self, + c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx), + remove_all, mark_survivors_dirty); + } + return n; +} + +guint +nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self, + gconstpointer entry) +{ + g_return_val_if_fail (self, 0); + + nm_assert (entry); + + if (!((NMDedupMultiEntry *) entry)->is_head) { + _remove_entry (self, (NMDedupMultiEntry *) entry, NULL); + return 1; + } + return _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE); +} + +guint +nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + const NMDedupMultiEntry *entry; + + entry = nm_dedup_multi_index_lookup_obj (self, idx_type, obj); + if (!entry) + return 0; + _remove_entry (self, (NMDedupMultiEntry *) entry, NULL); + return 1; +} + +guint +nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + const NMDedupMultiHeadEntry *entry; + + entry = nm_dedup_multi_index_lookup_head (self, idx_type, obj); + return entry + ? _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE) + : 0; +} + +guint +nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type) +{ + g_return_val_if_fail (self, 0); + g_return_val_if_fail (idx_type, 0); + + return _remove_idx_entry (self, idx_type, TRUE, FALSE); +} + +/*****************************************************************************/ + +/** + * nm_dedup_multi_index_lookup_obj: + * @self: the index cache + * @idx_type: the lookup index type + * @obj: the object to lookup. This means the match is performed + * according to NMDedupMultiIdxTypeClass's idx_obj_id_equal() + * of @idx_type. + * + * Returns: the cache entry or %NULL if the entry wasn't found. + */ +const NMDedupMultiEntry * +nm_dedup_multi_index_lookup_obj (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + + nm_assert (idx_type && idx_type->klass); + return _entry_lookup_obj (self, idx_type, obj); +} + +/** + * nm_dedup_multi_index_lookup_head: + * @self: the index cache + * @idx_type: the lookup index type + * @obj: the object to lookup, of type "const NMDedupMultiObj *". + * Depending on the idx_type, you *must* also provide a selector + * object, even when looking up the list head. That is, because + * the idx_type implementation may choose to partition the objects + * in distinct list, so you need a selector object to know which + * list head to lookup. + * + * Returns: the cache entry or %NULL if the entry wasn't found. + */ +const NMDedupMultiHeadEntry * +nm_dedup_multi_index_lookup_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + + return _entry_lookup_head (self, idx_type, obj); +} + +/*****************************************************************************/ + +void +nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + NMDedupMultiHeadEntry *head_entry; + CList *iter_entry; + + g_return_if_fail (self); + g_return_if_fail (idx_type); + + head_entry = _entry_lookup_head (self, idx_type, obj); + if (!head_entry) + return; + + c_list_for_each (iter_entry, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } +} + +void +nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type) +{ + CList *iter_idx, *iter_entry; + + g_return_if_fail (self); + g_return_if_fail (idx_type); + + c_list_for_each (iter_idx, &idx_type->lst_idx_head) { + NMDedupMultiHeadEntry *head_entry; + + head_entry = c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx); + c_list_for_each (iter_entry, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } + } +} + +/** + * nm_dedup_multi_index_dirty_remove_idx: + * @self: the index instance + * @idx_type: the index-type to select the objects. + * @mark_survivors_dirty: while the function removes all entries that are + * marked as dirty, if @set_dirty is true, the surviving objects + * will be marked dirty right away. + * + * Deletes all entries for @idx_type that are marked dirty. Only + * non-dirty objects survive. If @mark_survivors_dirty is set to TRUE, the survivors + * are marked as dirty right away. + * + * Returns: number of deleted entries. + */ +guint +nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean mark_survivors_dirty) +{ + g_return_val_if_fail (self, 0); + g_return_val_if_fail (idx_type, 0); + + return _remove_idx_entry (self, idx_type, FALSE, mark_survivors_dirty); +} + +/*****************************************************************************/ + +static guint +_dict_idx_box_hash (const Box *box) +{ + const NMDedupMultiObj *obj = box->parent.obj; + + return obj->klass->obj_full_hash (obj); +} + +static gboolean +_dict_idx_box_equal (const Box *box_a, + const Box *box_b) +{ + const NMDedupMultiObjClass *klass; + const NMDedupMultiObj *obj_a = box_a->parent.obj; + const NMDedupMultiObj *obj_b = box_b->parent.obj; + + klass = obj_a->klass; + + /* if the class differs, but at least one of them supports calls with + * differing klass, choose it. + * + * Implementing a klass that can compare equality for multiple + * types is hard to get right. E.g. hash(), equal() and get_ref() + * must all agree so that instances of different types look identical. */ + if ( klass != obj_b->klass + && !klass->obj_full_equality_allows_different_class) { + klass = obj_b->klass; + if (!klass->obj_full_equality_allows_different_class) + return FALSE; + } + + return klass->obj_full_equal (obj_a, obj_b); +} + +static void +_box_unref (NMDedupMultiIndex *self, + Box *box) +{ + nm_assert (box); + nm_assert (box->ref_count > 0); + nm_assert (g_hash_table_lookup (self->idx_box, box) == box); + + if (--box->ref_count > 0) + return; + + if (!g_hash_table_remove (self->idx_box, box)) + nm_assert_not_reached (); + + ((NMDedupMultiObj *) box->parent.obj)->klass->obj_put_ref ((NMDedupMultiObj *) box->parent.obj); + g_slice_free (Box, box); +} + +#define BOX_INIT(obj) \ + (&((const Box) { .parent = { .obj = obj, }, })) + +static Box * +_box_find (NMDedupMultiIndex *index, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + nm_assert (index); + nm_assert (obj); + + return g_hash_table_lookup (index->idx_box, BOX_INIT (obj)); +} + +const NMDedupMultiBox * +nm_dedup_multi_box_find (NMDedupMultiIndex *index, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (index, NULL); + g_return_val_if_fail (obj, NULL); + + return (NMDedupMultiBox *) _box_find (index, obj); +} + +const NMDedupMultiBox * +nm_dedup_multi_box_new (NMDedupMultiIndex *index, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + Box *box; + const NMDedupMultiObj *o; + + g_return_val_if_fail (index, NULL); + g_return_val_if_fail (obj, NULL); + + box = _box_find (index, obj); + if (box) { + box->ref_count++; + return (NMDedupMultiBox *) box; + } + + o = ((const NMDedupMultiObj *) obj)->klass->obj_get_ref (obj); + if (!o) + g_return_val_if_reached (NULL); + + box = g_slice_new (Box); + box->parent.obj = o; + box->ref_count = 1; + + nm_assert (_dict_idx_box_equal (box, BOX_INIT (obj))); + nm_assert (_dict_idx_box_equal (BOX_INIT (obj), box)); + nm_assert (_dict_idx_box_hash (BOX_INIT (obj)) == _dict_idx_box_hash (box)); + + if (!nm_g_hash_table_add (index->idx_box, box)) + nm_assert_not_reached (); + + return &box->parent; +} + +const NMDedupMultiBox * +nm_dedup_multi_box_ref (const NMDedupMultiBox *box) +{ + Box *b; + + b = (Box *) box; + + g_return_val_if_fail (b, NULL); + g_return_val_if_fail (b->ref_count > 0, NULL); + + b->ref_count++; + return box; +} + +const NMDedupMultiBox * +nm_dedup_multi_box_unref (NMDedupMultiIndex *self, + const NMDedupMultiBox *box) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (box, NULL); + g_return_val_if_fail (((Box *) box)->ref_count > 0, NULL); + + _box_unref (self, (Box *) box); + return NULL; +} + +/*****************************************************************************/ + +NMDedupMultiIndex * +nm_dedup_multi_index_new (void) +{ + NMDedupMultiIndex *self; + + self = g_slice_new0 (NMDedupMultiIndex); + self->ref_count = 1; + self->idx_entries = g_hash_table_new ((GHashFunc) _dict_idx_entries_hash, (GEqualFunc) _dict_idx_entries_equal); + self->idx_box = g_hash_table_new ((GHashFunc) _dict_idx_box_hash, (GEqualFunc) _dict_idx_box_equal); + return self; +} + +NMDedupMultiIndex * +nm_dedup_multi_index_ref (NMDedupMultiIndex *self) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (self->ref_count > 0, NULL); + + self->ref_count++; + return self; +} + +NMDedupMultiIndex * +nm_dedup_multi_index_unref (NMDedupMultiIndex *self) +{ + GHashTableIter iter; + const NMDedupMultiIdxType *idx_type; + NMDedupMultiEntry *entry; + + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (self->ref_count > 0, NULL); + + if (--self->ref_count > 0) + return NULL; + +more: + g_hash_table_iter_init (&iter, self->idx_entries); + while (g_hash_table_iter_next (&iter, (gpointer *) &entry, NULL)) { + if (entry->is_head) + idx_type = ((NMDedupMultiHeadEntry *) entry)->idx_type; + else + idx_type = entry->head->idx_type; + _remove_idx_entry (self, (NMDedupMultiIdxType *) idx_type, TRUE, FALSE); + goto more; + } + + nm_assert (g_hash_table_size (self->idx_entries) == 0); + + /* If callers took references to NMDedupMultiBox instances, they + * must keep NMDedupMultiIndex alive for as long as they keep + * the boxed reference. */ + nm_assert (g_hash_table_size (self->idx_box) == 0); + + g_hash_table_unref (self->idx_entries); + g_hash_table_unref (self->idx_box); + + g_slice_free (NMDedupMultiIndex, self); + return NULL; +} diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h new file mode 100644 index 0000000000..fc4283a808 --- /dev/null +++ b/shared/nm-utils/nm-dedup-multi.h @@ -0,0 +1,365 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * 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 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 Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2017 Red Hat, Inc. + */ + +#ifndef __NM_DEDUP_MULTI_H__ +#define __NM_DEDUP_MULTI_H__ + +#include "nm-obj.h" +#include "c-list.h" + +/*****************************************************************************/ + +typedef struct _NMDedupMultiObj NMDedupMultiObj; +typedef struct _NMDedupMultiObjClass NMDedupMultiObjClass; +typedef struct _NMDedupMultiBox NMDedupMultiBox; +typedef struct _NMDedupMultiIdxType NMDedupMultiIdxType; +typedef struct _NMDedupMultiIdxTypeClass NMDedupMultiIdxTypeClass; +typedef struct _NMDedupMultiEntry NMDedupMultiEntry; +typedef struct _NMDedupMultiHeadEntry NMDedupMultiHeadEntry; +typedef struct _NMDedupMultiIndex NMDedupMultiIndex; + +typedef enum _NMDedupMultiIdxMode { + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + + /* append new objects to the end of the list. + * If the object is already in the cache, don't move it. */ + NM_DEDUP_MULTI_IDX_MODE_APPEND, + + /* like NM_DEDUP_MULTI_IDX_MODE_APPEND, but if the object + * is already in teh cache, move it to the end. */ + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, +} NMDedupMultiIdxMode; + +/*****************************************************************************/ + +struct _NMDedupMultiObj { + union { + NMObjBaseInst parent; + const NMDedupMultiObjClass *klass; + }; +}; + +struct _NMDedupMultiObjClass { + NMObjBaseClass parent; + + /* obj_get_ref() may just increase the ref-count, or it may allocate a new object. + * In any case, it returns ownership of an equal object to @obj. */ + NMDedupMultiObj *(*obj_get_ref) (const NMDedupMultiObj *obj); + + void (*obj_put_ref) (NMDedupMultiObj *obj); + + gboolean obj_full_equality_allows_different_class; + + /* the NMDedupMultiObj can be deduplicated. For that the obj_full_hash() + * and obj_full_equal() compare *all* fields of the object, even minor ones. */ + guint (*obj_full_hash) (const NMDedupMultiObj *obj); + gboolean (*obj_full_equal) (const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); +}; + +/*****************************************************************************/ + +struct _NMDedupMultiBox { + gconstpointer obj; +}; + +const NMDedupMultiBox *nm_dedup_multi_box_new (NMDedupMultiIndex *index, /* const NMDedupMultiObj * */ gconstpointer obj); +const NMDedupMultiBox *nm_dedup_multi_box_find (NMDedupMultiIndex *index, /* const NMDedupMultiObj * */ gconstpointer obj); +const NMDedupMultiBox *nm_dedup_multi_box_ref (const NMDedupMultiBox *box); +const NMDedupMultiBox *nm_dedup_multi_box_unref (NMDedupMultiIndex *index, const NMDedupMultiBox *box); + +/*****************************************************************************/ + +/* the NMDedupMultiIdxType is an access handle under which you can store and + * retrieve NMDedupMultiObj instances in NMDedupMultiIndex. + * + * The NMDedupMultiIdxTypeClass determines it's behavior, but you can have + * multiple instances (of the same class). + * + * For example, NMIP4Config can have idx-type to put there all IPv4 Routes. + * This idx-type instance is private to the NMIP4Config instance. Basically, + * the NMIP4Config instance uses the idx-type to maintain an ordered list + * of routes in NMDedupMultiIndex. + * + * However, a NMDedupMultiIdxType may also partition the set of objects + * in multiple distinct lists. NMIP4Config doesn't do that (because instead + * of creating one idx-type for IPv4 and IPv6 routes, it just cretaes + * to distinct idx-types, one for each address family. + * This partitioning is used by NMPlatform to maintain a lookup index for + * routes by ifindex. As the ifindex is dynamic, it does not create an + * idx-type instance for each ifindex. Instead, it has one idx-type for + * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj, + * the partitioning NMDedupMultiIdxType takes into accound the NMDedupMultiObj + * instance to associate it with the right list. + * + * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry + * instances, which each is the head for a list of NMDedupMultiEntry instances. + * In the platform example, the NMDedupMultiHeadEntry parition the indexed objects + * by their ifindex. */ +struct _NMDedupMultiIdxType { + union { + NMObjBaseInst parent; + const NMDedupMultiIdxTypeClass *klass; + }; + + CList lst_idx_head; + + guint len; +}; + +void nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type, + const NMDedupMultiIdxTypeClass *klass); + +struct _NMDedupMultiIdxTypeClass { + NMObjBaseClass parent; + + guint (*idx_obj_id_hash) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj); + gboolean (*idx_obj_id_equal) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); + + /* an NMDedupMultiIdxTypeClass which implements partitioning of the + * tracked objects, must implement the idx_obj_partition*() functions. + * + * idx_obj_partitionable() may return NULL if the object cannot be tracked. + * For example, a index for routes by ifindex, may not want to track any + * routes that don't have a valid ifindex. If the idx-type says that the + * object is not partitionable, it is never added to the NMDedupMultiIndex. */ + gboolean (*idx_obj_partitionable) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj); + guint (*idx_obj_partition_hash) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj); + gboolean (*idx_obj_partition_equal) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); +}; + +static inline gboolean +nm_dedup_multi_idx_type_id_equal (const NMDedupMultiIdxType *idx_type, + /* const NMDedupMultiObj * */ gconstpointer obj_a, + /* const NMDedupMultiObj * */ gconstpointer obj_b) +{ + nm_assert (idx_type); + return idx_type->klass->idx_obj_id_equal (idx_type, + obj_a, + obj_b); +} + +static inline gboolean +nm_dedup_multi_idx_type_partition_equal (const NMDedupMultiIdxType *idx_type, + /* const NMDedupMultiObj * */ gconstpointer obj_a, + /* const NMDedupMultiObj * */ gconstpointer obj_b) +{ + nm_assert (idx_type); + if (idx_type->klass->idx_obj_partition_equal) { + nm_assert (obj_a); + nm_assert (obj_b); + return idx_type->klass->idx_obj_partition_equal (idx_type, + obj_a, + obj_b); + } + return TRUE; +} + +/*****************************************************************************/ + +struct _NMDedupMultiEntry { + + /* this is the list of all entries that share the same head entry. + * All entries compare equal according to idx_obj_partition_equal(). */ + CList lst_entries; + + /* the object instance. It is ref-counted and shared. + * Note that this instance must be immutable once it + * is added to the list. + * + * For head entries, @box is NULL and @head points to itself. */ + const NMDedupMultiBox *box; + + bool is_head; + bool dirty; + + const NMDedupMultiHeadEntry *head; +}; + +struct _NMDedupMultiHeadEntry { + + /* this is the list of all entries that share the same head entry. + * All entries compare equal according to idx_obj_partition_equal(). */ + CList lst_entries_head; + + const NMDedupMultiIdxType *idx_type; + + bool is_head; + + guint len; + + CList lst_idx; +}; + +static inline void +nm_dedup_multi_entry_set_dirty (const NMDedupMultiEntry *entry, + gboolean dirty) +{ + /* NMDedupMultiEntry is always exposed as a const object, because it is not + * supposed to be modified outside NMDedupMultiIndex API. Except the "dirty" + * flag. In C++ speak, it is a mutable field. + * + * Add this inline function, to case-away constness and set the dirty flag. */ + nm_assert (entry); + ((NMDedupMultiEntry *) entry)->dirty = dirty; +} + +/*****************************************************************************/ + +NMDedupMultiIndex *nm_dedup_multi_index_new (void); +NMDedupMultiIndex *nm_dedup_multi_index_ref (NMDedupMultiIndex *self); +NMDedupMultiIndex *nm_dedup_multi_index_unref (NMDedupMultiIndex *self); + +static inline void +_nm_auto_unref_dedup_multi_index (NMDedupMultiIndex **v) +{ + if (*v) + nm_dedup_multi_index_unref (*v); +} +#define nm_auto_unref_dedup_multi_index nm_auto(_nm_auto_unref_dedup_multi_index) + +#define NM_DEDUP_MULTI_ENTRY_MISSING ((const NMDedupMultiEntry *) GUINT_TO_POINTER (1)) +#define NM_DEDUP_MULTI_HEAD_ENTRY_MISSING ((const NMDedupMultiHeadEntry *) GUINT_TO_POINTER (1)) + +gboolean nm_dedup_multi_index_add_full (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + const NMDedupMultiEntry *entry_existing, + const NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiBox *box_existing, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiBox **out_old_box); + +gboolean nm_dedup_multi_index_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiBox **out_old_box); + +const NMDedupMultiEntry *nm_dedup_multi_index_lookup_obj (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +const NMDedupMultiHeadEntry *nm_dedup_multi_index_lookup_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +guint nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self, + gconstpointer entry); + +guint nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +guint nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +guint nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type); + +void nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +void nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type); + +guint nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean mark_survivors_dirty); + +/*****************************************************************************/ + +typedef struct _NMDedupMultiIter { + const NMDedupMultiHeadEntry *head; + const NMDedupMultiEntry *current; + const NMDedupMultiEntry *next; +} NMDedupMultiIter; + +static inline void +nm_dedup_multi_iter_init (NMDedupMultiIter *iter, const NMDedupMultiHeadEntry *head) +{ + g_return_if_fail (iter); + + iter->head = head; + iter->current = NULL; + iter->next = head && !c_list_is_empty (&head->lst_entries_head) + ? c_list_entry (head->lst_entries_head.next, NMDedupMultiEntry, lst_entries) + : NULL; +} + +static inline gboolean +nm_dedup_multi_iter_next (NMDedupMultiIter *iter) +{ + g_return_val_if_fail (iter, FALSE); + + if (!iter->next) + return FALSE; + + /* we always look ahead for the @next. This way, the user + * may delete the current entry (but no other entries). */ + iter->current = iter->next; + if (iter->next->lst_entries.next == &iter->head->lst_entries_head) + iter->next = NULL; + else + iter->next = c_list_entry (iter->next->lst_entries.next, NMDedupMultiEntry, lst_entries); + return TRUE; +} + +static inline void +nm_dedup_multi_iter_rewind (NMDedupMultiIter *iter) +{ + /* rewind the iterator. + * + * In principle, you can always delete the current entry. + * However, if you delete *all* current entries, the list + * head becomes invalid too and rewinding will crash. + * + * So, either + * - don't modify the list + * - if you modify it: + * - only delete the current entry, don't delete other entries. + * - you may add more entries, however that may make iteration + * confusing. + * - you may rewind the iterator, but only if not all + * entires were deleted. + * + * Use with care. */ + g_return_if_fail (iter); + nm_dedup_multi_iter_init (iter, iter->head); +} + +/*****************************************************************************/ + +#endif /* __NM_DEDUP_MULTI_H__ */