/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #ifdef HAVE_GIO_UNIX #include #endif #include #include "xb-builder.h" #include "xb-silo-export.h" #include "xb-silo-query.h" #include "xb-node.h" typedef struct { GCancellable *cancellable; GMainLoop *loop; GPtrArray *cmd_array; gboolean force; gboolean wait; gboolean profile; } XbToolPrivate; static void xb_tool_private_free (XbToolPrivate *priv) { if (priv == NULL) return; if (priv->cmd_array != NULL) g_ptr_array_unref (priv->cmd_array); g_main_loop_unref (priv->loop); g_object_unref (priv->cancellable); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbToolPrivate, xb_tool_private_free) #pragma clang diagnostic pop typedef gboolean (*FuUtilPrivateCb) (XbToolPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilPrivateCb callback; } FuUtilItem; static void xb_tool_item_free (FuUtilItem *item) { g_free (item->name); g_free (item->arguments); g_free (item->description); g_free (item); } static gint xb_tool_sort_command_name_cb (FuUtilItem **item1, FuUtilItem **item2) { return g_strcmp0 ((*item1)->name, (*item2)->name); } static void xb_tool_add (GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilPrivateCb callback) { g_auto(GStrv) names = NULL; g_return_if_fail (name != NULL); g_return_if_fail (description != NULL); g_return_if_fail (callback != NULL); /* add each one */ names = g_strsplit (name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilItem *item = g_new0 (FuUtilItem, 1); item->name = g_strdup (names[i]); if (i == 0) { item->description = g_strdup (description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf ("Alias to %s", names[0]); } item->arguments = g_strdup (arguments); item->callback = callback; g_ptr_array_add (array, item); } } static void xb_tool_cancelled_cb (GCancellable *cancellable, gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *) user_data; g_print ("Cancelled!\n"); g_main_loop_quit (priv->loop); } static gchar * xb_tool_get_descriptions (GPtrArray *array) { gsize len; const gsize max_len = 31; FuUtilItem *item; GString *string; /* print each command */ string = g_string_new (""); for (guint i = 0; i < array->len; i++) { item = g_ptr_array_index (array, i); g_string_append (string, " "); g_string_append (string, item->name); len = strlen (item->name) + 2; if (item->arguments != NULL) { g_string_append (string, " "); g_string_append (string, item->arguments); len += strlen (item->arguments) + 1; } if (len < max_len) { for (guint j = len; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } else { g_string_append_c (string, '\n'); for (guint j = 0; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size (string, string->len - 1); return g_string_free (string, FALSE); } static gboolean xb_tool_run (XbToolPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < priv->cmd_array->len; i++) { FuUtilItem *item = g_ptr_array_index (priv->cmd_array, i); if (g_strcmp0 (item->name, command) == 0) return item->callback (priv, values, error); } /* not found */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command not found"); return FALSE; } static gboolean xb_tool_dump (XbToolPrivate *priv, gchar **values, GError **error) { XbSiloLoadFlags flags = XB_SILO_LOAD_FLAG_NONE; /* check args */ if (g_strv_length (values) < 1) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME" " -- e.g. `example.xmlb`"); return FALSE; } /* don't check the magic to make fuzzing easier */ if (priv->force) flags |= XB_SILO_LOAD_FLAG_NO_MAGIC; /* load blobs */ for (guint i = 0; values[i] != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GFile) file = g_file_new_for_path (values[0]); g_autoptr(XbSilo) silo = xb_silo_new (); if (!xb_silo_load_from_file (silo, file, flags, NULL, error)) return FALSE; str = xb_silo_to_string (silo, error); if (str == NULL) return FALSE; g_print ("%s", str); } return TRUE; } static gboolean xb_tool_export (XbToolPrivate *priv, gchar **values, GError **error) { XbSiloLoadFlags flags = XB_SILO_LOAD_FLAG_NONE; /* check args */ if (g_strv_length (values) < 1) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME" " -- e.g. `example.xmlb`"); return FALSE; } /* don't check the magic to make fuzzing easier */ if (priv->force) flags |= XB_SILO_LOAD_FLAG_NO_MAGIC; /* load blobs */ for (guint i = 0; values[i] != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GFile) file = g_file_new_for_path (values[0]); g_autoptr(XbSilo) silo = xb_silo_new (); if (!xb_silo_load_from_file (silo, file, flags, NULL, error)) return FALSE; str = xb_silo_export (silo, XB_NODE_EXPORT_FLAG_ADD_HEADER | XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT | XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, error); if (str == NULL) return FALSE; g_print ("%s", str); } return TRUE; } static gboolean xb_tool_query (XbToolPrivate *priv, gchar **values, GError **error) { guint limit = 0; g_autoptr(GFile) file = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbSilo) silo = xb_silo_new (); /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME QUERY [LIMIT]" " -- e.g. `example.xmlb`"); return FALSE; } /* load blob */ file = g_file_new_for_path (values[0]); if (priv->profile) { xb_silo_set_profile_flags (silo, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_APPEND); } if (!xb_silo_load_from_file (silo, file, XB_SILO_LOAD_FLAG_NONE, NULL, error)) return FALSE; /* parse optional limit */ if (g_strv_length (values) == 3) limit = g_ascii_strtoull (values[2], NULL, 10); /* query */ results = xb_silo_query (silo, values[1], limit, error); if (results == NULL) return FALSE; for (guint i = 0; i < results->len; i++) { XbNode *n = g_ptr_array_index (results, i); g_autofree gchar *xml = NULL; xml = xb_node_export (n, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, error); if (xml == NULL) return FALSE; g_print ("RESULT: %s\n", xml); } /* profile */ if (priv->profile) g_print ("%s", xb_silo_get_profile_string (silo)); return TRUE; } static gboolean xb_tool_query_file (XbToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) file = NULL; g_autoptr(XbSilo) silo = xb_silo_new (); /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME FILENAME"); return FALSE; } /* load blob */ file = g_file_new_for_path (values[0]); if (!xb_silo_load_from_file (silo, file, XB_SILO_LOAD_FLAG_NONE, NULL, error)) return FALSE; /* optionally load file */ for (guint i = 1; values[i] != NULL; i++) { g_autofree gchar *xpath = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(GError) error_local = NULL; /* load XPath from file */ if (!g_file_get_contents (values[i], &xpath, NULL, error)) return FALSE; g_strdelimit (xpath, "\n", '\0'); /* query */ results = xb_silo_query (silo, xpath, 0, &error_local); if (results == NULL) { g_print ("FAILED: %s\n", error_local->message); continue; } for (guint j = 0; j < results->len; j++) { XbNode *n = g_ptr_array_index (results, j); g_autofree gchar *xml = NULL; xml = xb_node_export (n, XB_NODE_EXPORT_FLAG_NONE, error); if (xml == NULL) return FALSE; g_print ("RESULT: %s\n", xml); } } /* profile */ if (priv->profile) g_print ("%s", xb_silo_get_profile_string (silo)); return TRUE; } static void xb_tool_silo_invalidated_cb (XbSilo *silo, GParamSpec *pspec, gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *) user_data; g_main_loop_quit (priv->loop); } static gboolean xb_tool_compile (XbToolPrivate *priv, gchar **values, GError **error) { const gchar *const *locales = g_get_language_names (); g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbSilo) silo = NULL; g_autoptr(GFile) file_dst = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILE-OUT FILE [FILE]" " -- e.g. `example.xmlb example.xml`"); return FALSE; } /* load file */ for (guint i = 0; locales[i] != NULL; i++) xb_builder_add_locale (builder, locales[i]); for (guint i = 1; values[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path (values[i]); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, NULL, error)) return FALSE; xb_builder_import_source (builder, source); } file_dst = g_file_new_for_path (values[0]); xb_builder_set_profile_flags (builder, priv->profile ? XB_SILO_PROFILE_FLAG_APPEND : XB_SILO_PROFILE_FLAG_NONE); silo = xb_builder_ensure (builder, file_dst, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB | XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID | XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, NULL, error); if (silo == NULL) return FALSE; /* wait for invalidation */ if (priv->wait) { g_print ("Waiting for invalidation…\n"); g_signal_connect (silo, "notify::valid", G_CALLBACK (xb_tool_silo_invalidated_cb), priv); g_main_loop_run (priv->loop); } /* profile */ if (priv->profile) g_print ("%s", xb_silo_get_profile_string (silo)); /* success */ return TRUE; } #ifdef HAVE_GIO_UNIX static gboolean xb_tool_sigint_cb (gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *) user_data; g_debug ("Handling SIGINT"); g_cancellable_cancel (priv->cancellable); return FALSE; } #endif int main (int argc, char *argv[]) { gboolean ret; gboolean verbose = FALSE; g_autofree gchar *cmd_descriptions = NULL; g_autoptr(XbToolPrivate) priv = g_new0 (XbToolPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Print verbose debug statements", NULL }, { "force", 'v', 0, G_OPTION_ARG_NONE, &priv->force, "Force parsing of invalid files", NULL }, { "wait", 'w', 0, G_OPTION_ARG_NONE, &priv->wait, "Return only when the silo is no longer valid", NULL }, { "profile", 'p', 0, G_OPTION_ARG_NONE, &priv->profile, "Show profiling information", NULL }, { NULL} }; /* do not let GIO start a session bus */ g_setenv ("GIO_USE_VFS", "local", 1); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_tool_item_free); xb_tool_add (priv->cmd_array, "dump", "XMLBFILE", /* TRANSLATORS: command description */ "Dumps a XMLb file", xb_tool_dump); xb_tool_add (priv->cmd_array, "export", "XMLFILE", /* TRANSLATORS: command description */ "Exports a XMLb file", xb_tool_export); xb_tool_add (priv->cmd_array, "query", "XMLBFILE XPATH [LIMIT]", /* TRANSLATORS: command description */ "Queries a XMLb file", xb_tool_query); xb_tool_add (priv->cmd_array, "query-file", "XMLBFILE [FILE] [FILE]", /* TRANSLATORS: command description */ "Queries a XMLb file using an external XPath query", xb_tool_query_file); xb_tool_add (priv->cmd_array, "compile", "XMLBFILE XMLFILE [XMLFILE]", /* TRANSLATORS: command description */ "Compile XML to XMLb", xb_tool_compile); /* do stuff on ctrl+c */ priv->loop = g_main_loop_new (NULL, FALSE); priv->cancellable = g_cancellable_new (); g_signal_connect (priv->cancellable, "cancelled", G_CALLBACK (xb_tool_cancelled_cb), priv); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, xb_tool_sigint_cb, priv, NULL); #endif /* sort by command name */ g_ptr_array_sort (priv->cmd_array, (GCompareFunc) xb_tool_sort_command_name_cb); /* get a list of the commands */ context = g_option_context_new (NULL); cmd_descriptions = xb_tool_get_descriptions (priv->cmd_array); g_option_context_set_summary (context, cmd_descriptions); /* TRANSLATORS: DFU stands for device firmware update */ g_set_application_name ("Binary XML Utility"); g_option_context_add_main_entries (context, options, NULL); ret = g_option_context_parse (context, &argc, &argv, &error); if (!ret) { g_print ("%s: %s\n", "Failed to parse arguments", error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); /* run the specified command */ ret = xb_tool_run (priv, argv[1], (gchar**) &argv[2], &error); if (!ret) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (context, TRUE, NULL); g_print ("%s\n\n%s", error->message, tmp); } else { g_print ("%s\n", error->message); } return EXIT_FAILURE; } /* success/ */ return EXIT_SUCCESS; }