/* * 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. */ #include <errno.h> #include <locale.h> #include <glib.h> #include <json-glib/json-glib.h> #include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/utils-internal.h" #ifdef MOCK_CHECK_LOCALE typedef enum { MOCK_LOCALE_C, #ifdef MOCK_CHECK_LOCALE_HAVE_C_UTF8 MOCK_LOCALE_C_UTF8, #endif #ifdef MOCK_CHECK_LOCALE_HAVE_EN_US MOCK_LOCALE_EN_US, MOCK_LOCALE_EN_US_UTF8, #endif MOCK_LOCALE_EN_GB_UTF8, MOCK_LOCALE_COMPLICATED } MockLocale; static struct { const char *name; const char *alias; } mock_locales[] = { { "C", "POSIX" }, #ifdef MOCK_CHECK_LOCALE_HAVE_C_UTF8 { "C.UTF-8", NULL }, #endif #ifdef MOCK_CHECK_LOCALE_HAVE_EN_US { "en_US", NULL }, { "en_US.UTF-8", NULL }, #endif { "en_GB.UTF-8", NULL }, /* This is what smcv's en_GB installation of Debian returns from * setlocale(LC_ALL, ""). Why are some of them utf8 and some UTF-8? * We just don't know. */ { "LC_CTYPE=en_GB.UTF-8;" "LC_NUMERIC=en_GB.utf8;" "LC_TIME=en_GB.utf8;" "LC_COLLATE=en_GB.UTF-8;" "LC_MONETARY=en_GB.utf8;" "LC_MESSAGES=en_GB.UTF-8;" "LC_PAPER=en_GB.utf8;" "LC_NAME=en_GB.UTF-8;" "LC_ADDRESS=en_GB.UTF-8;" "LC_TELEPHONE=en_GB.UTF-8;" "LC_MEASUREMENT=en_GB.utf8;" "LC_IDENTIFICATION=en_GB.UTF-8", NULL }, }; G_STATIC_ASSERT (G_N_ELEMENTS (mock_locales) == MOCK_LOCALE_COMPLICATED + 1); static MockLocale mock_locale = MOCK_LOCALE_C; static const char * mock_setlocale (const char *locale_name) { gsize i; if (locale_name == NULL) return mock_locales[mock_locale].name; if (g_strcmp0 (locale_name, "") == 0) { #ifdef MOCK_CHECK_LOCALE_LEGACY mock_locale = MOCK_LOCALE_EN_US; #else mock_locale = MOCK_LOCALE_COMPLICATED; #endif return mock_locales[mock_locale].name; } for (i = 0; i < G_N_ELEMENTS (mock_locales); i++) { if (g_strcmp0 (locale_name, mock_locales[i].name) == 0 || g_strcmp0 (locale_name, mock_locales[i].alias) == 0) { mock_locale = i; return mock_locales[mock_locale].name; } } errno = ENOENT; return NULL; } static gboolean mock_get_charset (const char **charset) { switch (mock_locale) { case MOCK_LOCALE_C: if (charset != NULL) *charset = "ANSI_X3.4-1968"; return FALSE; #ifdef MOCK_CHECK_LOCALE_HAVE_EN_US case MOCK_LOCALE_EN_US: if (charset != NULL) *charset = "ISO-8859-1"; return FALSE; #endif #ifdef MOCK_CHECK_LOCALE_HAVE_C_UTF8 case MOCK_LOCALE_C_UTF8: #endif #ifdef MOCK_CHECK_LOCALE_HAVE_EN_US case MOCK_LOCALE_EN_US_UTF8: #endif case MOCK_LOCALE_EN_GB_UTF8: case MOCK_LOCALE_COMPLICATED: default: if (charset != NULL) *charset = "UTF-8"; return TRUE; } } #endif static gchar *opt_locale = NULL; static gboolean opt_print_version = FALSE; static gboolean opt_locale_cb (const char *name, const char *value, gpointer data, GError **error) { if (opt_locale != NULL) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "At most one locale is expected"); return FALSE; } opt_locale = g_strdup (value); return TRUE; } static const GOptionEntry option_entries[] = { { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version, "Print version number and exit", NULL }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_locale_cb, "The locale to test [default: use environment variables]", "[LOCALE]" }, { NULL } }; int main (int argc, char **argv) { GOptionContext *option_context = NULL; GError *local_error = NULL; const char *locale_result; const char *locale_name; const char *charset; gchar *json = NULL; JsonNode *root = NULL; JsonBuilder *builder = NULL; JsonGenerator *generator = NULL; int ret = 1; gboolean is_utf8; option_context = g_option_context_new (""); g_option_context_add_main_entries (option_context, option_entries, NULL); if (!g_option_context_parse (option_context, &argc, &argv, &local_error)) { ret = 2; goto out; } if (opt_print_version) { /* Output version number as YAML for machine-readability, * inspired by `ostree --version` and `docker version` */ g_print ( "%s:\n" " Package: steam-runtime-tools\n" " Version: %s\n", argv[0], VERSION); ret = 0; goto out; } if (opt_locale == NULL) locale_name = ""; else locale_name = opt_locale; builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "requested"); json_builder_add_string_value (builder, locale_name); #ifdef MOCK_CHECK_LOCALE locale_result = mock_setlocale (locale_name); #else locale_result = setlocale (LC_ALL, locale_name); #endif if (locale_result == NULL) { int saved_errno = errno; json_builder_set_member_name (builder, "error"); json_builder_add_string_value (builder, g_strerror (saved_errno)); ret = 1; } else { #ifdef MOCK_CHECK_LOCALE is_utf8 = mock_get_charset (&charset); #else is_utf8 = g_get_charset (&charset); #endif json_builder_set_member_name (builder, "result"); json_builder_add_string_value (builder, locale_result); json_builder_set_member_name (builder, "charset"); json_builder_add_string_value (builder, charset); json_builder_set_member_name (builder, "is_utf8"); json_builder_add_boolean_value (builder, is_utf8); ret = 0; } json_builder_end_object (builder); root = json_builder_get_root (builder); generator = json_generator_new (); json_generator_set_pretty (generator, TRUE); json_generator_set_root (generator, root); json = json_generator_to_data (generator, NULL); g_print ("%s\n", json); out: if (local_error != NULL) g_printerr ("%s: %s\n", g_get_prgname (), local_error->message); g_clear_object (&generator); g_clear_object (&builder); g_clear_error (&local_error); g_clear_pointer (&root, json_node_free); g_clear_pointer (&option_context, g_option_context_free); g_free (opt_locale); g_free (json); return ret; }