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