/* * This file is part of libmodulemd * Copyright (C) 2017-2018 Stephen Gallagher * * Fedora-License-Identifier: MIT * SPDX-2.0-License-Identifier: MIT * SPDX-3.0-License-Identifier: MIT * * This program is free software. * For more information on the license, see COPYING. * For more information on free software, see <https://www.gnu.org/philosophy/free-sw.en.html>. */ #include "modulemd.h" #include <string.h> #include "private/modulemd-util.h" #include "private/modulemd-improvedmodule-private.h" #include <locale.h> GQuark modulemd_error_quark (void) { return g_quark_from_static_string ("modulemd-error-quark"); } GHashTable * _modulemd_hash_table_deep_str_copy (GHashTable *orig) { GHashTable *new; GHashTableIter iter; gpointer key, value; g_return_val_if_fail (orig, NULL); new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_iter_init (&iter, orig); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert ( new, g_strdup ((const gchar *)key), g_strdup ((const gchar *)value)); } return new; } GHashTable * _modulemd_hash_table_deep_obj_copy (GHashTable *orig) { GHashTable *new; GHashTableIter iter; gpointer key, value; g_return_val_if_fail (orig, NULL); new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); g_hash_table_iter_init (&iter, orig); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert ( new, g_strdup ((const gchar *)key), g_object_ref ((GObject *)value)); } return new; } GHashTable * _modulemd_hash_table_deep_variant_copy (GHashTable *orig) { GHashTable *new; GHashTableIter iter; gpointer key, value; g_return_val_if_fail (orig, NULL); new = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, modulemd_variant_unref); g_hash_table_iter_init (&iter, orig); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert ( new, g_strdup ((const gchar *)key), g_variant_ref ((GVariant *)value)); } return new; } gint _modulemd_strcmp_sort (gconstpointer a, gconstpointer b) { return g_strcmp0 (*(const gchar **)a, *(const gchar **)b); } GPtrArray * _modulemd_ordered_str_keys (GHashTable *htable, GCompareFunc compare_func) { GPtrArray *keys; GHashTableIter iter; gpointer key, value; keys = g_ptr_array_new_full (g_hash_table_size (htable), g_free); g_hash_table_iter_init (&iter, htable); while (g_hash_table_iter_next (&iter, &key, &value)) { g_ptr_array_add (keys, g_strdup ((const gchar *)key)); } g_ptr_array_sort (keys, compare_func); return keys; } static gint _modulemd_int64_sort (gconstpointer a, gconstpointer b) { gint retval = *(const gint64 *)a - *(const gint64 *)b; return retval; } GList * _modulemd_ordered_int64_keys (GHashTable *htable) { g_autoptr (GList) hash_keys = NULL; GList *unsorted_keys = NULL; GList *sorted_keys = NULL; /* Get the keys from the hash table */ hash_keys = g_hash_table_get_keys (htable); /* Make a copy of the keys that we can modify */ unsorted_keys = g_list_copy (hash_keys); /* Sort the keys numerically */ sorted_keys = g_list_sort (unsorted_keys, _modulemd_int64_sort); return sorted_keys; } void modulemd_variant_unref (void *ptr) { g_variant_unref ((GVariant *)ptr); } gboolean modulemd_validate_nevra (const gchar *nevra) { g_autofree gchar *tmp = g_strdup (nevra); gsize len = strlen (nevra); gchar *i; gchar *endptr; /* Since the "name" portion of a NEVRA can have an infinite number of * hyphens, we need to parse from the end backwards. */ i = &tmp[len - 1]; /* Everything after the last '.' must be the architecture */ while (i >= tmp) { if (*i == '.') { break; } i--; } if (i < tmp) { /* We hit the start of the string without hitting '.' */ return FALSE; } /* * TODO: Compare the architecture suffix with a list of known-valid ones * This needs to come from an external source that's kept up to date or * this will regularly break. */ /* Process the "release" tag */ while (i >= tmp) { if (*i == '-') { break; } i--; } if (i < tmp) { /* We hit the start of the string without hitting '-' */ return FALSE; } /* No need to validate Release; it's fairly arbitrary */ /* Process the version */ while (i >= tmp) { if (*i == ':') { break; } i--; } if (i < tmp) { /* We hit the start of the string without hitting ':' */ return FALSE; } /* Process the epoch */ *i = '\0'; while (i >= tmp) { if (*i == '-') { break; } i--; } if (i < tmp) { /* We hit the start of the string without hitting '-' */ return FALSE; } /* Validate that this section is a number */ g_ascii_strtoll (i, &endptr, 10); if (endptr == i) { /* String conversion failed */ return FALSE; } /* No need to specifically parse the name section here */ return TRUE; } modulemd_tracer * modulemd_trace_init (const gchar *function_name) { modulemd_tracer *self = g_malloc0_n (1, sizeof (modulemd_tracer)); self->function_name = g_strdup (function_name); g_debug ("TRACE: Entering %s", self->function_name); return self; } void modulemd_trace_free (modulemd_tracer *tracer) { g_debug ("TRACE: Exiting %s", tracer->function_name); g_clear_pointer (&tracer->function_name, g_free); g_free (tracer); } ModulemdTranslationEntry * _get_locale_entry (ModulemdTranslation *translation, const gchar *_locale) { ModulemdTranslationEntry *entry = NULL; g_autofree gchar *locale = NULL; if (!translation) return NULL; g_return_val_if_fail (MODULEMD_IS_TRANSLATION (translation), NULL); if (_locale) { if (g_strcmp0 (_locale, "C") == 0 || g_strcmp0 (_locale, "C.UTF-8") == 0) { /* If the locale is "C" or "C.UTF-8", always return the standard value */ return NULL; } locale = g_strdup (_locale); } else { /* If the locale was NULL, use the locale of this process */ locale = g_strdup (setlocale (LC_MESSAGES, NULL)); } entry = modulemd_translation_get_entry_by_locale (translation, locale); return entry; } GPtrArray * _modulemd_index_serialize (GHashTable *index, GError **error) { GHashTableIter iter; gpointer key, value; gsize i; g_autoptr (GPtrArray) objects = NULL; g_autoptr (GPtrArray) sub_objects = NULL; if (!index) { g_set_error ( error, MODULEMD_ERROR, MODULEMD_ERROR_PROGRAMMING, "Index was NULL."); return NULL; } objects = g_ptr_array_new_with_free_func (g_object_unref); g_hash_table_iter_init (&iter, index); while (g_hash_table_iter_next (&iter, &key, &value)) { if (!value || !MODULEMD_IS_IMPROVEDMODULE (value)) { g_set_error (error, MODULEMD_ERROR, MODULEMD_ERROR_PROGRAMMING, "Index value was not a ModulemdImprovedModule."); return NULL; } sub_objects = modulemd_improvedmodule_serialize (MODULEMD_IMPROVEDMODULE (value)); for (i = 0; i < sub_objects->len; i++) { g_ptr_array_add (objects, g_object_ref (g_ptr_array_index (sub_objects, i))); } g_clear_pointer (&sub_objects, g_ptr_array_unref); } return g_ptr_array_ref (objects); } static ModulemdImprovedModule * get_or_create_module_from_index (GHashTable *htable, const gchar *module_name) { ModulemdImprovedModule *stored_module = NULL; ModulemdImprovedModule *module = NULL; stored_module = g_hash_table_lookup (htable, module_name); if (stored_module) { module = modulemd_improvedmodule_copy (stored_module); } else { /* This is the first encounter of this module */ module = modulemd_improvedmodule_new (module_name); } return module; } GHashTable * module_index_from_data (GPtrArray *data, GError **error) { GObject *item = NULL; gsize i = 0; gsize j = 0; g_autofree gchar *module_name = NULL; g_autofree gchar *stream_name = NULL; g_autoptr (ModulemdImprovedModule) module = NULL; ModulemdImprovedModule *stored_module = NULL; ModulemdModuleStream *stream = NULL; g_autoptr (GPtrArray) retrieved_streams = NULL; ModulemdModuleStream *retrieved_stream = NULL; ModulemdDefaults *defaults = NULL; g_autoptr (GHashTable) module_index = NULL; GError *merge_error = NULL; g_autoptr (GPtrArray) clean_data = NULL; g_autoptr (GPtrArray) translations = g_ptr_array_new_with_free_func (g_object_unref); ModulemdTranslation *translation = NULL; /* Deduplicate and merge any ModulemdDefaults objects in the list */ clean_data = modulemd_merge_defaults (data, NULL, FALSE, &merge_error); if (!clean_data) { g_debug ("Error merging defaults: %s", merge_error->message); g_propagate_error (error, merge_error); return NULL; } module_index = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); /* Iterate through the data and add the entries to the module_index */ for (i = 0; i < clean_data->len; i++) { item = g_ptr_array_index (clean_data, i); if (MODULEMD_IS_MODULESTREAM (item)) { stream = MODULEMD_MODULESTREAM (item); module_name = modulemd_modulestream_get_name (stream); stream_name = modulemd_modulestream_get_stream (stream); if (!module_name || !stream_name) { g_set_error (error, MODULEMD_ERROR, MODULEMD_ERROR_MISSING_CONTENT, "Module streams without a module name or stream " "name may not be read into an index."); return NULL; } module = get_or_create_module_from_index (module_index, module_name); /* Add the stream to this module. Note: if the same stream name * appears in the data more than once, the last one encountered wins. */ modulemd_improvedmodule_add_stream (module, stream); /* Save it back to the index */ g_hash_table_replace (module_index, g_strdup (module_name), modulemd_improvedmodule_copy (module)); } else if (MODULEMD_IS_DEFAULTS (item)) { defaults = MODULEMD_DEFAULTS (item); module_name = modulemd_defaults_dup_module_name (defaults); module = get_or_create_module_from_index (module_index, module_name); /* Update the defaults. */ modulemd_improvedmodule_set_defaults (module, defaults); /* Save it back to the index */ g_hash_table_replace (module_index, g_strdup (module_name), modulemd_improvedmodule_copy (module)); } else if (MODULEMD_IS_TRANSLATION (item)) { /* Queue these up to process at the end, because we need to ensure * that the streams they are associated with have been added first */ g_ptr_array_add (translations, g_object_ref (MODULEMD_TRANSLATION (item))); } g_clear_pointer (&module, g_object_unref); g_clear_pointer (&module_name, g_free); g_clear_pointer (&stream_name, g_free); } /* Iterate through the translations and associate them to the appropriate * streams. */ for (i = 0; i < translations->len; i++) { translation = g_ptr_array_index (translations, i); stored_module = g_hash_table_lookup ( module_index, modulemd_translation_peek_module_name (translation)); if (!stored_module) { /* No streams of this module were processed, so ignore this set of * translations. */ continue; } retrieved_streams = modulemd_improvedmodule_get_streams_by_name ( stored_module, modulemd_translation_peek_module_stream (translation)); if (!retrieved_streams) { /* This stream of this module wasn't processed, so ignore this set of * translations. */ continue; } for (j = 0; j < retrieved_streams->len; j++) { retrieved_stream = g_ptr_array_index (retrieved_streams, j); /* Assign this translation to the object. * Note: This will be ignored if there is a higher modified value already * assigned to this object. */ modulemd_modulestream_set_translation (retrieved_stream, translation); /* Save the updated stream back to the index */ modulemd_improvedmodule_add_stream (stored_module, retrieved_stream); } g_clear_pointer (&retrieved_streams, g_ptr_array_unref); } return g_hash_table_ref (module_index); }