/* * Copyright © 2019 Collabora Ltd. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Output basic information about the system on which the tool is run. * The output is a JSON object with the following keys: * * can-write-uinput: * The values are boolean: true if we can write to `/dev/uninput`, * false if we are not able to do it. * * architectures: * An object. The keys are multiarch tuples like %SRT_ABI_I386, * as used in Debian and the freedesktop.org SDK runtime. * The values are objects with more details of the architecture: * * can-run: * The values are boolean: true if we can definitely run * executables of this architecture, or false if we cannot prove * that we can do so. * * library-issues-summary: * A string array listing all the libraries problems that has been found * in the running system. Possible values can be: "cannot-load", * "missing-symbols", "misversioned-symbols" and "internal-error". * If "can-run" for this architecture is false we skip the library check * and this "library-issues-summary" will not be printed at all. * * library-details: * An object. The keys are library SONAMEs, such as `libjpeg.so.62`. * The values are objects with more details of the library: * * path: * The value is a string representing the full path about where the * @library has been found. The value is `null` if the library is * not available in the system. * * messages: * If present, the value is a string containing diagnostic messages * that were encountered when attempting to load the library. * This member will only be available if there were some messages. * * issues: * A string array listing all the @library problems that has been * found in the running system. For possible values check * `library-issues-summary`. This object will be available only if * there are some issues. * * missing-symbols: * A string array listing all the symbols that were expected to be * provided by @library but were not found. This object will be * available only if there are sone missing symbols. * * misversioned-symbols: * A string array listing all the symbols that were expected to be * provided by @library but were available with a different version. * This object will be available only if there are some misversioned * symbols. * * graphics: * An object. The keys are multiarch tuples like %SRT_ABI_I386, * as used in Debian and the freedesktop.org SDK runtime. * The values are objects with more details of the graphics results: * * locale-issues: * A string array listing locale-related issues. * * locales: * An object. The keys are either `<default>` (representing passing * the empty string to `setlocale()`), or locale names that can be * requested with `setlocale()`. They will include at least `C`, * `C.UTF-8`, `en_US.UTF-8` and `<default>`, and may include more * in future versions of steam-runtime-tools. The values are objects * containing either: * * error: * A string: The error that was encountered when trying to * set this locale * error-domain: * A string: The GError domain * error-code: * An integer: The GError code * * or: * * resulting-name: * A string: the locale name as returned by setlocale(), if * different * charset: * A string: the character set * is_utf8: * A boolean: whether the character set is UTF-8 */ #include <steam-runtime-tools/steam-runtime-tools.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <glib.h> #include <json-glib/json-glib.h> enum { OPTION_HELP = 1, OPTION_EXPECTATION, OPTION_VERBOSE, }; struct option long_options[] = { { "expectations", required_argument, NULL, OPTION_EXPECTATION }, { "verbose", no_argument, NULL, OPTION_VERBOSE }, { "help", no_argument, NULL, OPTION_HELP }, { NULL, 0, NULL, 0 } }; static void usage (int code) __attribute__((__noreturn__)); /* * Print usage information and exit with status @code. */ static void usage (int code) { FILE *fp; if (code == 0) fp = stdout; else fp = stderr; fprintf (fp, "Usage: %s [OPTIONS]\n", program_invocation_short_name); exit (code); } static FILE * divert_stdout_to_stderr (GError **error) { int original_stdout_fd; FILE *original_stdout; /* Duplicate the original stdout so that we still have a way to write * machine-readable output. */ original_stdout_fd = dup (STDOUT_FILENO); if (original_stdout_fd < 0) { int saved_errno = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), "Unable to duplicate fd %d: %s", STDOUT_FILENO, g_strerror (saved_errno)); return NULL; } /* If something like g_debug writes to stdout, make it come out of * our original stderr. */ if (dup2 (STDERR_FILENO, STDOUT_FILENO) != STDOUT_FILENO) { int saved_errno = errno; close (original_stdout_fd); g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), "Unable to make fd %d a copy of fd %d: %s", STDOUT_FILENO, STDERR_FILENO, g_strerror (saved_errno)); return NULL; } /* original_stdout takes ownership of original_stdout_fd on success */ original_stdout = fdopen (original_stdout_fd, "w"); if (original_stdout == NULL) { int saved_errno = errno; close (original_stdout_fd); g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), "Unable to create a stdio wrapper for fd %d: %s", original_stdout_fd, g_strerror (saved_errno)); return NULL; } return original_stdout; } static void jsonify_library_issues (JsonBuilder *builder, SrtLibraryIssues issues) { if ((issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD) != 0) json_builder_add_string_value (builder, "cannot-load"); if ((issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS) != 0) json_builder_add_string_value (builder, "missing-symbols"); if ((issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS) != 0) json_builder_add_string_value (builder, "misversioned-symbols"); if ((issues & SRT_LIBRARY_ISSUES_INTERNAL_ERROR) != 0) json_builder_add_string_value (builder, "internal-error"); if ((issues & SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS) != 0) json_builder_add_string_value (builder, "unknown-expectations"); } static void jsonify_graphics_issues (JsonBuilder *builder, SrtGraphicsIssues issues) { if ((issues & SRT_GRAPHICS_ISSUES_CANNOT_LOAD) != 0) json_builder_add_string_value (builder, "cannot-load"); if ((issues & SRT_GRAPHICS_ISSUES_INTERNAL_ERROR) != 0) json_builder_add_string_value (builder, "internal-error"); if ((issues & SRT_GRAPHICS_ISSUES_SOFTWARE_RENDERING) != 0) json_builder_add_string_value (builder, "software-rendering"); } static void jsonify_steam_issues (JsonBuilder *builder, SrtSteamIssues issues) { if ((issues & SRT_STEAM_ISSUES_INTERNAL_ERROR) != 0) json_builder_add_string_value (builder, "internal-error"); if ((issues & SRT_STEAM_ISSUES_CANNOT_FIND) != 0) json_builder_add_string_value (builder, "cannot-find"); if ((issues & SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK) != 0) json_builder_add_string_value (builder, "dot-steam-steam-not-symlink"); } static void jsonify_runtime_issues (JsonBuilder *builder, SrtRuntimeIssues issues) { if ((issues & SRT_RUNTIME_ISSUES_INTERNAL_ERROR) != 0) json_builder_add_string_value (builder, "internal-error"); if ((issues & SRT_RUNTIME_ISSUES_DISABLED) != 0) json_builder_add_string_value (builder, "disabled"); if ((issues & SRT_RUNTIME_ISSUES_NOT_RUNTIME) != 0) json_builder_add_string_value (builder, "not-runtime"); if ((issues & SRT_RUNTIME_ISSUES_UNOFFICIAL) != 0) json_builder_add_string_value (builder, "unofficial"); if ((issues & SRT_RUNTIME_ISSUES_UNEXPECTED_LOCATION) != 0) json_builder_add_string_value (builder, "unexpected-location"); if ((issues & SRT_RUNTIME_ISSUES_UNEXPECTED_VERSION) != 0) json_builder_add_string_value (builder, "unexpected-version"); if ((issues & SRT_RUNTIME_ISSUES_NOT_IN_LD_PATH) != 0) json_builder_add_string_value (builder, "not-in-ld-path"); if ((issues & SRT_RUNTIME_ISSUES_NOT_IN_PATH) != 0) json_builder_add_string_value (builder, "not-in-path"); if ((issues & SRT_RUNTIME_ISSUES_NOT_IN_ENVIRONMENT) != 0) json_builder_add_string_value (builder, "not-in-environment"); if ((issues & SRT_RUNTIME_ISSUES_NOT_USING_NEWER_HOST_LIBRARIES) != 0) json_builder_add_string_value (builder, "not-using-newer-host-libraries"); } static void jsonify_locale_issues (JsonBuilder *builder, SrtLocaleIssues issues) { if ((issues & SRT_LOCALE_ISSUES_INTERNAL_ERROR) != 0) json_builder_add_string_value (builder, "internal-error"); if ((issues & SRT_LOCALE_ISSUES_DEFAULT_MISSING) != 0) json_builder_add_string_value (builder, "default-missing"); if ((issues & SRT_LOCALE_ISSUES_DEFAULT_NOT_UTF8) != 0) json_builder_add_string_value (builder, "default-not-utf8"); if ((issues & SRT_LOCALE_ISSUES_C_UTF8_MISSING) != 0) json_builder_add_string_value (builder, "c-utf8-missing"); if ((issues & SRT_LOCALE_ISSUES_EN_US_UTF8_MISSING) != 0) json_builder_add_string_value (builder, "en-us-utf8-missing"); } static void print_libraries_details (JsonBuilder *builder, GList *libraries, gboolean verbose) { json_builder_set_member_name (builder, "library-details"); json_builder_begin_object (builder); for (GList *l = libraries; l != NULL; l = l->next) { if (verbose || srt_library_get_issues (l->data) != SRT_LIBRARY_ISSUES_NONE) { const char *messages; const char * const *missing_symbols; const char * const *misversioned_symbols; json_builder_set_member_name (builder, srt_library_get_soname (l->data)); json_builder_begin_object (builder); messages = srt_library_get_messages (l->data); if (messages != NULL) { json_builder_set_member_name (builder, "messages"); json_builder_add_string_value (builder, messages); } json_builder_set_member_name (builder, "path"); json_builder_add_string_value (builder, srt_library_get_absolute_path (l->data)); if (srt_library_get_issues (l->data) != SRT_LIBRARY_ISSUES_NONE) { json_builder_set_member_name (builder, "issues"); json_builder_begin_array (builder); jsonify_library_issues (builder, srt_library_get_issues (l->data)); json_builder_end_array (builder); } missing_symbols = srt_library_get_missing_symbols (l->data); if (missing_symbols[0] != NULL) { json_builder_set_member_name (builder, "missing-symbols"); json_builder_begin_array (builder); for (gsize i = 0; missing_symbols[i] != NULL; i++) json_builder_add_string_value (builder, missing_symbols[i]); json_builder_end_array (builder); } misversioned_symbols = srt_library_get_misversioned_symbols (l->data); if (misversioned_symbols[0] != NULL) { json_builder_set_member_name (builder, "misversioned-symbols"); json_builder_begin_array (builder); for (gsize i = 0; misversioned_symbols[i] != NULL; i++) json_builder_add_string_value (builder, misversioned_symbols[i]); json_builder_end_array (builder); } json_builder_end_object (builder); } } json_builder_end_object (builder); return; } static void print_graphics_details(JsonBuilder *builder, GList *graphics_list) { json_builder_set_member_name (builder, "graphics-details"); json_builder_begin_object (builder); for (GList *g = graphics_list; g != NULL; g = g->next) { gchar *parameters = srt_graphics_dup_parameters_string (g->data); const char *messages; json_builder_set_member_name (builder, parameters); json_builder_begin_object (builder); messages = srt_graphics_get_messages (g->data); if (messages != NULL) { json_builder_set_member_name (builder, "messages"); json_builder_add_string_value (builder, messages); } json_builder_set_member_name (builder, "renderer"); json_builder_add_string_value (builder, srt_graphics_get_renderer_string (g->data)); json_builder_set_member_name (builder, "version"); json_builder_add_string_value (builder, srt_graphics_get_version_string (g->data)); if (srt_graphics_get_issues (g->data) != SRT_GRAPHICS_ISSUES_NONE) { json_builder_set_member_name (builder, "issues"); json_builder_begin_array (builder); jsonify_graphics_issues (builder, srt_graphics_get_issues (g->data)); json_builder_end_array (builder); } json_builder_end_object (builder); // End object for parameters g_free (parameters); } json_builder_end_object (builder); // End garphics-details } static const char * const locales[] = { "", "C", "C.UTF-8", "en_US.UTF-8", }; int main (int argc, char **argv) { FILE *original_stdout = NULL; GError *error = NULL; SrtSystemInfo *info; SrtLibraryIssues library_issues = SRT_LIBRARY_ISSUES_NONE; SrtSteamIssues steam_issues = SRT_STEAM_ISSUES_NONE; SrtRuntimeIssues runtime_issues = SRT_RUNTIME_ISSUES_NONE; SrtLocaleIssues locale_issues = SRT_LOCALE_ISSUES_NONE; char *expectations = NULL; gboolean verbose = FALSE; JsonBuilder *builder; JsonGenerator *generator; gboolean can_run = FALSE; gchar *json_output; gchar *version = NULL; gchar *inst_path = NULL; gchar *rt_path = NULL; int opt; static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 }; while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (opt) { case OPTION_EXPECTATION: expectations = optarg; break; case OPTION_VERBOSE: verbose = TRUE; break; case OPTION_HELP: usage (0); break; case '?': default: usage (1); break; /* not reached */ } } if (optind != argc) usage (1); /* stdout is reserved for machine-readable output, so avoid having * things like g_debug() pollute it. */ original_stdout = divert_stdout_to_stderr (&error); if (original_stdout == NULL) { g_warning ("%s", error->message); g_clear_error (&error); return 1; } info = srt_system_info_new (expectations); builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "can-write-uinput"); json_builder_add_boolean_value (builder, srt_system_info_can_write_to_uinput (info)); json_builder_set_member_name (builder, "steam-installation"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "path"); inst_path = srt_system_info_dup_steam_installation_path (info); json_builder_add_string_value (builder, inst_path); json_builder_set_member_name (builder, "issues"); json_builder_begin_array (builder); steam_issues = srt_system_info_get_steam_issues (info); jsonify_steam_issues (builder, steam_issues); json_builder_end_array (builder); json_builder_end_object (builder); json_builder_set_member_name (builder, "runtime"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "path"); rt_path = srt_system_info_dup_runtime_path (info); json_builder_add_string_value (builder, rt_path); json_builder_set_member_name (builder, "version"); version = srt_system_info_dup_runtime_version (info); json_builder_add_string_value (builder, version); json_builder_set_member_name (builder, "issues"); json_builder_begin_array (builder); runtime_issues = srt_system_info_get_runtime_issues (info); jsonify_runtime_issues (builder, runtime_issues); json_builder_end_array (builder); json_builder_end_object (builder); json_builder_set_member_name (builder, "architectures"); json_builder_begin_object (builder); for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++) { GList *libraries = NULL; json_builder_set_member_name (builder, multiarch_tuples[i]); json_builder_begin_object (builder); json_builder_set_member_name (builder, "can-run"); can_run = srt_system_info_can_run (info, multiarch_tuples[i]); json_builder_add_boolean_value (builder, can_run); if (can_run) { json_builder_set_member_name (builder, "library-issues-summary"); json_builder_begin_array (builder); library_issues = srt_system_info_check_libraries (info, multiarch_tuples[i], &libraries); jsonify_library_issues (builder, library_issues); json_builder_end_array (builder); } if (libraries != NULL && (library_issues != SRT_LIBRARY_ISSUES_NONE || verbose)) print_libraries_details (builder, libraries, verbose); GList *graphics_list = srt_system_info_check_all_graphics (info, multiarch_tuples[i]); print_graphics_details (builder, graphics_list); json_builder_end_object (builder); // End multiarch_tuple object g_list_free_full (libraries, g_object_unref); g_list_free_full (graphics_list, g_object_unref); } json_builder_end_object (builder); json_builder_set_member_name (builder, "locale-issues"); json_builder_begin_array (builder); locale_issues = srt_system_info_get_locale_issues (info); jsonify_locale_issues (builder, locale_issues); json_builder_end_array (builder); json_builder_set_member_name (builder, "locales"); json_builder_begin_object (builder); for (gsize i = 0; i < G_N_ELEMENTS (locales); i++) { SrtLocale *locale = srt_system_info_check_locale (info, locales[i], &error); if (locales[i][0] == '\0') json_builder_set_member_name (builder, "<default>"); else json_builder_set_member_name (builder, locales[i]); json_builder_begin_object (builder); if (locale != NULL) { json_builder_set_member_name (builder, "resulting-name"); json_builder_add_string_value (builder, srt_locale_get_resulting_name (locale)); json_builder_set_member_name (builder, "charset"); json_builder_add_string_value (builder, srt_locale_get_charset (locale)); json_builder_set_member_name (builder, "is_utf8"); json_builder_add_boolean_value (builder, srt_locale_is_utf8 (locale)); } else { json_builder_set_member_name (builder, "error-domain"); json_builder_add_string_value (builder, g_quark_to_string (error->domain)); json_builder_set_member_name (builder, "error-code"); json_builder_add_int_value (builder, error->code); json_builder_set_member_name (builder, "error"); json_builder_add_string_value (builder, error->message); } json_builder_end_object (builder); g_clear_object (&locale); g_clear_error (&error); } json_builder_end_object (builder); json_builder_end_object (builder); // End global object JsonNode *root = json_builder_get_root (builder); generator = json_generator_new (); json_generator_set_root (generator, root); json_generator_set_pretty (generator, TRUE); json_output = json_generator_to_data (generator, NULL); if (fputs (json_output, original_stdout) < 0) g_warning ("Unable to write output: %s", g_strerror (errno)); if (fputs ("\n", original_stdout) < 0) g_warning ("Unable to write final newline: %s", g_strerror (errno)); if (fclose (original_stdout) != 0) g_warning ("Unable to close stdout: %s", g_strerror (errno)); g_free (json_output); g_object_unref (generator); json_node_free (root); g_object_unref (builder); g_object_unref (info); g_free (rt_path); g_free (inst_path); g_free (version); return 0; }