1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 * GObject introspection: Dump introspection data
3 *
4 * Copyright (C) 2008 Colin Walters <walters@verbum.org>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-or-later
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 */
23
24 /* This file is both compiled into libgirepository.so, and installed
25 * on the filesystem. But for the dumper, we want to avoid linking
26 * to libgirepository; see
27 * https://bugzilla.gnome.org/show_bug.cgi?id=630342
28 */
29 #ifdef GI_COMPILATION
30 #include "config.h"
31 #include "girepository.h"
32 #endif
33
34 #include <glib.h>
35 #include <glib-object.h>
36 #include <gmodule.h>
37
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41
42 /* Analogue of g_output_stream_write_all(). */
43 static gboolean
44 write_all (FILE *out,
45 const void *buffer,
46 gsize count,
47 gsize *bytes_written,
48 GError **error)
49 {
50 size_t ret;
51
52 ret = fwrite (buffer, 1, count, out);
53
54 if (bytes_written != NULL)
55 *bytes_written = ret;
56
57 if (ret < count)
58 {
59 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
60 "Failed to write to file");
61 return FALSE;
62 }
63
64 return TRUE;
65 }
66
67 /* Analogue of g_data_input_stream_read_line(). */
68 static char *
69 read_line (FILE *input,
70 size_t *len_out)
71 {
72 GByteArray *buffer = g_byte_array_new ();
73 const guint8 nul = '\0';
74
75 while (TRUE)
76 {
77 size_t ret;
78 guint8 byte;
79
80 ret = fread (&byte, 1, 1, input);
81 if (ret == 0)
82 break;
83
84 if (byte == '\n')
85 break;
86
87 g_byte_array_append (buffer, &byte, 1);
88 }
89
90 g_byte_array_append (buffer, &nul, 1);
91
92 if (len_out != NULL)
93 *len_out = buffer->len - 1; /* don’t include terminating nul */
94
95 return (char *) g_byte_array_free (buffer, FALSE);
96 }
97
98 static void
99 escaped_printf (FILE *out, const char *fmt, ...) G_GNUC_PRINTF (2, 3);
100
101 static void
102 escaped_printf (FILE *out, const char *fmt, ...)
103 {
104 char *str;
105 va_list args;
106 gsize written;
107 GError *error = NULL;
108
109 va_start (args, fmt);
110
111 str = g_markup_vprintf_escaped (fmt, args);
112 if (!write_all (out, str, strlen (str), &written, &error))
113 {
114 g_critical ("failed to write to iochannel: %s", error->message);
115 g_clear_error (&error);
116 }
117 g_free (str);
118
119 va_end (args);
120 }
121
122 static void
123 goutput_write (FILE *out, const char *str)
124 {
125 gsize written;
126 GError *error = NULL;
127 if (!write_all (out, str, strlen (str), &written, &error))
128 {
129 g_critical ("failed to write to iochannel: %s", error->message);
130 g_clear_error (&error);
131 }
132 }
133
134 typedef GType (*GetTypeFunc)(void);
135 typedef GQuark (*ErrorQuarkFunc)(void);
136
137 static GType
138 invoke_get_type (GModule *self, const char *symbol, GError **error)
139 {
140 GetTypeFunc sym;
141 GType ret;
142
143 if (!g_module_symbol (self, symbol, (void**)&sym))
144 {
145 g_set_error (error,
146 G_FILE_ERROR,
147 G_FILE_ERROR_FAILED,
148 "Failed to find symbol '%s'", symbol);
149 return G_TYPE_INVALID;
150 }
151
152 ret = sym ();
153 if (ret == G_TYPE_INVALID)
154 {
155 g_set_error (error,
156 G_FILE_ERROR,
157 G_FILE_ERROR_FAILED,
158 "Function '%s' returned G_TYPE_INVALID", symbol);
159 }
160 return ret;
161 }
162
163 static GQuark
164 invoke_error_quark (GModule *self, const char *symbol, GError **error)
165 {
166 ErrorQuarkFunc sym;
167
168 if (!g_module_symbol (self, symbol, (void**)&sym))
169 {
170 g_set_error (error,
171 G_FILE_ERROR,
172 G_FILE_ERROR_FAILED,
173 "Failed to find symbol '%s'", symbol);
174 return G_TYPE_INVALID;
175 }
176
177 return sym ();
178 }
179
180 static char *
181 value_transform_to_string (const GValue *value)
182 {
183 GValue tmp = G_VALUE_INIT;
184 char *s = NULL;
185
186 g_value_init (&tmp, G_TYPE_STRING);
187
188 if (g_value_transform (value, &tmp))
189 {
190 const char *str = g_value_get_string (&tmp);
191
192 if (str != NULL)
193 s = g_strescape (str, NULL);
194 }
195
196 g_value_unset (&tmp);
197
198 return s;
199 }
200
201 /* A simpler version of g_strdup_value_contents(), but with stable
202 * output and less complex semantics
203 */
204 static char *
205 value_to_string (const GValue *value)
206 {
207 if (value == NULL)
208 return NULL;
209
210 if (G_VALUE_HOLDS_STRING (value))
211 {
212 const char *s = g_value_get_string (value);
213
214 if (s == NULL)
215 return g_strdup ("NULL");
216
217 return g_strescape (s, NULL);
218 }
219 else
220 {
221 GType value_type = G_VALUE_TYPE (value);
222
223 switch (G_TYPE_FUNDAMENTAL (value_type))
224 {
225 case G_TYPE_BOXED:
226 if (g_value_get_boxed (value) == NULL)
227 return NULL;
228 else
229 return value_transform_to_string (value);
230 break;
231
232 case G_TYPE_OBJECT:
233 if (g_value_get_object (value) == NULL)
234 return NULL;
235 else
236 return value_transform_to_string (value);
237 break;
238
239 case G_TYPE_POINTER:
240 return NULL;
241
242 default:
243 return value_transform_to_string (value);
244 }
245 }
246
247 return NULL;
248 }
249
250 static void
251 dump_properties (GType type, FILE *out)
252 {
253 guint i;
254 guint n_properties = 0;
255 GParamSpec **props;
256
257 if (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT)
258 {
259 GObjectClass *klass;
260 klass = g_type_class_ref (type);
261 props = g_object_class_list_properties (klass, &n_properties);
262 }
263 else
264 {
265 void *klass;
266 klass = g_type_default_interface_ref (type);
267 props = g_object_interface_list_properties (klass, &n_properties);
268 }
269
270 for (i = 0; i < n_properties; i++)
271 {
272 GParamSpec *prop;
273
274 prop = props[i];
275 if (prop->owner_type != type)
276 continue;
277
278 const GValue *v = g_param_spec_get_default_value (prop);
279 char *default_value = value_to_string (v);
280
281 if (v != NULL && default_value != NULL)
282 {
283 escaped_printf (out, " <property name=\"%s\" type=\"%s\" flags=\"%d\" default-value=\"%s\"/>\n",
284 prop->name,
285 g_type_name (prop->value_type),
286 prop->flags,
287 default_value);
288 }
289 else
290 {
291 escaped_printf (out, " <property name=\"%s\" type=\"%s\" flags=\"%d\"/>\n",
292 prop->name,
293 g_type_name (prop->value_type),
294 prop->flags);
295 }
296
297 g_free (default_value);
298 }
299
300 g_free (props);
301 }
302
303 static void
304 dump_signals (GType type, FILE *out)
305 {
306 guint i;
307 guint n_sigs;
308 guint *sig_ids;
309
310 sig_ids = g_signal_list_ids (type, &n_sigs);
311 for (i = 0; i < n_sigs; i++)
312 {
313 guint sigid;
314 GSignalQuery query;
315 guint j;
316
317 sigid = sig_ids[i];
318 g_signal_query (sigid, &query);
319
320 escaped_printf (out, " <signal name=\"%s\" return=\"%s\"",
321 query.signal_name, g_type_name (query.return_type));
322
323 if (query.signal_flags & G_SIGNAL_RUN_FIRST)
324 escaped_printf (out, " when=\"first\"");
325 else if (query.signal_flags & G_SIGNAL_RUN_LAST)
326 escaped_printf (out, " when=\"last\"");
327 else if (query.signal_flags & G_SIGNAL_RUN_CLEANUP)
328 escaped_printf (out, " when=\"cleanup\"");
329 else if (query.signal_flags & G_SIGNAL_MUST_COLLECT)
330 escaped_printf (out, " when=\"must-collect\"");
331 if (query.signal_flags & G_SIGNAL_NO_RECURSE)
332 escaped_printf (out, " no-recurse=\"1\"");
333
334 if (query.signal_flags & G_SIGNAL_DETAILED)
335 escaped_printf (out, " detailed=\"1\"");
336
337 if (query.signal_flags & G_SIGNAL_ACTION)
338 escaped_printf (out, " action=\"1\"");
339
340 if (query.signal_flags & G_SIGNAL_NO_HOOKS)
341 escaped_printf (out, " no-hooks=\"1\"");
342
343 goutput_write (out, ">\n");
344
345 for (j = 0; j < query.n_params; j++)
346 {
347 escaped_printf (out, " <param type=\"%s\"/>\n",
348 g_type_name (query.param_types[j]));
349 }
350 goutput_write (out, " </signal>\n");
351 }
352 g_free (sig_ids);
353 }
354
355 static void
356 dump_object_type (GType type, const char *symbol, FILE *out)
357 {
358 guint n_interfaces;
359 guint i;
360 GType *interfaces;
361
362 escaped_printf (out, " <class name=\"%s\" get-type=\"%s\"",
363 g_type_name (type), symbol);
364 if (type != G_TYPE_OBJECT)
365 {
366 GString *parent_str;
367 GType parent;
368 gboolean first = TRUE;
369
370 parent = g_type_parent (type);
371 parent_str = g_string_new ("");
372 while (parent != G_TYPE_INVALID)
373 {
374 if (first)
375 first = FALSE;
376 else
377 g_string_append_c (parent_str, ',');
378 g_string_append (parent_str, g_type_name (parent));
379 parent = g_type_parent (parent);
380 }
381
382 escaped_printf (out, " parents=\"%s\"", parent_str->str);
383
384 g_string_free (parent_str, TRUE);
385 }
386
387 if (G_TYPE_IS_ABSTRACT (type))
388 escaped_printf (out, " abstract=\"1\"");
389
390 if (G_TYPE_IS_FINAL (type))
391 escaped_printf (out, " final=\"1\"");
392
393 goutput_write (out, ">\n");
394
395 interfaces = g_type_interfaces (type, &n_interfaces);
396 for (i = 0; i < n_interfaces; i++)
397 {
398 GType itype = interfaces[i];
399 escaped_printf (out, " <implements name=\"%s\"/>\n",
400 g_type_name (itype));
401 }
402 g_free (interfaces);
403
404 dump_properties (type, out);
405 dump_signals (type, out);
406 goutput_write (out, " </class>\n");
407 }
408
409 static void
410 dump_interface_type (GType type, const char *symbol, FILE *out)
411 {
412 guint n_interfaces;
413 guint i;
414 GType *interfaces;
415
416 escaped_printf (out, " <interface name=\"%s\" get-type=\"%s\">\n",
417 g_type_name (type), symbol);
418
419 interfaces = g_type_interface_prerequisites (type, &n_interfaces);
420 for (i = 0; i < n_interfaces; i++)
421 {
422 GType itype = interfaces[i];
423 if (itype == G_TYPE_OBJECT)
424 {
425 /* Treat this as implicit for now; in theory GInterfaces are
426 * supported on things like GstMiniObject, but right now
427 * the introspection system only supports GObject.
428 * http://bugzilla.gnome.org/show_bug.cgi?id=559706
429 */
430 continue;
431 }
432 escaped_printf (out, " <prerequisite name=\"%s\"/>\n",
433 g_type_name (itype));
434 }
435 g_free (interfaces);
436
437 dump_properties (type, out);
438 dump_signals (type, out);
439 goutput_write (out, " </interface>\n");
440 }
441
442 static void
443 dump_boxed_type (GType type, const char *symbol, FILE *out)
444 {
445 escaped_printf (out, " <boxed name=\"%s\" get-type=\"%s\"/>\n",
446 g_type_name (type), symbol);
447 }
448
449 static void
450 dump_flags_type (GType type, const char *symbol, FILE *out)
451 {
452 guint i;
453 GFlagsClass *klass;
454
455 klass = g_type_class_ref (type);
456 escaped_printf (out, " <flags name=\"%s\" get-type=\"%s\">\n",
457 g_type_name (type), symbol);
458
459 for (i = 0; i < klass->n_values; i++)
460 {
461 GFlagsValue *value = &(klass->values[i]);
462
463 escaped_printf (out, " <member name=\"%s\" nick=\"%s\" value=\"%u\"/>\n",
464 value->value_name, value->value_nick, value->value);
465 }
466 goutput_write (out, " </flags>\n");
467 }
468
469 static void
470 dump_enum_type (GType type, const char *symbol, FILE *out)
471 {
472 guint i;
473 GEnumClass *klass;
474
475 klass = g_type_class_ref (type);
476 escaped_printf (out, " <enum name=\"%s\" get-type=\"%s\">\n",
477 g_type_name (type), symbol);
478
479 for (i = 0; i < klass->n_values; i++)
480 {
481 GEnumValue *value = &(klass->values[i]);
482
483 escaped_printf (out, " <member name=\"%s\" nick=\"%s\" value=\"%d\"/>\n",
484 value->value_name, value->value_nick, value->value);
485 }
486 goutput_write (out, " </enum>");
487 }
488
489 static void
490 dump_fundamental_type (GType type, const char *symbol, FILE *out)
491 {
492 guint n_interfaces;
493 guint i;
494 GType *interfaces;
495 GString *parent_str;
496 GType parent;
497 gboolean first = TRUE;
498
499
500 escaped_printf (out, " <fundamental name=\"%s\" get-type=\"%s\"",
501 g_type_name (type), symbol);
502
503 if (G_TYPE_IS_ABSTRACT (type))
504 escaped_printf (out, " abstract=\"1\"");
505
506 if (G_TYPE_IS_FINAL (type))
507 escaped_printf (out, " final=\"1\"");
508
509 if (G_TYPE_IS_INSTANTIATABLE (type))
510 escaped_printf (out, " instantiatable=\"1\"");
511
512 parent = g_type_parent (type);
513 parent_str = g_string_new ("");
514 while (parent != G_TYPE_INVALID)
515 {
516 if (first)
517 first = FALSE;
518 else
519 g_string_append_c (parent_str, ',');
520 if (!g_type_name (parent))
521 break;
522 g_string_append (parent_str, g_type_name (parent));
523 parent = g_type_parent (parent);
524 }
525
526 if (parent_str->len > 0)
527 escaped_printf (out, " parents=\"%s\"", parent_str->str);
528 g_string_free (parent_str, TRUE);
529
530 goutput_write (out, ">\n");
531
532 interfaces = g_type_interfaces (type, &n_interfaces);
533 for (i = 0; i < n_interfaces; i++)
534 {
535 GType itype = interfaces[i];
536 escaped_printf (out, " <implements name=\"%s\"/>\n",
537 g_type_name (itype));
538 }
539 g_free (interfaces);
540 goutput_write (out, " </fundamental>\n");
541 }
542
543 static void
544 dump_type (GType type, const char *symbol, FILE *out)
545 {
546 switch (g_type_fundamental (type))
547 {
548 case G_TYPE_OBJECT:
549 dump_object_type (type, symbol, out);
550 break;
551 case G_TYPE_INTERFACE:
552 dump_interface_type (type, symbol, out);
553 break;
554 case G_TYPE_BOXED:
555 dump_boxed_type (type, symbol, out);
556 break;
557 case G_TYPE_FLAGS:
558 dump_flags_type (type, symbol, out);
559 break;
560 case G_TYPE_ENUM:
561 dump_enum_type (type, symbol, out);
562 break;
563 case G_TYPE_POINTER:
564 /* GValue, etc. Just skip them. */
565 break;
566 default:
567 dump_fundamental_type (type, symbol, out);
568 break;
569 }
570 }
571
572 static void
573 dump_error_quark (GQuark quark, const char *symbol, FILE *out)
574 {
575 escaped_printf (out, " <error-quark function=\"%s\" domain=\"%s\"/>\n",
576 symbol, g_quark_to_string (quark));
577 }
578
579 /**
580 * gi_repository_dump:
581 * @input_filename: (type filename): Input filename (for example `input.txt`)
582 * @output_filename: (type filename): Output filename (for example `output.xml`)
583 * @error: a %GError
584 *
585 * Dump the introspection data from the types specified in @input_filename to
586 * @output_filename.
587 *
588 * The input file should be a
589 * UTF-8 Unix-line-ending text file, with each line containing either
590 * `get-type:` followed by the name of a [type@GObject.Type] `_get_type`
591 * function, or `error-quark:` followed by the name of an error quark function.
592 * No extra whitespace is allowed.
593 *
594 * This function will overwrite the contents of the output file.
595 *
596 * Returns: true on success, false on error
597 * Since: 2.80
598 */
599 #ifndef GI_COMPILATION
600 static gboolean
601 dump_irepository (const char *input_filename,
602 const char *output_filename,
603 GError **error) G_GNUC_UNUSED;
604 static gboolean
605 dump_irepository (const char *input_filename,
606 const char *output_filename,
607 GError **error)
608 #else
609 gboolean
610 gi_repository_dump (const char *input_filename,
611 const char *output_filename,
612 GError **error)
613 #endif
614 {
615 GHashTable *output_types;
616 FILE *input;
617 FILE *output;
618 GModule *self;
619 gboolean caught_error = FALSE;
620
621 self = g_module_open (NULL, 0);
622 if (!self)
623 {
624 g_set_error (error,
625 G_FILE_ERROR,
626 G_FILE_ERROR_FAILED,
627 "failed to open self: %s",
628 g_module_error ());
629 return FALSE;
630 }
631
632 input = fopen (input_filename, "rb");
633 if (input == NULL)
634 {
635 int saved_errno = errno;
636 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
637 "Failed to open ‘%s’: %s", input_filename, g_strerror (saved_errno));
638
639 g_module_close (self);
640
641 return FALSE;
642 }
643
644 output = fopen (output_filename, "wb");
645 if (output == NULL)
646 {
647 int saved_errno = errno;
648 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
649 "Failed to open ‘%s’: %s", output_filename, g_strerror (saved_errno));
650
651 fclose (input);
652 g_module_close (self);
653
654 return FALSE;
655 }
656
657 goutput_write (output, "<?xml version=\"1.0\"?>\n");
658 goutput_write (output, "<dump>\n");
659
660 output_types = g_hash_table_new (NULL, NULL);
661
662 while (TRUE)
663 {
664 gsize len;
665 char *line = read_line (input, &len);
666 const char *function;
667
668 if (line == NULL || *line == '\0')
669 {
670 g_free (line);
671 break;
672 }
673
674 g_strchomp (line);
675
676 if (strncmp (line, "get-type:", strlen ("get-type:")) == 0)
677 {
678 GType type;
679
680 function = line + strlen ("get-type:");
681
682 type = invoke_get_type (self, function, error);
683
684 if (type == G_TYPE_INVALID)
685 {
686 g_printerr ("Invalid GType function: '%s'\n", function);
687 caught_error = TRUE;
688 g_free (line);
689 break;
690 }
691
692 if (g_hash_table_lookup (output_types, (gpointer) type))
693 goto next;
694 g_hash_table_insert (output_types, (gpointer) type, (gpointer) type);
695
696 dump_type (type, function, output);
697 }
698 else if (strncmp (line, "error-quark:", strlen ("error-quark:")) == 0)
699 {
700 GQuark quark;
701 function = line + strlen ("error-quark:");
702 quark = invoke_error_quark (self, function, error);
703
704 if (quark == 0)
705 {
706 g_printerr ("Invalid error quark function: '%s'\n", function);
707 caught_error = TRUE;
708 g_free (line);
709 break;
710 }
711
712 dump_error_quark (quark, function, output);
713 }
714
715
716 next:
717 g_free (line);
718 }
719
720 g_hash_table_destroy (output_types);
721
722 goutput_write (output, "</dump>\n");
723
724 {
725 /* Avoid overwriting an earlier set error */
726 if (fclose (input) != 0 && !caught_error)
727 {
728 int saved_errno = errno;
729
730 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
731 "Error closing input file ‘%s’: %s", input_filename,
732 g_strerror (saved_errno));
733 caught_error = TRUE;
734 }
735
736 if (fclose (output) != 0 && !caught_error)
737 {
738 int saved_errno = errno;
739
740 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
741 "Error closing output file ‘%s’: %s", output_filename,
742 g_strerror (saved_errno));
743 caught_error = TRUE;
744 }
745 }
746
747 return !caught_error;
748 }