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 "modulemd.h"
15 #include "config.h"
16 
17 #include "private/modulemd-module-stream-private.h"
18 #include "private/modulemd-subdocument-info-private.h"
19 #include "private/modulemd-packager-v3-private.h"
20 
21 #include <errno.h>
22 #include <inttypes.h>
23 
24 const gchar *
modulemd_get_version(void)25 modulemd_get_version (void)
26 {
27   return LIBMODULEMD_VERSION;
28 }
29 
30 
31 static ModulemdModuleIndex *
32 verify_load (int ret,
33              ModulemdModuleIndex *idx,
34              GPtrArray *failures,
35              GError **error,
36              GError **nested_error);
37 
38 ModulemdModuleIndex *
modulemd_load_file(const gchar * yaml_file,GError ** error)39 modulemd_load_file (const gchar *yaml_file, GError **error)
40 {
41   gboolean ret;
42   g_autoptr (ModulemdModuleIndex) idx = NULL;
43   g_autoptr (GError) nested_error = NULL;
44   g_autoptr (GPtrArray) failures = NULL;
45 
46   g_return_val_if_fail (yaml_file, NULL);
47   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
48 
49   failures = g_ptr_array_new_with_free_func (g_object_unref);
50   idx = modulemd_module_index_new ();
51 
52   ret = modulemd_module_index_update_from_file (
53     idx, yaml_file, FALSE, &failures, &nested_error);
54   return verify_load (ret, idx, failures, error, &nested_error);
55 }
56 
57 
58 ModulemdModuleIndex *
modulemd_load_string(const gchar * yaml_string,GError ** error)59 modulemd_load_string (const gchar *yaml_string, GError **error)
60 {
61   gboolean ret;
62   g_autoptr (ModulemdModuleIndex) idx = NULL;
63   g_autoptr (GError) nested_error = NULL;
64   g_autoptr (GPtrArray) failures = NULL;
65 
66   g_return_val_if_fail (yaml_string, NULL);
67   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
68 
69   failures = g_ptr_array_new_with_free_func (g_object_unref);
70   idx = modulemd_module_index_new ();
71 
72   ret = modulemd_module_index_update_from_string (
73     idx, yaml_string, FALSE, &failures, &nested_error);
74   return verify_load (ret, idx, failures, error, &nested_error);
75 }
76 
77 
78 static ModulemdModuleIndex *
verify_load(gboolean ret,ModulemdModuleIndex * idx,GPtrArray * failures,GError ** error,GError ** nested_error)79 verify_load (gboolean ret,
80              ModulemdModuleIndex *idx,
81              GPtrArray *failures,
82              GError **error,
83              GError **nested_error)
84 {
85   if (!ret)
86     {
87       if (*nested_error)
88         {
89           g_propagate_error (error, g_steal_pointer (nested_error));
90           return NULL;
91         }
92       else if (failures && failures->len)
93         {
94           modulemd_subdocument_info_debug_dump_failures (failures);
95           g_set_error (error,
96                        MODULEMD_ERROR,
97                        MMD_ERROR_VALIDATE,
98                        "One or more YAML subdocuments were invalid.");
99           return NULL;
100         }
101 
102       /* Should never get here, must be a programming error */
103       g_set_error (
104         error, MODULEMD_ERROR, MMD_ERROR_VALIDATE, "Unknown internal error.");
105       g_return_val_if_reached (NULL);
106     }
107 
108   return g_object_ref (idx);
109 }
110 
111 static GType
112 modulemd_read_packager_from_parser (yaml_parser_t *parser,
113                                     GObject **object,
114                                     const gchar *module_name,
115                                     const gchar *module_stream,
116                                     GError **error);
117 
118 GType
modulemd_read_packager_file(const gchar * yaml_path,GObject ** object,GError ** error)119 modulemd_read_packager_file (const gchar *yaml_path,
120                              GObject **object,
121                              GError **error)
122 {
123   return modulemd_read_packager_file_ext (
124     yaml_path, object, NULL, NULL, error);
125 }
126 
127 GType
modulemd_read_packager_file_ext(const gchar * yaml_path,GObject ** object,const gchar * module_name,const gchar * module_stream,GError ** error)128 modulemd_read_packager_file_ext (const gchar *yaml_path,
129                                  GObject **object,
130                                  const gchar *module_name,
131                                  const gchar *module_stream,
132                                  GError **error)
133 {
134   MMD_INIT_YAML_PARSER (parser);
135   g_autoptr (FILE) yaml_stream = NULL;
136   gint err;
137 
138   g_return_val_if_fail (yaml_path, G_TYPE_INVALID);
139   g_return_val_if_fail (object, G_TYPE_INVALID);
140   g_return_val_if_fail (error == NULL || *error == NULL, G_TYPE_INVALID);
141 
142   errno = 0;
143   yaml_stream = g_fopen (yaml_path, "rbe");
144   err = errno;
145 
146   if (!yaml_stream)
147     {
148       g_set_error (error,
149                    MODULEMD_YAML_ERROR,
150                    MMD_YAML_ERROR_OPEN,
151                    "%s",
152                    g_strerror (err));
153       return G_TYPE_INVALID;
154     }
155 
156   yaml_parser_set_input_file (&parser, yaml_stream);
157 
158   return modulemd_read_packager_from_parser (
159     &parser, object, module_name, module_stream, error);
160 }
161 
162 GType
modulemd_read_packager_string(const gchar * yaml_string,GObject ** object,GError ** error)163 modulemd_read_packager_string (const gchar *yaml_string,
164                                GObject **object,
165                                GError **error)
166 {
167   return modulemd_read_packager_string_ext (
168     yaml_string, object, NULL, NULL, error);
169 }
170 
171 GType
modulemd_read_packager_string_ext(const gchar * yaml_string,GObject ** object,const gchar * module_name,const gchar * module_stream,GError ** error)172 modulemd_read_packager_string_ext (const gchar *yaml_string,
173                                    GObject **object,
174                                    const gchar *module_name,
175                                    const gchar *module_stream,
176                                    GError **error)
177 {
178   MMD_INIT_YAML_PARSER (parser);
179 
180   g_return_val_if_fail (yaml_string, G_TYPE_INVALID);
181   g_return_val_if_fail (object, G_TYPE_INVALID);
182   g_return_val_if_fail (error == NULL || *error == NULL, G_TYPE_INVALID);
183 
184   yaml_parser_set_input_string (
185     &parser, (const unsigned char *)yaml_string, strlen (yaml_string));
186 
187   return modulemd_read_packager_from_parser (
188     &parser, object, module_name, module_stream, error);
189 }
190 
191 static GType
modulemd_read_packager_from_parser(yaml_parser_t * parser,GObject ** object,const gchar * module_name,const gchar * module_stream,GError ** error)192 modulemd_read_packager_from_parser (yaml_parser_t *parser,
193                                     GObject **object,
194                                     const gchar *module_name,
195                                     const gchar *module_stream,
196                                     GError **error)
197 {
198   MMD_INIT_YAML_EVENT (event);
199   g_autoptr (ModulemdModuleStreamV1) stream_v1 = NULL;
200   g_autoptr (ModulemdModuleStreamV2) stream_v2 = NULL;
201   g_autoptr (ModulemdPackagerV3) packager_v3 = NULL;
202   g_autoptr (ModulemdSubdocumentInfo) subdoc = NULL;
203   ModulemdYamlDocumentTypeEnum doctype;
204   const GError *gerror = NULL;
205   g_autoptr (GObject) return_object = NULL;
206   GType return_type = G_TYPE_INVALID;
207 
208   /* The first event must be the stream start */
209   if (!yaml_parser_parse (parser, &event))
210     {
211       g_set_error_literal (error,
212                            MODULEMD_YAML_ERROR,
213                            MMD_YAML_ERROR_UNPARSEABLE,
214                            "Parser error");
215       return G_TYPE_INVALID;
216     }
217   if (event.type != YAML_STREAM_START_EVENT)
218     {
219       g_set_error_literal (error,
220                            MODULEMD_YAML_ERROR,
221                            MMD_YAML_ERROR_PARSE,
222                            "YAML didn't begin with STREAM_START.");
223       return G_TYPE_INVALID;
224     }
225   yaml_event_delete (&event);
226 
227   /* The second event must be the document start */
228   if (!yaml_parser_parse (parser, &event))
229     {
230       g_set_error_literal (error,
231                            MODULEMD_YAML_ERROR,
232                            MMD_YAML_ERROR_UNPARSEABLE,
233                            "Parser error");
234       return G_TYPE_INVALID;
235     }
236   if (event.type != YAML_DOCUMENT_START_EVENT)
237     {
238       g_set_error_literal (error,
239                            MODULEMD_YAML_ERROR,
240                            MMD_YAML_ERROR_PARSE,
241                            "YAML didn't begin with STREAM_START.");
242       return G_TYPE_INVALID;
243     }
244   yaml_event_delete (&event);
245 
246   subdoc = modulemd_yaml_parse_document_type (parser);
247   gerror = modulemd_subdocument_info_get_gerror (subdoc);
248   if (gerror)
249     {
250       g_set_error (error,
251                    gerror->domain,
252                    gerror->code,
253                    "Parse error identifying document type and version: %s",
254                    gerror->message);
255       return G_TYPE_INVALID;
256     }
257 
258 
259   doctype = modulemd_subdocument_info_get_doctype (subdoc);
260 
261   switch (doctype)
262     {
263     case MODULEMD_YAML_DOC_PACKAGER:
264       if (modulemd_subdocument_info_get_mdversion (subdoc) <
265           MD_PACKAGER_VERSION_TWO)
266         {
267           g_set_error (error,
268                        MODULEMD_YAML_ERROR,
269                        MMD_YAML_ERROR_PARSE,
270                        "Invalid mdversion for a packager document");
271           return G_TYPE_INVALID;
272         }
273 
274       if (modulemd_subdocument_info_get_mdversion (subdoc) ==
275           MD_PACKAGER_VERSION_THREE)
276         {
277           packager_v3 = modulemd_packager_v3_parse_yaml (subdoc, error);
278           if (!packager_v3)
279             {
280               return G_TYPE_INVALID;
281             }
282 
283           if (module_name)
284             {
285               modulemd_packager_v3_set_module_name (packager_v3, module_name);
286             }
287 
288           if (module_stream)
289             {
290               modulemd_packager_v3_set_stream_name (packager_v3,
291                                                     module_stream);
292             }
293 
294           return_object = (GObject *)g_steal_pointer (&packager_v3);
295           return_type = MODULEMD_TYPE_PACKAGER_V3;
296           break;
297         }
298 
299       /* Falling through intentionally: packager V2 format is handled below */
300 
301     case MODULEMD_YAML_DOC_MODULESTREAM:
302       switch (modulemd_subdocument_info_get_mdversion (subdoc))
303         {
304         case MD_MODULESTREAM_VERSION_ONE:
305           stream_v1 =
306             modulemd_module_stream_v1_parse_yaml (subdoc, FALSE, error);
307           if (!stream_v1)
308             {
309               return G_TYPE_INVALID;
310             }
311 
312           stream_v2 = MODULEMD_MODULE_STREAM_V2 (
313             modulemd_module_stream_upgrade_v1_to_v2 (
314               MODULEMD_MODULE_STREAM (stream_v1)));
315           if (!stream_v2)
316             {
317               /* This should be impossible, since there are no failure returns
318                * from modulemd_module_stream_upgrade_v1_to_v2()
319                */
320               g_set_error (error,
321                            MODULEMD_ERROR,
322                            MMD_ERROR_UPGRADE,
323                            "Upgrading to v2 failed for an unknown reason");
324               return G_TYPE_INVALID;
325             }
326 
327           if (module_name)
328             {
329               modulemd_module_stream_set_module_name (
330                 MODULEMD_MODULE_STREAM (stream_v2), module_name);
331             }
332 
333           if (module_stream)
334             {
335               modulemd_module_stream_set_stream_name (
336                 MODULEMD_MODULE_STREAM (stream_v2), module_stream);
337             }
338 
339           return_object = (GObject *)g_steal_pointer (&stream_v2);
340           return_type = MODULEMD_TYPE_MODULE_STREAM_V2;
341           break;
342 
343         case MD_MODULESTREAM_VERSION_TWO:
344           stream_v2 = modulemd_module_stream_v2_parse_yaml (
345             subdoc, FALSE, doctype == MODULEMD_YAML_DOC_PACKAGER, error);
346           if (!stream_v2)
347             {
348               return G_TYPE_INVALID;
349             }
350 
351           if (module_name)
352             {
353               modulemd_module_stream_set_module_name (
354                 MODULEMD_MODULE_STREAM (stream_v2), module_name);
355             }
356 
357           if (module_stream)
358             {
359               modulemd_module_stream_set_stream_name (
360                 MODULEMD_MODULE_STREAM (stream_v2), module_stream);
361             }
362 
363           return_object = (GObject *)g_steal_pointer (&stream_v2);
364           return_type = MODULEMD_TYPE_MODULE_STREAM_V2;
365           break;
366 
367         default:
368           g_set_error (error,
369                        MODULEMD_YAML_ERROR,
370                        MMD_YAML_ERROR_PARSE,
371                        "Invalid mdversion (%" PRIu64
372                        ") for a modulemd[-stream] document",
373                        modulemd_subdocument_info_get_mdversion (subdoc));
374           return G_TYPE_INVALID;
375         }
376       break;
377 
378     default:
379       g_set_error (
380         error,
381         MODULEMD_YAML_ERROR,
382         MMD_YAML_ERROR_PARSE,
383         "Expected `document: modulemd[-stream] or modulemd-packager`, got %d",
384         modulemd_subdocument_info_get_doctype (subdoc));
385       return G_TYPE_INVALID;
386     }
387 
388   /* The last event must be the stream end */
389   if (!yaml_parser_parse (parser, &event))
390     {
391       g_set_error_literal (error,
392                            MODULEMD_YAML_ERROR,
393                            MMD_YAML_ERROR_UNPARSEABLE,
394                            "Parser error");
395       return G_TYPE_INVALID;
396     }
397 
398   if (event.type != YAML_STREAM_END_EVENT)
399     {
400       g_set_error_literal (error,
401                            MODULEMD_YAML_ERROR,
402                            MMD_YAML_ERROR_PARSE,
403                            "YAML contained more than a single subdocument");
404       return G_TYPE_INVALID;
405     }
406   yaml_event_delete (&event);
407 
408   *object = g_steal_pointer (&return_object);
409   return return_type;
410 }
411