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-buildopts.h"
18 #include "private/glib-extensions.h"
19 #include "private/modulemd-buildopts-private.h"
20 #include "private/modulemd-util.h"
21 #include "private/modulemd-yaml.h"
22 
23 #define B_DEFAULT_STRING "__BUILDOPTS_RPM_MACROS_UNSET__"
24 
25 struct _ModulemdBuildopts
26 {
27   GObject parent_instance;
28 
29   gchar *rpm_macros;
30 
31   GHashTable *whitelist;
32   GHashTable *arches;
33 };
34 
35 G_DEFINE_TYPE (ModulemdBuildopts, modulemd_buildopts, G_TYPE_OBJECT)
36 
37 enum
38 {
39   PROP_0,
40 
41   PROP_RPM_MACROS,
42 
43   N_PROPS
44 };
45 
46 static GParamSpec *properties[N_PROPS];
47 
48 
49 gboolean
modulemd_buildopts_equals(ModulemdBuildopts * self_1,ModulemdBuildopts * self_2)50 modulemd_buildopts_equals (ModulemdBuildopts *self_1,
51                            ModulemdBuildopts *self_2)
52 {
53   if (!self_1 && !self_2)
54     {
55       return TRUE;
56     }
57 
58   if (!self_1 || !self_2)
59     {
60       return FALSE;
61     }
62 
63   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self_1), FALSE);
64   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self_2), FALSE);
65 
66   if (g_strcmp0 (self_1->rpm_macros, self_2->rpm_macros) != 0)
67     {
68       return FALSE;
69     }
70 
71   if (!modulemd_hash_table_sets_are_equal (self_1->whitelist,
72                                            self_2->whitelist))
73     {
74       return FALSE;
75     }
76 
77   if (!modulemd_hash_table_sets_are_equal (self_1->arches, self_2->arches))
78     {
79       return FALSE;
80     }
81 
82   return TRUE;
83 }
84 
85 gint
modulemd_buildopts_compare(ModulemdBuildopts * self_1,ModulemdBuildopts * self_2)86 modulemd_buildopts_compare (ModulemdBuildopts *self_1,
87                             ModulemdBuildopts *self_2)
88 {
89   gint cmp;
90 
91   if (!self_1 && !self_2)
92     {
93       return 0;
94     }
95 
96   if (!self_1)
97     {
98       return -1;
99     }
100 
101   if (!self_2)
102     {
103       return 1;
104     }
105 
106   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self_1), 1);
107   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self_2), -1);
108 
109   cmp = g_strcmp0 (self_1->rpm_macros, self_2->rpm_macros);
110   if (cmp != 0)
111     {
112       return cmp;
113     }
114 
115   cmp =
116     modulemd_hash_table_compare (self_1->whitelist, self_2->whitelist, NULL);
117   if (cmp != 0)
118     {
119       return cmp;
120     }
121 
122   cmp = modulemd_hash_table_compare (self_1->arches, self_2->arches, NULL);
123   if (cmp != 0)
124     {
125       return cmp;
126     }
127 
128   return 0;
129 }
130 
131 
132 ModulemdBuildopts *
modulemd_buildopts_new(void)133 modulemd_buildopts_new (void)
134 {
135   return g_object_new (MODULEMD_TYPE_BUILDOPTS, NULL);
136 }
137 
138 
139 ModulemdBuildopts *
modulemd_buildopts_copy(ModulemdBuildopts * self)140 modulemd_buildopts_copy (ModulemdBuildopts *self)
141 {
142   g_autoptr (ModulemdBuildopts) copy = NULL;
143   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self), NULL);
144 
145   copy = modulemd_buildopts_new ();
146 
147   modulemd_buildopts_set_rpm_macros (copy,
148                                      modulemd_buildopts_get_rpm_macros (self));
149 
150   MODULEMD_REPLACE_SET (copy->whitelist, self->whitelist);
151   MODULEMD_REPLACE_SET (copy->arches, self->arches);
152 
153   return g_steal_pointer (&copy);
154 }
155 
156 
157 static void
modulemd_buildopts_finalize(GObject * object)158 modulemd_buildopts_finalize (GObject *object)
159 {
160   ModulemdBuildopts *self = (ModulemdBuildopts *)object;
161 
162   g_clear_pointer (&self->rpm_macros, g_free);
163   g_clear_pointer (&self->whitelist, g_hash_table_unref);
164   g_clear_pointer (&self->arches, g_hash_table_unref);
165 
166   G_OBJECT_CLASS (modulemd_buildopts_parent_class)->finalize (object);
167 }
168 
169 
170 void
modulemd_buildopts_set_rpm_macros(ModulemdBuildopts * self,const gchar * rpm_macros)171 modulemd_buildopts_set_rpm_macros (ModulemdBuildopts *self,
172                                    const gchar *rpm_macros)
173 {
174   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
175 
176   g_clear_pointer (&self->rpm_macros, g_free);
177   self->rpm_macros = g_strdup (rpm_macros);
178 
179   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RPM_MACROS]);
180 }
181 
182 
183 const gchar *
modulemd_buildopts_get_rpm_macros(ModulemdBuildopts * self)184 modulemd_buildopts_get_rpm_macros (ModulemdBuildopts *self)
185 {
186   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self), NULL);
187 
188   return self->rpm_macros;
189 }
190 
191 
192 void
modulemd_buildopts_add_rpm_to_whitelist(ModulemdBuildopts * self,const gchar * rpm)193 modulemd_buildopts_add_rpm_to_whitelist (ModulemdBuildopts *self,
194                                          const gchar *rpm)
195 {
196   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
197   g_hash_table_add (self->whitelist, g_strdup (rpm));
198 }
199 
200 
201 void
modulemd_buildopts_remove_rpm_from_whitelist(ModulemdBuildopts * self,const gchar * rpm)202 modulemd_buildopts_remove_rpm_from_whitelist (ModulemdBuildopts *self,
203                                               const gchar *rpm)
204 {
205   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
206   g_hash_table_remove (self->whitelist, rpm);
207 }
208 
209 void
modulemd_buildopts_clear_rpm_whitelist(ModulemdBuildopts * self)210 modulemd_buildopts_clear_rpm_whitelist (ModulemdBuildopts *self)
211 {
212   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
213   g_hash_table_remove_all (self->whitelist);
214 }
215 
216 
217 GStrv
modulemd_buildopts_get_rpm_whitelist_as_strv(ModulemdBuildopts * self)218 modulemd_buildopts_get_rpm_whitelist_as_strv (ModulemdBuildopts *self)
219 {
220   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self), NULL);
221 
222   return modulemd_ordered_str_keys_as_strv (self->whitelist);
223 }
224 
225 
226 void
modulemd_buildopts_add_arch(ModulemdBuildopts * self,const gchar * arch)227 modulemd_buildopts_add_arch (ModulemdBuildopts *self, const gchar *arch)
228 {
229   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
230   g_hash_table_add (self->arches, g_strdup (arch));
231 }
232 
233 
234 void
modulemd_buildopts_remove_arch(ModulemdBuildopts * self,const gchar * arch)235 modulemd_buildopts_remove_arch (ModulemdBuildopts *self, const gchar *arch)
236 {
237   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
238   g_hash_table_remove (self->arches, arch);
239 }
240 
241 
242 void
modulemd_buildopts_clear_arches(ModulemdBuildopts * self)243 modulemd_buildopts_clear_arches (ModulemdBuildopts *self)
244 {
245   g_return_if_fail (MODULEMD_IS_BUILDOPTS (self));
246   g_hash_table_remove_all (self->arches);
247 }
248 
249 
250 GStrv
modulemd_buildopts_get_arches_as_strv(ModulemdBuildopts * self)251 modulemd_buildopts_get_arches_as_strv (ModulemdBuildopts *self)
252 {
253   g_return_val_if_fail (MODULEMD_IS_BUILDOPTS (self), NULL);
254 
255   return modulemd_ordered_str_keys_as_strv (self->arches);
256 }
257 
258 
259 static void
modulemd_buildopts_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)260 modulemd_buildopts_get_property (GObject *object,
261                                  guint prop_id,
262                                  GValue *value,
263                                  GParamSpec *pspec)
264 {
265   ModulemdBuildopts *self = MODULEMD_BUILDOPTS (object);
266 
267   switch (prop_id)
268     {
269     case PROP_RPM_MACROS:
270       g_value_set_string (value, modulemd_buildopts_get_rpm_macros (self));
271       break;
272     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
273     }
274 }
275 
276 
277 static void
modulemd_buildopts_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)278 modulemd_buildopts_set_property (GObject *object,
279                                  guint prop_id,
280                                  const GValue *value,
281                                  GParamSpec *pspec)
282 {
283   ModulemdBuildopts *self = MODULEMD_BUILDOPTS (object);
284 
285   switch (prop_id)
286     {
287     case PROP_RPM_MACROS:
288       modulemd_buildopts_set_rpm_macros (self, g_value_get_string (value));
289       break;
290     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291     }
292 }
293 
294 
295 static void
modulemd_buildopts_class_init(ModulemdBuildoptsClass * klass)296 modulemd_buildopts_class_init (ModulemdBuildoptsClass *klass)
297 {
298   GObjectClass *object_class = G_OBJECT_CLASS (klass);
299 
300   object_class->finalize = modulemd_buildopts_finalize;
301   object_class->get_property = modulemd_buildopts_get_property;
302   object_class->set_property = modulemd_buildopts_set_property;
303 
304   properties[PROP_RPM_MACROS] = g_param_spec_string (
305     "rpm-macros",
306     "Rpm Macros",
307     "A string containing RPM build macros in the form that they would appear "
308     "in an RPM macros file on-disk.",
309     B_DEFAULT_STRING,
310     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
311 
312   g_object_class_install_properties (object_class, N_PROPS, properties);
313 }
314 
315 
316 static void
modulemd_buildopts_init(ModulemdBuildopts * self)317 modulemd_buildopts_init (ModulemdBuildopts *self)
318 {
319   self->whitelist =
320     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
321   self->arches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
322 }
323 
324 
325 /* === YAML Functions === */
326 
327 static gboolean
328 modulemd_buildopts_parse_rpm_buildopts (yaml_parser_t *parser,
329                                         ModulemdBuildopts *buildopts,
330                                         gboolean strict,
331                                         GError **error);
332 
333 ModulemdBuildopts *
modulemd_buildopts_parse_yaml(yaml_parser_t * parser,gboolean strict,GError ** error)334 modulemd_buildopts_parse_yaml (yaml_parser_t *parser,
335                                gboolean strict,
336                                GError **error)
337 {
338   MODULEMD_INIT_TRACE ();
339   MMD_INIT_YAML_EVENT (event);
340   gboolean done = FALSE;
341   gboolean in_map = FALSE;
342   g_autoptr (ModulemdBuildopts) buildopts = NULL;
343   g_autoptr (GError) nested_error = NULL;
344 
345   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
346 
347   buildopts = modulemd_buildopts_new ();
348 
349   while (!done)
350     {
351       YAML_PARSER_PARSE_WITH_EXIT (parser, &event, error);
352 
353       switch (event.type)
354         {
355         case YAML_MAPPING_START_EVENT: in_map = TRUE; break;
356 
357         case YAML_MAPPING_END_EVENT:
358           in_map = FALSE;
359           done = TRUE;
360           break;
361 
362         case YAML_SCALAR_EVENT:
363           if (!in_map)
364             {
365               MMD_YAML_ERROR_EVENT_EXIT_BOOL (
366                 error, event, "Missing mapping in buildopts");
367             }
368 
369           if (g_str_equal ((const gchar *)event.data.scalar.value, "rpms"))
370             {
371               if (!modulemd_buildopts_parse_rpm_buildopts (
372                     parser, buildopts, strict, &nested_error))
373                 {
374                   g_propagate_error (error, nested_error);
375                   return NULL;
376                 }
377             }
378           else if (g_str_equal (event.data.scalar.value, "arches"))
379             {
380               g_hash_table_unref (buildopts->arches);
381               buildopts->arches =
382                 modulemd_yaml_parse_string_set (parser, &nested_error);
383               if (buildopts->arches == NULL)
384                 {
385                   MMD_YAML_ERROR_EVENT_EXIT_BOOL (
386                     error,
387                     event,
388                     "Failed to parse arches list in buildopts: %s",
389                     nested_error->message);
390                 }
391             }
392           else
393             {
394               SKIP_UNKNOWN (parser,
395                             NULL,
396                             "Unexpected key in buildopts: %s",
397                             (const gchar *)event.data.scalar.value);
398               break;
399             }
400           break;
401 
402         default:
403           MMD_YAML_ERROR_EVENT_EXIT (error,
404                                      event,
405                                      "Unexpected YAML event in buildopts: %s",
406                                      mmd_yaml_get_event_name (event.type));
407           break;
408         }
409 
410       yaml_event_delete (&event);
411     }
412   return g_steal_pointer (&buildopts);
413 }
414 
415 static gboolean
modulemd_buildopts_parse_rpm_buildopts(yaml_parser_t * parser,ModulemdBuildopts * buildopts,gboolean strict,GError ** error)416 modulemd_buildopts_parse_rpm_buildopts (yaml_parser_t *parser,
417                                         ModulemdBuildopts *buildopts,
418                                         gboolean strict,
419                                         GError **error)
420 {
421   MODULEMD_INIT_TRACE ();
422   MMD_INIT_YAML_EVENT (event);
423   gboolean done = FALSE;
424   gboolean in_map = FALSE;
425   g_autofree gchar *value = NULL;
426   g_autoptr (GError) nested_error = NULL;
427 
428   /* Read in RPM attributes */
429   while (!done)
430     {
431       YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
432 
433       switch (event.type)
434         {
435         case YAML_MAPPING_START_EVENT: in_map = TRUE; break;
436 
437         case YAML_MAPPING_END_EVENT:
438           in_map = FALSE;
439           done = TRUE;
440           break;
441 
442         case YAML_SCALAR_EVENT:
443           if (!in_map)
444             {
445               MMD_YAML_ERROR_EVENT_EXIT_BOOL (
446                 error, event, "Missing mapping in buildopts rpms entry");
447             }
448 
449           if (g_str_equal (event.data.scalar.value, "whitelist"))
450             {
451               g_hash_table_unref (buildopts->whitelist);
452               buildopts->whitelist =
453                 modulemd_yaml_parse_string_set (parser, &nested_error);
454               if (buildopts->whitelist == NULL)
455                 {
456                   MMD_YAML_ERROR_EVENT_EXIT_BOOL (
457                     error,
458                     event,
459                     "Failed to parse whitelist list in buildopts rpms: %s",
460                     nested_error->message);
461                 }
462             }
463           else if (g_str_equal (event.data.scalar.value, "macros"))
464             {
465               value = modulemd_yaml_parse_string (parser, &nested_error);
466               if (!value)
467                 {
468                   MMD_YAML_ERROR_EVENT_EXIT_BOOL (
469                     error,
470                     event,
471                     "Failed to parse rpm_macros in buildopts: %s",
472                     nested_error->message);
473                 }
474               modulemd_buildopts_set_rpm_macros (buildopts, value);
475               g_clear_pointer (&value, g_free);
476             }
477           else
478             {
479               SKIP_UNKNOWN (parser,
480                             FALSE,
481                             "Unexpected key in buildopts body: %s",
482                             (const gchar *)event.data.scalar.value);
483             }
484           break;
485 
486         default:
487           MMD_YAML_ERROR_EVENT_EXIT_BOOL (
488             error,
489             event,
490             "Unexpected YAML event in rpm buildopts: %s",
491             mmd_yaml_get_event_name (event.type));
492           break;
493         }
494 
495       yaml_event_delete (&event);
496     }
497   return TRUE;
498 }
499 
500 
501 gboolean
modulemd_buildopts_emit_yaml(ModulemdBuildopts * self,yaml_emitter_t * emitter,GError ** error)502 modulemd_buildopts_emit_yaml (ModulemdBuildopts *self,
503                               yaml_emitter_t *emitter,
504                               GError **error)
505 {
506   MODULEMD_INIT_TRACE ();
507   int ret;
508   g_auto (GStrv) list = NULL;
509   g_autoptr (GError) nested_error = NULL;
510   MMD_INIT_YAML_EVENT (event);
511 
512   ret = mmd_emitter_scalar (
513     emitter, "rpms", YAML_PLAIN_SCALAR_STYLE, &nested_error);
514   if (!ret)
515     {
516       g_propagate_prefixed_error (
517         error,
518         g_steal_pointer (&nested_error),
519         "Failed to emit buildopts 'rpms' constant: ");
520       return FALSE;
521     }
522 
523   ret = mmd_emitter_start_mapping (
524     emitter, YAML_BLOCK_MAPPING_STYLE, &nested_error);
525   if (!ret)
526     {
527       g_propagate_prefixed_error (error,
528                                   g_steal_pointer (&nested_error),
529                                   "Failed to start buildopts mapping: ");
530       return FALSE;
531     }
532 
533   if (modulemd_buildopts_get_rpm_macros (self) != NULL)
534     {
535       ret = mmd_emitter_scalar (
536         emitter, "macros", YAML_PLAIN_SCALAR_STYLE, &nested_error);
537       if (!ret)
538         {
539           g_propagate_prefixed_error (error,
540                                       g_steal_pointer (&nested_error),
541                                       "Failed to emit buildopts macros key: ");
542           return FALSE;
543         }
544 
545       ret = mmd_emitter_scalar (emitter,
546                                 modulemd_buildopts_get_rpm_macros (self),
547                                 YAML_FOLDED_SCALAR_STYLE,
548                                 &nested_error);
549       if (!ret)
550         {
551           g_propagate_prefixed_error (
552             error,
553             g_steal_pointer (&nested_error),
554             "Failed to emit buildopts macros value: ");
555           return FALSE;
556         }
557     }
558 
559   if (g_hash_table_size (self->whitelist) != 0)
560     {
561       ret = mmd_emitter_scalar (
562         emitter, "whitelist", YAML_PLAIN_SCALAR_STYLE, &nested_error);
563       if (!ret)
564         {
565           g_propagate_prefixed_error (
566             error,
567             g_steal_pointer (&nested_error),
568             "Failed to emit buildopts whitelist key: ");
569           return FALSE;
570         }
571 
572       list = modulemd_buildopts_get_rpm_whitelist_as_strv (self);
573 
574       ret = mmd_emitter_strv (
575         emitter, YAML_BLOCK_SEQUENCE_STYLE, list, &nested_error);
576       if (!ret)
577         {
578           g_propagate_prefixed_error (error,
579                                       g_steal_pointer (&nested_error),
580                                       "Failed to emit buildopts whitelist: ");
581           return FALSE;
582         }
583 
584       g_clear_pointer (&list, g_strfreev);
585     }
586 
587   ret = mmd_emitter_end_mapping (emitter, &nested_error);
588   if (!ret)
589     {
590       g_propagate_prefixed_error (error,
591                                   g_steal_pointer (&nested_error),
592                                   "Failed to end buildopts mapping");
593       return FALSE;
594     }
595 
596   if (g_hash_table_size (self->arches) != 0)
597     {
598       ret = mmd_emitter_scalar (
599         emitter, "arches", YAML_PLAIN_SCALAR_STYLE, &nested_error);
600       if (!ret)
601         {
602           g_propagate_prefixed_error (error,
603                                       g_steal_pointer (&nested_error),
604                                       "Failed to emit buildopts arches key: ");
605           return FALSE;
606         }
607 
608       list = modulemd_buildopts_get_arches_as_strv (self);
609 
610       ret = mmd_emitter_strv (
611         emitter, YAML_FLOW_SEQUENCE_STYLE, list, &nested_error);
612       if (!ret)
613         {
614           g_propagate_prefixed_error (error,
615                                       g_steal_pointer (&nested_error),
616                                       "Failed to emit buildopts arches: ");
617           return FALSE;
618         }
619 
620       g_clear_pointer (&list, g_strfreev);
621     }
622 
623   return TRUE;
624 }
625