1 /*
2  * This file is part of libmodulemd
3  * Copyright (C) 2018 Red Hat, Inc.
4  *
5  * Fedora-License-Identifier: MIT
6  * SPDX-2.0-License-Identifier: MIT
7  * SPDX-3.0-License-Identifier: MIT
8  *
9  * This program is free software.
10  * For more information on the license, see COPYING.
11  * For more information on free software, see <https://www.gnu.org/philosophy/free-sw.en.html>.
12  */
13 
14 #include <glib.h>
15 #include <yaml.h>
16 
17 #include "modulemd-dependencies.h"
18 #include "modulemd-errors.h"
19 #include "private/glib-extensions.h"
20 #include "private/modulemd-dependencies-private.h"
21 #include "private/modulemd-util.h"
22 #include "private/modulemd-yaml.h"
23 
24 struct _ModulemdDependencies
25 {
26   GObject parent_instance;
27 
28   /* @key: dependent modules.
29    * @value: #GHashTable set of compatible streams
30    */
31   GHashTable *buildtime_deps;
32 
33   /* @key: dependent modules.
34    * @value: #GHashTable set of compatible streams
35    */
36   GHashTable *runtime_deps;
37 };
38 
G_DEFINE_TYPE(ModulemdDependencies,modulemd_dependencies,G_TYPE_OBJECT)39 G_DEFINE_TYPE (ModulemdDependencies, modulemd_dependencies, G_TYPE_OBJECT)
40 
41 ModulemdDependencies *
42 modulemd_dependencies_new (void)
43 {
44   return g_object_new (MODULEMD_TYPE_DEPENDENCIES, NULL);
45 }
46 
47 
48 gboolean
modulemd_dependencies_equals(ModulemdDependencies * self_1,ModulemdDependencies * self_2)49 modulemd_dependencies_equals (ModulemdDependencies *self_1,
50                               ModulemdDependencies *self_2)
51 {
52   if (!self_1 && !self_2)
53     {
54       return TRUE;
55     }
56 
57   if (!self_1 || !self_2)
58     {
59       return FALSE;
60     }
61 
62   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self_1), FALSE);
63   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self_2), FALSE);
64 
65   if (!modulemd_hash_table_equals (self_1->buildtime_deps,
66                                    self_2->buildtime_deps,
67                                    modulemd_hash_table_sets_are_equal_wrapper))
68     {
69       return FALSE;
70     }
71 
72   if (!modulemd_hash_table_equals (self_1->runtime_deps,
73                                    self_2->runtime_deps,
74                                    modulemd_hash_table_sets_are_equal_wrapper))
75     {
76       return FALSE;
77     }
78 
79   return TRUE;
80 }
81 
82 
83 ModulemdDependencies *
modulemd_dependencies_copy(ModulemdDependencies * self)84 modulemd_dependencies_copy (ModulemdDependencies *self)
85 {
86   g_autoptr (ModulemdDependencies) d = NULL;
87   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self), NULL);
88 
89   d = modulemd_dependencies_new ();
90 
91   g_hash_table_unref (d->buildtime_deps);
92   d->buildtime_deps = g_hash_table_ref (self->buildtime_deps);
93   g_hash_table_unref (d->runtime_deps);
94   d->runtime_deps = g_hash_table_ref (self->runtime_deps);
95 
96   return g_steal_pointer (&d);
97 }
98 
99 
100 static void
modulemd_dependencies_finalize(GObject * object)101 modulemd_dependencies_finalize (GObject *object)
102 {
103   ModulemdDependencies *self = (ModulemdDependencies *)object;
104 
105   g_clear_pointer (&self->buildtime_deps, g_hash_table_unref);
106   g_clear_pointer (&self->runtime_deps, g_hash_table_unref);
107 
108   G_OBJECT_CLASS (modulemd_dependencies_parent_class)->finalize (object);
109 }
110 
111 
112 static GHashTable *
modulemd_dependencies_nested_table_get_or_create(GHashTable * table,const gchar * key)113 modulemd_dependencies_nested_table_get_or_create (GHashTable *table,
114                                                   const gchar *key)
115 {
116   g_autofree gchar *keyi = NULL;
117 
118   GHashTable *inner = NULL;
119   inner = g_hash_table_lookup (table, key);
120   if (inner != NULL)
121     {
122       return inner;
123     }
124 
125   // We know that the hash table will end up holding on to it for us.
126   keyi = g_strdup (key);
127 
128   inner = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
129   g_hash_table_insert (table, keyi, inner);
130   keyi = NULL;
131   return inner;
132 }
133 
134 
135 static void
modulemd_dependencies_nested_table_add(GHashTable * table,const gchar * key,const gchar * value)136 modulemd_dependencies_nested_table_add (GHashTable *table,
137                                         const gchar *key,
138                                         const gchar *value)
139 {
140   GHashTable *inner =
141     modulemd_dependencies_nested_table_get_or_create (table, key);
142   g_return_if_fail (inner);
143   if (value != NULL)
144     {
145       g_hash_table_add (inner, g_strdup (value));
146     }
147 }
148 
149 
150 static GStrv
modulemd_dependencies_nested_table_values_as_strv(GHashTable * table,const gchar * key)151 modulemd_dependencies_nested_table_values_as_strv (GHashTable *table,
152                                                    const gchar *key)
153 {
154   GHashTable *inner = g_hash_table_lookup (table, key);
155   if (inner == NULL)
156     {
157       g_warning ("Streams requested for unknown module: %s", key);
158       return NULL;
159     }
160   return modulemd_ordered_str_keys_as_strv (inner);
161 }
162 
163 
164 void
modulemd_dependencies_add_buildtime_stream(ModulemdDependencies * self,const gchar * module_name,const gchar * module_stream)165 modulemd_dependencies_add_buildtime_stream (ModulemdDependencies *self,
166                                             const gchar *module_name,
167                                             const gchar *module_stream)
168 {
169   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
170   g_return_if_fail (module_name);
171   g_return_if_fail (module_stream);
172   modulemd_dependencies_nested_table_add (
173     self->buildtime_deps, module_name, module_stream);
174 }
175 
176 
177 void
modulemd_dependencies_set_empty_buildtime_dependencies_for_module(ModulemdDependencies * self,const gchar * module_name)178 modulemd_dependencies_set_empty_buildtime_dependencies_for_module (
179   ModulemdDependencies *self, const gchar *module_name)
180 {
181   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
182   g_return_if_fail (module_name);
183   modulemd_dependencies_nested_table_add (
184     self->buildtime_deps, module_name, NULL);
185 }
186 
187 
188 void
modulemd_dependencies_clear_buildtime_dependencies(ModulemdDependencies * self)189 modulemd_dependencies_clear_buildtime_dependencies (ModulemdDependencies *self)
190 {
191   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
192   g_hash_table_remove_all (self->buildtime_deps);
193 }
194 
195 
196 GStrv
modulemd_dependencies_get_buildtime_modules_as_strv(ModulemdDependencies * self)197 modulemd_dependencies_get_buildtime_modules_as_strv (
198   ModulemdDependencies *self)
199 {
200   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self), NULL);
201   return modulemd_ordered_str_keys_as_strv (self->buildtime_deps);
202 }
203 
204 
205 GStrv
modulemd_dependencies_get_buildtime_streams_as_strv(ModulemdDependencies * self,const gchar * module)206 modulemd_dependencies_get_buildtime_streams_as_strv (
207   ModulemdDependencies *self, const gchar *module)
208 {
209   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self), NULL);
210   return modulemd_dependencies_nested_table_values_as_strv (
211     self->buildtime_deps, module);
212 }
213 
214 
215 void
modulemd_dependencies_add_runtime_stream(ModulemdDependencies * self,const gchar * module_name,const gchar * module_stream)216 modulemd_dependencies_add_runtime_stream (ModulemdDependencies *self,
217                                           const gchar *module_name,
218                                           const gchar *module_stream)
219 {
220   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
221   g_return_if_fail (module_name);
222   g_return_if_fail (module_stream);
223   modulemd_dependencies_nested_table_add (
224     self->runtime_deps, module_name, module_stream);
225 }
226 
227 
228 void
modulemd_dependencies_set_empty_runtime_dependencies_for_module(ModulemdDependencies * self,const gchar * module_name)229 modulemd_dependencies_set_empty_runtime_dependencies_for_module (
230   ModulemdDependencies *self, const gchar *module_name)
231 {
232   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
233   g_return_if_fail (module_name);
234   modulemd_dependencies_nested_table_add (
235     self->runtime_deps, module_name, NULL);
236 }
237 
238 
239 void
modulemd_dependencies_clear_runtime_dependencies(ModulemdDependencies * self)240 modulemd_dependencies_clear_runtime_dependencies (ModulemdDependencies *self)
241 {
242   g_return_if_fail (MODULEMD_IS_DEPENDENCIES (self));
243   g_hash_table_remove_all (self->runtime_deps);
244 }
245 
246 
247 GStrv
modulemd_dependencies_get_runtime_modules_as_strv(ModulemdDependencies * self)248 modulemd_dependencies_get_runtime_modules_as_strv (ModulemdDependencies *self)
249 {
250   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self), NULL);
251   return modulemd_ordered_str_keys_as_strv (self->runtime_deps);
252 }
253 
254 
255 GStrv
modulemd_dependencies_get_runtime_streams_as_strv(ModulemdDependencies * self,const gchar * module)256 modulemd_dependencies_get_runtime_streams_as_strv (ModulemdDependencies *self,
257                                                    const gchar *module)
258 {
259   g_return_val_if_fail (MODULEMD_IS_DEPENDENCIES (self), NULL);
260   return modulemd_dependencies_nested_table_values_as_strv (self->runtime_deps,
261                                                             module);
262 }
263 
264 
265 static gboolean
modulemd_dependencies_validate_deps(GHashTable * deps,GError ** error)266 modulemd_dependencies_validate_deps (GHashTable *deps, GError **error)
267 {
268   GHashTableIter iter;
269   gpointer key;
270   gpointer value;
271   gchar *module_name = NULL;
272   gchar *stream_name = NULL;
273   gssize signedness = 0;
274   g_autoptr (GPtrArray) set = NULL;
275 
276   g_hash_table_iter_init (&iter, deps);
277   while (g_hash_table_iter_next (&iter, &key, &value))
278     {
279       module_name = (gchar *)key;
280       /* The value is a set of strings. Get it and check them all */
281       set = modulemd_ordered_str_keys (value, modulemd_strcmp_sort);
282 
283       /* An empty set is always valid */
284       if (set->len == 0)
285         {
286           g_clear_pointer (&set, g_ptr_array_unref);
287           continue;
288         }
289 
290       /* The first element will determine the signedness for the whole
291        * set.
292        */
293       if (((const gchar *)g_ptr_array_index (set, 0))[0] == '-')
294         {
295           signedness = -1;
296         }
297       else
298         {
299           signedness = 1;
300         }
301 
302       for (guint i = 1; i < set->len; i++)
303         {
304           stream_name = (gchar *)g_ptr_array_index (set, i);
305           if ((stream_name[0] == '-' && signedness > 0) ||
306               (stream_name[0] != '-' && signedness < 0))
307             {
308               g_set_error (error,
309                            MODULEMD_ERROR,
310                            MMD_ERROR_VALIDATE,
311                            "Runtime dependency %s contained a mix of positive "
312                            "and negative entries.",
313                            module_name);
314               return FALSE;
315             }
316         }
317 
318       g_clear_pointer (&set, g_ptr_array_unref);
319     }
320 
321   return TRUE;
322 }
323 
324 
325 gboolean
modulemd_dependencies_validate(ModulemdDependencies * self,GError ** error)326 modulemd_dependencies_validate (ModulemdDependencies *self, GError **error)
327 {
328   /* Look through all the runtime dependencies */
329   if (!modulemd_dependencies_validate_deps (self->runtime_deps, error))
330     {
331       return FALSE;
332     }
333 
334   if (!modulemd_dependencies_validate_deps (self->buildtime_deps, error))
335     {
336       return FALSE;
337     }
338 
339   return TRUE;
340 }
341 
342 
343 static void
modulemd_dependencies_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)344 modulemd_dependencies_get_property (GObject *object,
345                                     guint prop_id,
346                                     GValue *value,
347                                     GParamSpec *pspec)
348 {
349   switch (prop_id)
350     {
351     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
352     }
353 }
354 
355 
356 static void
modulemd_dependencies_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)357 modulemd_dependencies_set_property (GObject *object,
358                                     guint prop_id,
359                                     const GValue *value,
360                                     GParamSpec *pspec)
361 {
362   switch (prop_id)
363     {
364     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
365     }
366 }
367 
368 
369 static void
modulemd_dependencies_class_init(ModulemdDependenciesClass * klass)370 modulemd_dependencies_class_init (ModulemdDependenciesClass *klass)
371 {
372   GObjectClass *object_class = G_OBJECT_CLASS (klass);
373 
374   object_class->finalize = modulemd_dependencies_finalize;
375   object_class->get_property = modulemd_dependencies_get_property;
376   object_class->set_property = modulemd_dependencies_set_property;
377 }
378 
379 
380 static void
modulemd_dependencies_init(ModulemdDependencies * self)381 modulemd_dependencies_init (ModulemdDependencies *self)
382 {
383   self->buildtime_deps = g_hash_table_new_full (
384     g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy);
385   self->runtime_deps = g_hash_table_new_full (
386     g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy);
387 }
388 
389 /* === YAML Functions === */
390 
391 ModulemdDependencies *
modulemd_dependencies_parse_yaml(yaml_parser_t * parser,gboolean strict,GError ** error)392 modulemd_dependencies_parse_yaml (yaml_parser_t *parser,
393                                   gboolean strict,
394                                   GError **error)
395 {
396   MODULEMD_INIT_TRACE ();
397   MMD_INIT_YAML_EVENT (event);
398   gboolean done = FALSE;
399   g_autoptr (ModulemdDependencies) d = NULL;
400   g_autoptr (GError) nested_error = NULL;
401 
402   d = modulemd_dependencies_new ();
403 
404   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
405 
406   while (!done)
407     {
408       YAML_PARSER_PARSE_WITH_EXIT (parser, &event, error);
409 
410       switch (event.type)
411         {
412         case YAML_MAPPING_END_EVENT: done = TRUE; break;
413 
414         case YAML_SCALAR_EVENT:
415           if (g_str_equal (event.data.scalar.value, "buildrequires"))
416             {
417               g_hash_table_unref (d->buildtime_deps);
418               d->buildtime_deps =
419                 modulemd_yaml_parse_nested_set (parser, &nested_error);
420               if (d->buildtime_deps == NULL)
421                 {
422                   MMD_YAML_ERROR_EVENT_EXIT (
423                     error,
424                     event,
425                     "Failed to parse buildtime deps: %s",
426                     nested_error->message);
427                 }
428             }
429           else if (g_str_equal (event.data.scalar.value, "requires"))
430             {
431               g_hash_table_unref (d->runtime_deps);
432               d->runtime_deps =
433                 modulemd_yaml_parse_nested_set (parser, &nested_error);
434               if (d->runtime_deps == NULL)
435                 {
436                   MMD_YAML_ERROR_EVENT_EXIT (
437                     error,
438                     event,
439                     "Failed to parse runtime deps: %s",
440                     nested_error->message);
441                 }
442             }
443           else
444             {
445               SKIP_UNKNOWN (parser,
446                             NULL,
447                             "Unexpected key in dependencies body: %s",
448                             (const gchar *)event.data.scalar.value);
449               break;
450             }
451           break;
452 
453         default:
454           MMD_YAML_ERROR_EVENT_EXIT (
455             error,
456             event,
457             "Unexpected YAML event in dependencies: %d",
458             event.type);
459           break;
460         }
461 
462       yaml_event_delete (&event);
463     }
464   return g_steal_pointer (&d);
465 }
466 
467 
468 gboolean
modulemd_dependencies_emit_yaml(ModulemdDependencies * self,yaml_emitter_t * emitter,GError ** error)469 modulemd_dependencies_emit_yaml (ModulemdDependencies *self,
470                                  yaml_emitter_t *emitter,
471                                  GError **error)
472 {
473   MODULEMD_INIT_TRACE ();
474   int ret;
475   g_autoptr (GError) nested_error = NULL;
476   MMD_INIT_YAML_EVENT (event);
477 
478   ret = mmd_emitter_start_mapping (
479     emitter, YAML_BLOCK_MAPPING_STYLE, &nested_error);
480   if (!ret)
481     {
482       g_propagate_prefixed_error (error,
483                                   g_steal_pointer (&nested_error),
484                                   "Failed to start dependencies mapping: ");
485       return FALSE;
486     }
487 
488   if (g_hash_table_size (self->buildtime_deps) != 0)
489     {
490       ret = mmd_emitter_scalar (
491         emitter, "buildrequires", YAML_PLAIN_SCALAR_STYLE, &nested_error);
492       if (!ret)
493         {
494           g_propagate_prefixed_error (
495             error,
496             g_steal_pointer (&nested_error),
497             "Failed to emit dependencies buildrequires key: ");
498           return FALSE;
499         }
500 
501       ret = modulemd_yaml_emit_nested_set (
502         emitter, self->buildtime_deps, &nested_error);
503       if (!ret)
504         {
505           g_propagate_prefixed_error (
506             error,
507             g_steal_pointer (&nested_error),
508             "Failed to emit buildtime dependencies rpms: ");
509           return FALSE;
510         }
511     }
512 
513   if (g_hash_table_size (self->runtime_deps) != 0)
514     {
515       ret = mmd_emitter_scalar (
516         emitter, "requires", YAML_PLAIN_SCALAR_STYLE, &nested_error);
517       if (!ret)
518         {
519           g_propagate_prefixed_error (
520             error,
521             g_steal_pointer (&nested_error),
522             "Failed to emit dependencies run-requires key: ");
523           return FALSE;
524         }
525 
526       ret = modulemd_yaml_emit_nested_set (
527         emitter, self->runtime_deps, &nested_error);
528       if (!ret)
529         {
530           g_propagate_prefixed_error (
531             error,
532             g_steal_pointer (&nested_error),
533             "Failed to emit runtime dependencies rpms: ");
534           return FALSE;
535         }
536     }
537 
538   ret = mmd_emitter_end_mapping (emitter, &nested_error);
539   if (!ret)
540     {
541       g_propagate_prefixed_error (error,
542                                   g_steal_pointer (&nested_error),
543                                   "Failed to end dependencies mapping");
544       return FALSE;
545     }
546   return TRUE;
547 }
548 
549 
550 static gboolean
requires_module_and_stream(GHashTable * modules,const gchar * module_name,const gchar * stream_name)551 requires_module_and_stream (GHashTable *modules,
552                             const gchar *module_name,
553                             const gchar *stream_name)
554 {
555   GHashTable *streams = NULL;
556   GHashTableIter iter;
557   gpointer key;
558   gpointer value;
559   g_autofree gchar *negated = NULL;
560 
561   streams = g_hash_table_lookup (modules, module_name);
562   /* If the module doesn't appear at all, return false */
563   if (!streams)
564     {
565       return FALSE;
566     }
567 
568   /* Check whether this module is the empty set (which means "all streams") */
569   if (g_hash_table_size (streams) == 0)
570     {
571       return TRUE;
572     }
573 
574   /* Check whether it includes the stream name explicitly */
575   if (g_hash_table_contains (streams, stream_name))
576     {
577       return TRUE;
578     }
579 
580 
581   /* Get the first item from the table and check if it's a negation */
582   negated = g_strdup_printf ("-%s", stream_name);
583 
584   g_hash_table_iter_init (&iter, streams);
585 
586   /* We already checked that this hash table is not empty, so if iterating it
587    * fails, something has gone horribly wrong. Wrap this in a
588    * g_return_val_if_fail() to make static analysis happy.
589    */
590   g_return_val_if_fail (g_hash_table_iter_next (&iter, &key, &value), FALSE);
591 
592   /* If we have a negative value for any entry, they all must be negative.
593    * Check whether we're explicitly excluding the requested stream */
594   if (((const gchar *)key)[0] == '-' &&
595       (!g_hash_table_contains (streams, negated)))
596     {
597       return TRUE;
598     }
599 
600   return FALSE;
601 }
602 
603 
604 gboolean
modulemd_dependencies_requires_module_and_stream(ModulemdDependencies * self,const gchar * module_name,const gchar * stream_name)605 modulemd_dependencies_requires_module_and_stream (ModulemdDependencies *self,
606                                                   const gchar *module_name,
607                                                   const gchar *stream_name)
608 {
609   return requires_module_and_stream (
610     self->runtime_deps, module_name, stream_name);
611 }
612 
613 
614 gboolean
modulemd_dependencies_buildrequires_module_and_stream(ModulemdDependencies * self,const gchar * module_name,const gchar * stream_name)615 modulemd_dependencies_buildrequires_module_and_stream (
616   ModulemdDependencies *self,
617   const gchar *module_name,
618   const gchar *stream_name)
619 {
620   return requires_module_and_stream (
621     self->buildtime_deps, module_name, stream_name);
622 }
623