1 /*
2  * This file is part of libmodulemd
3  * Copyright (C) 2017-2018 Stephen Gallagher
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-service-level.h"
18 #include "private/glib-extensions.h"
19 #include "private/modulemd-service-level-private.h"
20 #include "private/modulemd-util.h"
21 #include "private/modulemd-yaml.h"
22 
23 #define SL_DEFAULT_STRING "__NAME_UNSET__"
24 
25 struct _ModulemdServiceLevel
26 {
27   GObject parent_instance;
28 
29   gchar *name;
30   GDate *eol;
31 };
32 
33 G_DEFINE_TYPE (ModulemdServiceLevel, modulemd_service_level, G_TYPE_OBJECT)
34 
35 enum
36 {
37   PROP_0,
38 
39   PROP_NAME,
40 
41   N_PROPS
42 };
43 
44 static GParamSpec *properties[N_PROPS];
45 
46 
47 ModulemdServiceLevel *
modulemd_service_level_new(const gchar * name)48 modulemd_service_level_new (const gchar *name)
49 {
50   // clang-format off
51   return g_object_new (MODULEMD_TYPE_SERVICE_LEVEL,
52                        "name", name,
53                        NULL);
54   // clang-format on
55 }
56 
57 
58 gboolean
modulemd_service_level_equals_wrapper(const void * a,const void * b)59 modulemd_service_level_equals_wrapper (const void *a, const void *b)
60 {
61   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL ((ModulemdServiceLevel *)a),
62                         FALSE);
63   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL ((ModulemdServiceLevel *)b),
64                         FALSE);
65 
66   return modulemd_service_level_equals ((ModulemdServiceLevel *)a,
67                                         (ModulemdServiceLevel *)b);
68 }
69 
70 
71 gboolean
modulemd_service_level_equals(ModulemdServiceLevel * self_1,ModulemdServiceLevel * self_2)72 modulemd_service_level_equals (ModulemdServiceLevel *self_1,
73                                ModulemdServiceLevel *self_2)
74 {
75   if (!self_1 && !self_2)
76     {
77       return TRUE;
78     }
79 
80   if (!self_1 || !self_2)
81     {
82       return FALSE;
83     }
84 
85   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL (self_1), FALSE);
86   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL (self_2), FALSE);
87 
88   if (g_strcmp0 (modulemd_service_level_get_name (self_1),
89                  modulemd_service_level_get_name (self_2)) != 0)
90     {
91       return FALSE;
92     }
93 
94   /*if both eols are invalid, its equivalent*/
95   if (!g_date_valid (self_1->eol) && !g_date_valid (self_2->eol))
96     {
97       return TRUE;
98     }
99 
100   if (!g_date_valid (self_1->eol) || !g_date_valid (self_2->eol))
101     {
102       return FALSE;
103     }
104 
105   if (g_date_compare (self_1->eol, self_2->eol) != 0)
106     {
107       return FALSE;
108     }
109 
110   return TRUE;
111 }
112 
113 
114 ModulemdServiceLevel *
modulemd_service_level_copy(ModulemdServiceLevel * self)115 modulemd_service_level_copy (ModulemdServiceLevel *self)
116 {
117   g_autoptr (ModulemdServiceLevel) sl = NULL;
118   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL (self), NULL);
119 
120   sl = modulemd_service_level_new (modulemd_service_level_get_name (self));
121 
122   modulemd_service_level_set_eol (sl, modulemd_service_level_get_eol (self));
123 
124   return g_object_ref (sl);
125 }
126 
127 
128 static void
modulemd_service_level_finalize(GObject * object)129 modulemd_service_level_finalize (GObject *object)
130 {
131   ModulemdServiceLevel *self = (ModulemdServiceLevel *)object;
132 
133   g_clear_pointer (&self->name, g_free);
134   g_clear_pointer (&self->eol, g_date_free);
135 
136   G_OBJECT_CLASS (modulemd_service_level_parent_class)->finalize (object);
137 }
138 
139 
140 static void
modulemd_service_level_set_name(ModulemdServiceLevel * self,const gchar * name)141 modulemd_service_level_set_name (ModulemdServiceLevel *self, const gchar *name)
142 {
143   g_return_if_fail (MODULEMD_IS_SERVICE_LEVEL (self));
144 
145   /* It is a coding error if we ever get a NULL name here */
146   g_return_if_fail (name);
147 
148   /* It is a coding error if we ever get the default name here */
149   g_return_if_fail (g_strcmp0 (name, SL_DEFAULT_STRING));
150 
151   g_clear_pointer (&self->name, g_free);
152   self->name = g_strdup (name);
153 
154   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]);
155 }
156 
157 
158 const gchar *
modulemd_service_level_get_name(ModulemdServiceLevel * self)159 modulemd_service_level_get_name (ModulemdServiceLevel *self)
160 {
161   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL (self), NULL);
162 
163   return self->name;
164 }
165 
166 
167 void
modulemd_service_level_set_eol(ModulemdServiceLevel * self,GDate * date)168 modulemd_service_level_set_eol (ModulemdServiceLevel *self, GDate *date)
169 {
170   g_return_if_fail (MODULEMD_IS_SERVICE_LEVEL (self));
171 
172   if (!date || !g_date_valid (date))
173     {
174       g_date_clear (self->eol, 1);
175       return;
176     }
177 
178   if (!g_date_valid (self->eol) || g_date_compare (date, self->eol) != 0)
179     {
180       /* Date is changing. Update it */
181       g_date_set_year (self->eol, g_date_get_year (date));
182       g_date_set_month (self->eol, g_date_get_month (date));
183       g_date_set_day (self->eol, g_date_get_day (date));
184     }
185 }
186 
187 
188 void
modulemd_service_level_set_eol_ymd(ModulemdServiceLevel * self,GDateYear year,GDateMonth month,GDateDay day)189 modulemd_service_level_set_eol_ymd (ModulemdServiceLevel *self,
190                                     GDateYear year,
191                                     GDateMonth month,
192                                     GDateDay day)
193 {
194   g_autoptr (GDate) date = NULL;
195   g_return_if_fail (MODULEMD_IS_SERVICE_LEVEL (self));
196 
197   if (!g_date_valid_dmy (day, month, year))
198     {
199       /* Treat invalid dates as NULL */
200       return modulemd_service_level_set_eol (self, NULL);
201     }
202 
203   date = g_date_new_dmy (day, month, year);
204   return modulemd_service_level_set_eol (self, date);
205 }
206 
207 
208 void
modulemd_service_level_remove_eol(ModulemdServiceLevel * self)209 modulemd_service_level_remove_eol (ModulemdServiceLevel *self)
210 {
211   return modulemd_service_level_set_eol (self, NULL);
212 }
213 
214 
215 GDate *
modulemd_service_level_get_eol(ModulemdServiceLevel * self)216 modulemd_service_level_get_eol (ModulemdServiceLevel *self)
217 {
218   g_return_val_if_fail (MODULEMD_IS_SERVICE_LEVEL (self), NULL);
219 
220   if (self->eol && g_date_valid (self->eol))
221     {
222       return self->eol;
223     }
224 
225   return NULL;
226 }
227 
228 
229 gchar *
modulemd_service_level_get_eol_as_string(ModulemdServiceLevel * self)230 modulemd_service_level_get_eol_as_string (ModulemdServiceLevel *self)
231 {
232   if (self->eol && g_date_valid (self->eol))
233     {
234       return g_strdup_printf ("%.4d-%.2d-%.2d",
235                               g_date_get_year (self->eol),
236                               g_date_get_month (self->eol),
237                               g_date_get_day (self->eol));
238     }
239 
240   return NULL;
241 }
242 
243 
244 static void
modulemd_service_level_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)245 modulemd_service_level_get_property (GObject *object,
246                                      guint prop_id,
247                                      GValue *value,
248                                      GParamSpec *pspec)
249 {
250   ModulemdServiceLevel *self = MODULEMD_SERVICE_LEVEL (object);
251 
252   switch (prop_id)
253     {
254     case PROP_NAME:
255       g_value_set_string (value, modulemd_service_level_get_name (self));
256       break;
257 
258     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
259     }
260 }
261 
262 
263 static void
modulemd_service_level_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)264 modulemd_service_level_set_property (GObject *object,
265                                      guint prop_id,
266                                      const GValue *value,
267                                      GParamSpec *pspec)
268 {
269   ModulemdServiceLevel *self = MODULEMD_SERVICE_LEVEL (object);
270 
271   switch (prop_id)
272     {
273     case PROP_NAME:
274       modulemd_service_level_set_name (self, g_value_get_string (value));
275       break;
276 
277     default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
278     }
279 }
280 
281 
282 static void
modulemd_service_level_class_init(ModulemdServiceLevelClass * klass)283 modulemd_service_level_class_init (ModulemdServiceLevelClass *klass)
284 {
285   GObjectClass *object_class = G_OBJECT_CLASS (klass);
286 
287   object_class->finalize = modulemd_service_level_finalize;
288   object_class->get_property = modulemd_service_level_get_property;
289   object_class->set_property = modulemd_service_level_set_property;
290 
291   properties[PROP_NAME] = g_param_spec_string (
292     "name",
293     "Name",
294     "A human-readable name for this servicelevel",
295     SL_DEFAULT_STRING,
296     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
297 
298   g_object_class_install_properties (object_class, N_PROPS, properties);
299 }
300 
301 
302 static void
modulemd_service_level_init(ModulemdServiceLevel * self)303 modulemd_service_level_init (ModulemdServiceLevel *self)
304 {
305   self->eol = g_date_new ();
306 }
307 
308 
309 /* === YAML Functions === */
310 
311 ModulemdServiceLevel *
modulemd_service_level_parse_yaml(yaml_parser_t * parser,const gchar * name,gboolean strict,GError ** error)312 modulemd_service_level_parse_yaml (yaml_parser_t *parser,
313                                    const gchar *name,
314                                    gboolean strict,
315                                    GError **error)
316 {
317   MODULEMD_INIT_TRACE ();
318   MMD_INIT_YAML_EVENT (event);
319   gboolean done = FALSE;
320   gboolean in_map = FALSE;
321   g_autoptr (ModulemdServiceLevel) sl = NULL;
322   GDate *eol = NULL;
323   g_autoptr (GError) nested_error = NULL;
324 
325   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
326 
327   sl = modulemd_service_level_new (name);
328 
329   /* Read in any supplementary attributes of the service level,
330    * such as 'eol'
331    */
332   while (!done)
333     {
334       YAML_PARSER_PARSE_WITH_EXIT (parser, &event, error);
335 
336       switch (event.type)
337         {
338         case YAML_MAPPING_START_EVENT:
339           /* This is the start of the service level content. */
340           in_map = TRUE;
341           break;
342 
343         case YAML_MAPPING_END_EVENT:
344           if (!in_map)
345             {
346               MMD_YAML_ERROR_EVENT_EXIT (
347                 error, event, "Unexpected MAPPING_END in service level");
348               break;
349             }
350           /* We're done processing the service level content */
351           in_map = FALSE;
352           done = TRUE;
353           break;
354 
355         case YAML_SCALAR_EVENT:
356           if (!in_map)
357             {
358               /* We must be in the map before we handle scalars */
359               MMD_YAML_ERROR_EVENT_EXIT (
360                 error, event, "Missing mapping in service level");
361               break;
362             }
363           /* Only "eol" is supported right now */
364           if (!g_strcmp0 ((const gchar *)event.data.scalar.value, "eol"))
365             {
366               /* Get the EOL date */
367               eol = modulemd_yaml_parse_date (parser, &nested_error);
368               if (!eol)
369                 {
370                   MMD_YAML_ERROR_EVENT_EXIT (
371                     error,
372                     event,
373                     "Failed to parse EOL date in service level: %s",
374                     nested_error->message);
375                 }
376 
377               modulemd_service_level_set_eol (sl, eol);
378               g_date_free (eol);
379             }
380           else
381             {
382               /* Unknown field in service level */
383               SKIP_UNKNOWN (parser,
384                             FALSE,
385                             "Unexpected key in service level body: %s",
386                             (const gchar *)event.data.scalar.value);
387             }
388           break;
389 
390         default:
391           /* We received a YAML event we shouldn't expect at this level */
392           MMD_YAML_ERROR_EVENT_EXIT (
393             error, event, "Unexpected YAML event in service level");
394           break;
395         }
396       yaml_event_delete (&event);
397     }
398 
399   return g_object_ref (sl);
400 }
401 
402 gboolean
modulemd_service_level_emit_yaml(ModulemdServiceLevel * self,yaml_emitter_t * emitter,GError ** error)403 modulemd_service_level_emit_yaml (ModulemdServiceLevel *self,
404                                   yaml_emitter_t *emitter,
405                                   GError **error)
406 {
407   MODULEMD_INIT_TRACE ();
408   int ret;
409   g_autoptr (GError) nested_error = NULL;
410   g_autofree gchar *eol_string = NULL;
411   MMD_INIT_YAML_EVENT (event);
412 
413   /* Emit the Service Level Name */
414   ret = mmd_emitter_scalar (emitter,
415                             modulemd_service_level_get_name (self),
416                             YAML_PLAIN_SCALAR_STYLE,
417                             &nested_error);
418   if (!ret)
419     {
420       g_propagate_prefixed_error (error,
421                                   g_steal_pointer (&nested_error),
422                                   "Failed to emit service level name: ");
423       return FALSE;
424     }
425 
426   /* Start the mapping for additional attributes of this service level */
427   ret = mmd_emitter_start_mapping (
428     emitter, YAML_BLOCK_MAPPING_STYLE, &nested_error);
429   if (!ret)
430     {
431       g_propagate_prefixed_error (error,
432                                   g_steal_pointer (&nested_error),
433                                   "Failed to start service level mapping: ");
434       return FALSE;
435     }
436 
437   /* Add service level attributes if available */
438   if (modulemd_service_level_get_eol (self) != NULL)
439     {
440       ret = mmd_emitter_scalar (
441         emitter, "eol", YAML_PLAIN_SCALAR_STYLE, &nested_error);
442       if (!ret)
443         {
444           g_propagate_prefixed_error (error,
445                                       g_steal_pointer (&nested_error),
446                                       "Failed to emit EOL key: ");
447           return FALSE;
448         }
449 
450       eol_string = modulemd_service_level_get_eol_as_string (self);
451       ret = mmd_emitter_scalar (
452         emitter, eol_string, YAML_PLAIN_SCALAR_STYLE, &nested_error);
453       if (!ret)
454         {
455           g_propagate_prefixed_error (error,
456                                       g_steal_pointer (&nested_error),
457                                       "Failed to emit EOL string [%s]: ",
458                                       eol_string);
459           return FALSE;
460         }
461     }
462 
463   /* End the mapping */
464   ret = mmd_emitter_end_mapping (emitter, &nested_error);
465   if (!ret)
466     {
467       g_propagate_prefixed_error (error,
468                                   g_steal_pointer (&nested_error),
469                                   "Failed to end service level mapping: ");
470       return FALSE;
471     }
472 
473   return TRUE;
474 }
475