|  | /* | 
|  | * Copyright © 2010 Codethink Limited | 
|  | * | 
|  | * 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 licence, 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, see <http://www.gnu.org/licenses/>. | 
|  | * | 
|  | * Author: Ryan Lortie <desrt@desrt.ca> | 
|  | */ | 
|  |  | 
|  | #include "gvdb-reader.h" | 
|  | #include "gvdb-format.h" | 
|  |  | 
|  | #include <string.h> | 
|  |  | 
|  | struct _GvdbTable { | 
|  | gint ref_count; | 
|  |  | 
|  | const gchar *data; | 
|  | gsize size; | 
|  |  | 
|  | gpointer user_data; | 
|  | GvdbRefFunc ref_user_data; | 
|  | GDestroyNotify unref_user_data; | 
|  |  | 
|  | gboolean byteswapped; | 
|  | gboolean trusted; | 
|  |  | 
|  | const guint32_le *bloom_words; | 
|  | guint32 n_bloom_words; | 
|  | guint bloom_shift; | 
|  |  | 
|  | const guint32_le *hash_buckets; | 
|  | guint32 n_buckets; | 
|  |  | 
|  | struct gvdb_hash_item *hash_items; | 
|  | guint32 n_hash_items; | 
|  | }; | 
|  |  | 
|  | static const gchar * | 
|  | gvdb_table_item_get_key (GvdbTable                   *file, | 
|  | const struct gvdb_hash_item *item, | 
|  | gsize                       *size) | 
|  | { | 
|  | guint32 start, end; | 
|  |  | 
|  | start = guint32_from_le (item->key_start); | 
|  | *size = guint16_from_le (item->key_size); | 
|  | end = start + *size; | 
|  |  | 
|  | if G_UNLIKELY (start > end || end > file->size) | 
|  | return NULL; | 
|  |  | 
|  | return file->data + start; | 
|  | } | 
|  |  | 
|  | static gconstpointer | 
|  | gvdb_table_dereference (GvdbTable                 *file, | 
|  | const struct gvdb_pointer *pointer, | 
|  | gint                       alignment, | 
|  | gsize                     *size) | 
|  | { | 
|  | guint32 start, end; | 
|  |  | 
|  | start = guint32_from_le (pointer->start); | 
|  | end = guint32_from_le (pointer->end); | 
|  |  | 
|  | if G_UNLIKELY (start > end || end > file->size || start & (alignment - 1)) | 
|  | return NULL; | 
|  |  | 
|  | *size = end - start; | 
|  |  | 
|  | return file->data + start; | 
|  | } | 
|  |  | 
|  | static void | 
|  | gvdb_table_setup_root (GvdbTable                 *file, | 
|  | const struct gvdb_pointer *pointer) | 
|  | { | 
|  | const struct gvdb_hash_header *header; | 
|  | guint32 n_bloom_words; | 
|  | guint32 n_buckets; | 
|  | gsize size; | 
|  |  | 
|  | header = gvdb_table_dereference (file, pointer, 4, &size); | 
|  |  | 
|  | if G_UNLIKELY (header == NULL || size < sizeof *header) | 
|  | return; | 
|  |  | 
|  | size -= sizeof *header; | 
|  |  | 
|  | n_bloom_words = guint32_from_le (header->n_bloom_words); | 
|  | n_buckets = guint32_from_le (header->n_buckets); | 
|  | n_bloom_words &= (1u << 27) - 1; | 
|  |  | 
|  | if G_UNLIKELY (n_bloom_words * sizeof (guint32_le) > size) | 
|  | return; | 
|  |  | 
|  | file->bloom_words = (gpointer) (header + 1); | 
|  | size -= n_bloom_words * sizeof (guint32_le); | 
|  | file->n_bloom_words = n_bloom_words; | 
|  |  | 
|  | if G_UNLIKELY (n_buckets > G_MAXUINT / sizeof (guint32_le) || | 
|  | n_buckets * sizeof (guint32_le) > size) | 
|  | return; | 
|  |  | 
|  | file->hash_buckets = file->bloom_words + file->n_bloom_words; | 
|  | size -= n_buckets * sizeof (guint32_le); | 
|  | file->n_buckets = n_buckets; | 
|  |  | 
|  | if G_UNLIKELY (size % sizeof (struct gvdb_hash_item)) | 
|  | return; | 
|  |  | 
|  | file->hash_items = (gpointer) (file->hash_buckets + n_buckets); | 
|  | file->n_hash_items = size / sizeof (struct gvdb_hash_item); | 
|  | } | 
|  |  | 
|  | static GvdbTable * | 
|  | new_from_data (const void    *data, | 
|  | gsize          data_len, | 
|  | gboolean       trusted, | 
|  | gpointer       user_data, | 
|  | GvdbRefFunc    ref, | 
|  | GDestroyNotify unref, | 
|  | const char    *filename, | 
|  | GError       **error) | 
|  | { | 
|  | GvdbTable *file; | 
|  |  | 
|  | file = g_slice_new0 (GvdbTable); | 
|  | file->data = data; | 
|  | file->size = data_len; | 
|  | file->trusted = trusted; | 
|  | file->ref_count = 1; | 
|  | file->ref_user_data = ref; | 
|  | file->unref_user_data = unref; | 
|  | file->user_data = user_data; | 
|  |  | 
|  | if (sizeof (struct gvdb_header) <= file->size) | 
|  | { | 
|  | const struct gvdb_header *header = (gpointer) file->data; | 
|  |  | 
|  | if (header->signature[0] == GVDB_SIGNATURE0 && | 
|  | header->signature[1] == GVDB_SIGNATURE1 && | 
|  | guint32_from_le (header->version) == 0) | 
|  | file->byteswapped = FALSE; | 
|  |  | 
|  | else if (header->signature[0] == GVDB_SWAPPED_SIGNATURE0 && | 
|  | header->signature[1] == GVDB_SWAPPED_SIGNATURE1 && | 
|  | guint32_from_le (header->version) == 0) | 
|  | file->byteswapped = TRUE; | 
|  |  | 
|  | else | 
|  | { | 
|  | if (filename) | 
|  | g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | 
|  | "%s: invalid header", filename); | 
|  | else | 
|  | g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | 
|  | "invalid gvdb header"); | 
|  | g_slice_free (GvdbTable, file); | 
|  | if (unref) | 
|  | unref (user_data); | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | gvdb_table_setup_root (file, &header->root); | 
|  | } | 
|  |  | 
|  | return file; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_new: | 
|  | * @filename: the path to the hash file | 
|  | * @trusted: if the contents of @filename are trusted | 
|  | * @error: %NULL, or a pointer to a %NULL #GError | 
|  | * | 
|  | * Creates a new #GvdbTable from the contents of the file found at | 
|  | * @filename. | 
|  | * | 
|  | * The only time this function fails is if the file cannot be opened. | 
|  | * In that case, the #GError that is returned will be an error from | 
|  | * g_mapped_file_new(). | 
|  | * | 
|  | * An empty or otherwise corrupted file is considered to be a valid | 
|  | * #GvdbTable with no entries. | 
|  | * | 
|  | * You should call gvdb_table_unref() on the return result when you no | 
|  | * longer require it. | 
|  | * | 
|  | * Returns: a new #GvdbTable | 
|  | **/ | 
|  | GvdbTable * | 
|  | gvdb_table_new (const gchar  *filename, | 
|  | gboolean      trusted, | 
|  | GError      **error) | 
|  | { | 
|  | GMappedFile *mapped; | 
|  |  | 
|  | if ((mapped = g_mapped_file_new (filename, FALSE, error)) == NULL) | 
|  | return NULL; | 
|  |  | 
|  | return new_from_data (g_mapped_file_get_contents (mapped), | 
|  | g_mapped_file_get_length (mapped), | 
|  | trusted, | 
|  | mapped, | 
|  | (GvdbRefFunc)g_mapped_file_ref, | 
|  | (GDestroyNotify)g_mapped_file_unref, | 
|  | filename, | 
|  | error); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_new_from_data: | 
|  | * @data: the data | 
|  | * @data_len: the length of @data in bytes | 
|  | * @trusted: if the contents of @data are trusted | 
|  | * @user_data: User supplied data that owns @data | 
|  | * @ref: Ref function for @user_data | 
|  | * @unref: Unref function for @user_data | 
|  | * | 
|  | * Creates a new #GvdbTable from the data in @data. | 
|  | * | 
|  | * An empty or otherwise corrupted data is considered to be a valid | 
|  | * #GvdbTable with no entries. | 
|  | * | 
|  | * You should call gvdb_table_unref() on the return result when you no | 
|  | * longer require it. | 
|  | * | 
|  | * Returns: a new #GvdbTable | 
|  | **/ | 
|  | GvdbTable * | 
|  | gvdb_table_new_from_data (const void    *data, | 
|  | gsize          data_len, | 
|  | gboolean       trusted, | 
|  | gpointer       user_data, | 
|  | GvdbRefFunc    ref, | 
|  | GDestroyNotify unref, | 
|  | GError        **error) | 
|  | { | 
|  | return new_from_data (data, data_len, | 
|  | trusted, | 
|  | user_data, ref, unref, | 
|  | NULL, | 
|  | error); | 
|  | } | 
|  |  | 
|  | static gboolean | 
|  | gvdb_table_bloom_filter (GvdbTable *file, | 
|  | guint32    hash_value) | 
|  | { | 
|  | guint32 word, mask; | 
|  |  | 
|  | if (file->n_bloom_words == 0) | 
|  | return TRUE; | 
|  |  | 
|  | word = (hash_value / 32) % file->n_bloom_words; | 
|  | mask = 1 << (hash_value & 31); | 
|  | mask |= 1 << ((hash_value >> file->bloom_shift) & 31); | 
|  |  | 
|  | return (guint32_from_le (file->bloom_words[word]) & mask) == mask; | 
|  | } | 
|  |  | 
|  | static gboolean | 
|  | gvdb_table_check_name (GvdbTable             *file, | 
|  | struct gvdb_hash_item *item, | 
|  | const gchar           *key, | 
|  | guint                  key_length) | 
|  | { | 
|  | const gchar *this_key; | 
|  | gsize this_size; | 
|  | guint32 parent; | 
|  |  | 
|  | this_key = gvdb_table_item_get_key (file, item, &this_size); | 
|  |  | 
|  | if G_UNLIKELY (this_key == NULL || this_size > key_length) | 
|  | return FALSE; | 
|  |  | 
|  | key_length -= this_size; | 
|  |  | 
|  | if G_UNLIKELY (memcmp (this_key, key + key_length, this_size) != 0) | 
|  | return FALSE; | 
|  |  | 
|  | parent = guint32_from_le (item->parent); | 
|  | if (key_length == 0 && parent == 0xffffffffu) | 
|  | return TRUE; | 
|  |  | 
|  | if G_LIKELY (parent < file->n_hash_items && this_size > 0) | 
|  | return gvdb_table_check_name (file, | 
|  | &file->hash_items[parent], | 
|  | key, key_length); | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static const struct gvdb_hash_item * | 
|  | gvdb_table_lookup (GvdbTable   *file, | 
|  | const gchar *key, | 
|  | gchar        type) | 
|  | { | 
|  | guint32 hash_value = 5381; | 
|  | guint key_length; | 
|  | guint32 bucket; | 
|  | guint32 lastno; | 
|  | guint32 itemno; | 
|  |  | 
|  | if G_UNLIKELY (file->n_buckets == 0 || file->n_hash_items == 0) | 
|  | return NULL; | 
|  |  | 
|  | for (key_length = 0; key[key_length]; key_length++) | 
|  | hash_value = (hash_value * 33) + ((signed char *) key)[key_length]; | 
|  |  | 
|  | if (!gvdb_table_bloom_filter (file, hash_value)) | 
|  | return NULL; | 
|  |  | 
|  | bucket = hash_value % file->n_buckets; | 
|  | itemno = guint32_from_le (file->hash_buckets[bucket]); | 
|  |  | 
|  | if (bucket == file->n_buckets - 1 || | 
|  | (lastno = guint32_from_le(file->hash_buckets[bucket + 1])) > file->n_hash_items) | 
|  | lastno = file->n_hash_items; | 
|  |  | 
|  | while G_LIKELY (itemno < lastno) | 
|  | { | 
|  | struct gvdb_hash_item *item = &file->hash_items[itemno]; | 
|  |  | 
|  | if (hash_value == guint32_from_le (item->hash_value)) | 
|  | if G_LIKELY (gvdb_table_check_name (file, item, key, key_length)) | 
|  | if G_LIKELY (item->type == type) | 
|  | return item; | 
|  |  | 
|  | itemno++; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static const struct gvdb_hash_item * | 
|  | gvdb_table_get_item (GvdbTable  *table, | 
|  | guint32_le  item_no) | 
|  | { | 
|  | guint32 item_no_native = guint32_from_le (item_no); | 
|  |  | 
|  | if G_LIKELY (item_no_native < table->n_hash_items) | 
|  | return table->hash_items + item_no_native; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static gboolean | 
|  | gvdb_table_list_from_item (GvdbTable                    *table, | 
|  | const struct gvdb_hash_item  *item, | 
|  | const guint32_le            **list, | 
|  | guint                        *length) | 
|  | { | 
|  | gsize size; | 
|  |  | 
|  | *list = gvdb_table_dereference (table, &item->value.pointer, 4, &size); | 
|  |  | 
|  | if G_LIKELY (*list == NULL || size % 4) | 
|  | return FALSE; | 
|  |  | 
|  | *length = size / 4; | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * gvdb_table_list: | 
|  | * @file: a #GvdbTable | 
|  | * @key: a string | 
|  | * | 
|  | * List all of the keys that appear below @key.  The nesting of keys | 
|  | * within the hash file is defined by the program that created the hash | 
|  | * file.  One thing is constant: each item in the returned array can be | 
|  | * concatenated to @key to obtain the full name of that key. | 
|  | * | 
|  | * It is not possible to tell from this function if a given key is | 
|  | * itself a path, a value, or another hash table; you are expected to | 
|  | * know this for yourself. | 
|  | * | 
|  | * You should call g_strfreev() on the return result when you no longer | 
|  | * require it. | 
|  | * | 
|  | * Returns: a %NULL-terminated string array | 
|  | **/ | 
|  | gchar ** | 
|  | gvdb_table_list (GvdbTable   *file, | 
|  | const gchar *key) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  | const guint32_le *list; | 
|  | gchar **strv; | 
|  | guint length; | 
|  | guint i; | 
|  |  | 
|  | if ((item = gvdb_table_lookup (file, key, 'L')) == NULL) | 
|  | return NULL; | 
|  |  | 
|  | if (!gvdb_table_list_from_item (file, item, &list, &length)) | 
|  | return NULL; | 
|  |  | 
|  | strv = g_new (gchar *, length + 1); | 
|  | for (i = 0; i < length; i++) | 
|  | { | 
|  | guint32 itemno = guint32_from_le (list[i]); | 
|  |  | 
|  | if (itemno < file->n_hash_items) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  | const gchar *string; | 
|  | gsize strsize; | 
|  |  | 
|  | item = file->hash_items + itemno; | 
|  |  | 
|  | string = gvdb_table_item_get_key (file, item, &strsize); | 
|  |  | 
|  | if (string != NULL) | 
|  | strv[i] = g_strndup (string, strsize); | 
|  | else | 
|  | strv[i] = g_malloc0 (1); | 
|  | } | 
|  | else | 
|  | strv[i] = g_malloc0 (1); | 
|  | } | 
|  |  | 
|  | strv[i] = NULL; | 
|  |  | 
|  | return strv; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_has_value: | 
|  | * @file: a #GvdbTable | 
|  | * @key: a string | 
|  | * | 
|  | * Checks for a value named @key in @file. | 
|  | * | 
|  | * Note: this function does not consider non-value nodes (other hash | 
|  | * tables, for example). | 
|  | * | 
|  | * Returns: %TRUE if @key is in the table | 
|  | **/ | 
|  | gboolean | 
|  | gvdb_table_has_value (GvdbTable    *file, | 
|  | const gchar  *key) | 
|  | { | 
|  | return gvdb_table_lookup (file, key, 'v') != NULL; | 
|  | } | 
|  |  | 
|  | static GVariant * | 
|  | gvdb_table_value_from_item (GvdbTable                   *table, | 
|  | const struct gvdb_hash_item *item) | 
|  | { | 
|  | GVariant *variant, *value; | 
|  | gconstpointer data; | 
|  | gsize size; | 
|  |  | 
|  | data = gvdb_table_dereference (table, &item->value.pointer, 8, &size); | 
|  |  | 
|  | if G_UNLIKELY (data == NULL) | 
|  | return NULL; | 
|  |  | 
|  | variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, | 
|  | data, size, table->trusted, | 
|  | table->unref_user_data, | 
|  | table->ref_user_data ? table->ref_user_data (table->user_data) : table->user_data); | 
|  | value = g_variant_get_variant (variant); | 
|  | g_variant_unref (variant); | 
|  |  | 
|  | return value; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_get_value: | 
|  | * @file: a #GvdbTable | 
|  | * @key: a string | 
|  | * | 
|  | * Looks up a value named @key in @file. | 
|  | * | 
|  | * If the value is not found then %NULL is returned.  Otherwise, a new | 
|  | * #GVariant instance is returned.  The #GVariant does not depend on the | 
|  | * continued existence of @file. | 
|  | * | 
|  | * You should call g_variant_unref() on the return result when you no | 
|  | * longer require it. | 
|  | * | 
|  | * Returns: a #GVariant, or %NULL | 
|  | **/ | 
|  | GVariant * | 
|  | gvdb_table_get_value (GvdbTable    *file, | 
|  | const gchar  *key) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  | GVariant *value; | 
|  |  | 
|  | if ((item = gvdb_table_lookup (file, key, 'v')) == NULL) | 
|  | return NULL; | 
|  |  | 
|  | value = gvdb_table_value_from_item (file, item); | 
|  |  | 
|  | if (value && file->byteswapped) | 
|  | { | 
|  | GVariant *tmp; | 
|  |  | 
|  | tmp = g_variant_byteswap (value); | 
|  | g_variant_unref (value); | 
|  | value = tmp; | 
|  | } | 
|  |  | 
|  | return value; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_get_raw_value: | 
|  | * @table: a #GvdbTable | 
|  | * @key: a string | 
|  | * | 
|  | * Looks up a value named @key in @file. | 
|  | * | 
|  | * This call is equivalent to gvdb_table_get_value() except that it | 
|  | * never byteswaps the value. | 
|  | * | 
|  | * Returns: a #GVariant, or %NULL | 
|  | **/ | 
|  | GVariant * | 
|  | gvdb_table_get_raw_value (GvdbTable   *table, | 
|  | const gchar *key) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  |  | 
|  | if ((item = gvdb_table_lookup (table, key, 'v')) == NULL) | 
|  | return NULL; | 
|  |  | 
|  | return gvdb_table_value_from_item (table, item); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_get_table: | 
|  | * @file: a #GvdbTable | 
|  | * @key: a string | 
|  | * | 
|  | * Looks up the hash table named @key in @file. | 
|  | * | 
|  | * The toplevel hash table in a #GvdbTable can contain reference to | 
|  | * child hash tables (and those can contain further references...). | 
|  | * | 
|  | * If @key is not found in @file then %NULL is returned.  Otherwise, a | 
|  | * new #GvdbTable is returned, referring to the child hashtable as | 
|  | * contained in the file.  This newly-created #GvdbTable does not depend | 
|  | * on the continued existence of @file. | 
|  | * | 
|  | * You should call gvdb_table_unref() on the return result when you no | 
|  | * longer require it. | 
|  | * | 
|  | * Returns: a new #GvdbTable, or %NULL | 
|  | **/ | 
|  | GvdbTable * | 
|  | gvdb_table_get_table (GvdbTable   *file, | 
|  | const gchar *key) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  | GvdbTable *new; | 
|  |  | 
|  | item = gvdb_table_lookup (file, key, 'H'); | 
|  |  | 
|  | if (item == NULL) | 
|  | return NULL; | 
|  |  | 
|  | new = g_slice_new0 (GvdbTable); | 
|  | new->user_data = file->ref_user_data ? file->ref_user_data (file->user_data) : file->user_data; | 
|  | new->ref_user_data = file->ref_user_data; | 
|  | new->unref_user_data = file->unref_user_data; | 
|  | new->byteswapped = file->byteswapped; | 
|  | new->trusted = file->trusted; | 
|  | new->data = file->data; | 
|  | new->size = file->size; | 
|  | new->ref_count = 1; | 
|  |  | 
|  | gvdb_table_setup_root (new, &item->value.pointer); | 
|  |  | 
|  | return new; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_ref: | 
|  | * @file: a #GvdbTable | 
|  | * | 
|  | * Increases the reference count on @file. | 
|  | * | 
|  | * Returns: a new reference on @file | 
|  | **/ | 
|  | GvdbTable * | 
|  | gvdb_table_ref (GvdbTable *file) | 
|  | { | 
|  | g_atomic_int_inc (&file->ref_count); | 
|  |  | 
|  | return file; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_unref: | 
|  | * @file: a #GvdbTable | 
|  | * | 
|  | * Decreases the reference count on @file, possibly freeing it. | 
|  | * | 
|  | * Since: 2.26 | 
|  | **/ | 
|  | void | 
|  | gvdb_table_unref (GvdbTable *file) | 
|  | { | 
|  | if (g_atomic_int_dec_and_test (&file->ref_count)) | 
|  | { | 
|  | if (file->unref_user_data) | 
|  | file->unref_user_data (file->user_data); | 
|  | g_slice_free (GvdbTable, file); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_is_valid: | 
|  | * @table: a #GvdbTable | 
|  | * | 
|  | * Checks if the table is still valid. | 
|  | * | 
|  | * An on-disk GVDB can be marked as invalid.  This happens when the file | 
|  | * has been replaced.  The appropriate action is typically to reopen the | 
|  | * file. | 
|  | * | 
|  | * Returns: %TRUE if @table is still valid | 
|  | **/ | 
|  | gboolean | 
|  | gvdb_table_is_valid (GvdbTable *table) | 
|  | { | 
|  | return !!*table->data; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * gvdb_table_walk: | 
|  | * @table: a #GvdbTable | 
|  | * @key: a key corresponding to a list | 
|  | * @open_func: the #GvdbWalkOpenFunc | 
|  | * @value_func: the #GvdbWalkValueFunc | 
|  | * @close_func: the #GvdbWalkCloseFunc | 
|  | * @user_data: data to pass to the callbacks | 
|  | * | 
|  | * Looks up the list at @key and iterate over the items in it. | 
|  | * | 
|  | * First, @open_func is called to signal that we are starting to iterate over | 
|  | * the list.  Then the list is iterated.  When all items in the list have been | 
|  | * iterated over, the @close_func is called. | 
|  | * | 
|  | * When iterating, if a given item in the list is a value then @value_func is | 
|  | * called. | 
|  | * | 
|  | * If a given item in the list is itself a list then @open_func is called.  If | 
|  | * that function returns %TRUE then the walk begins iterating the items in the | 
|  | * sublist, until there are no more items, at which point a matching | 
|  | * @close_func call is made.  If @open_func returns %FALSE then no iteration of | 
|  | * the sublist occurs and no corresponding @close_func call is made. | 
|  | **/ | 
|  | void | 
|  | gvdb_table_walk (GvdbTable         *table, | 
|  | const gchar       *key, | 
|  | GvdbWalkOpenFunc   open_func, | 
|  | GvdbWalkValueFunc  value_func, | 
|  | GvdbWalkCloseFunc  close_func, | 
|  | gpointer           user_data) | 
|  | { | 
|  | const struct gvdb_hash_item *item; | 
|  | const guint32_le *pointers[64]; | 
|  | const guint32_le *enders[64]; | 
|  | gsize name_lengths[64]; | 
|  | gint index = 0; | 
|  |  | 
|  | item = gvdb_table_lookup (table, key, 'L'); | 
|  | name_lengths[0] = 0; | 
|  | pointers[0] = NULL; | 
|  | enders[0] = NULL; | 
|  | goto start_here; | 
|  |  | 
|  | while (index) | 
|  | { | 
|  | close_func (name_lengths[index], user_data); | 
|  | index--; | 
|  |  | 
|  | while (pointers[index] < enders[index]) | 
|  | { | 
|  | const gchar *name; | 
|  | gsize name_len; | 
|  |  | 
|  | item = gvdb_table_get_item (table, *pointers[index]++); | 
|  | start_here: | 
|  |  | 
|  | if (item != NULL && | 
|  | (name = gvdb_table_item_get_key (table, item, &name_len))) | 
|  | { | 
|  | if (item->type == 'L') | 
|  | { | 
|  | if (open_func (name, name_len, user_data)) | 
|  | { | 
|  | guint length = 0; | 
|  |  | 
|  | index++; | 
|  | g_assert (index < 64); | 
|  |  | 
|  | gvdb_table_list_from_item (table, item, | 
|  | &pointers[index], | 
|  | &length); | 
|  | enders[index] = pointers[index] + length; | 
|  | name_lengths[index] = name_len; | 
|  | } | 
|  | } | 
|  | else if (item->type == 'v') | 
|  | { | 
|  | GVariant *value; | 
|  |  | 
|  | value = gvdb_table_value_from_item (table, item); | 
|  |  | 
|  | if (value != NULL) | 
|  | { | 
|  | if (table->byteswapped) | 
|  | { | 
|  | GVariant *tmp; | 
|  |  | 
|  | tmp = g_variant_byteswap (value); | 
|  | g_variant_unref (value); | 
|  | value = tmp; | 
|  | } | 
|  |  | 
|  | value_func (name, name_len, value, user_data); | 
|  | g_variant_unref (value); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |