From b1a7927d72a4b5db6c122f76fa825b403404a933 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Wed, 11 Sep 2019 19:50:09 +0100 Subject: [PATCH] wrap: Use a new pressure-vessel-locale-gen to set up locales This avoids relying on the host system (if using the host system) or the container (otherwise) having both its configured locales and the frequently-hard-coded en_US.UTF-8. Signed-off-by: Simon McVittie <smcv@collabora.com> --- build-relocatable-install.py | 4 +- ci/Dockerfile.in | 2 + debian/.gitignore | 1 + debian/control | 2 + debian/copyright | 6 + debian/gitlab-ci.yml | 2 + debian/rules | 7 + man/locale-gen.1.md | 86 +++++++++ man/try-setlocale.1.md | 60 ++++++ meson.build | 50 ++++- meson_options.txt | 1 + pressure-vessel-locale-gen | 364 +++++++++++++++++++++++++++++++++++ src/try-setlocale.c | 114 +++++++++++ src/wrap.c | 147 ++++++++++++++ tests/shellcheck.sh | 2 +- tests/test-locale-gen.sh | 236 +++++++++++++++++++++++ 16 files changed, 1081 insertions(+), 3 deletions(-) create mode 100644 man/locale-gen.1.md create mode 100644 man/try-setlocale.1.md create mode 100755 pressure-vessel-locale-gen create mode 100644 src/try-setlocale.c create mode 100755 tests/test-locale-gen.sh diff --git a/build-relocatable-install.py b/build-relocatable-install.py index 507bed216..cf86be7c5 100755 --- a/build-relocatable-install.py +++ b/build-relocatable-install.py @@ -96,13 +96,15 @@ PRIMARY_ARCH_DEPENDENCIES = { 'libselinux1': 'libselinux', } SCRIPTS = [ + 'pressure-vessel-locale-gen', 'pressure-vessel-test-ui', 'pressure-vessel-unruntime', 'pressure-vessel-unruntime-scout', 'pressure-vessel-unruntime-test-ui', ] EXECUTABLES = [ - 'pressure-vessel-wrap' + 'pressure-vessel-try-setlocale', + 'pressure-vessel-wrap', ] LIBCAPSULE_TOOLS = [ 'capsule-capture-libs', diff --git a/ci/Dockerfile.in b/ci/Dockerfile.in index e6c775f25..86e6adba2 100644 --- a/ci/Dockerfile.in +++ b/ci/Dockerfile.in @@ -18,6 +18,8 @@ apt-get -y install \ libpcre3 \ libselinux1 \ libxau6 \ + locales \ + pandoc \ zlib1g:amd64 \ zlib1g:i386 \ ${NULL+} diff --git a/debian/.gitignore b/debian/.gitignore index 6a9f93f39..c3b9c3f16 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -3,6 +3,7 @@ /*.log /*.substvars /files +/locales/ /pressure-vessel-libs-*/ /pressure-vessel-relocatable/ /tmp/ diff --git a/debian/control b/debian/control index 6bab4066e..8d88b32de 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,9 @@ Build-Depends: debhelper (>= 9), libglib2.0-dev, libxau-dev, + locales <!nocheck> | locales-all <!nocheck>, meson, + pandoc, python3, python3 (>= 3.5) | python3.5, zlib1g <!nocheck>, diff --git a/debian/copyright b/debian/copyright index 126f0c7fc..d50b123c5 100644 --- a/debian/copyright +++ b/debian/copyright @@ -18,6 +18,12 @@ Copyright: 2017-2019 Collabora Ltd. License: LGPL-2.1+ +Files: + src/try-setlocale.* +Copyright: + 2019 Collabora Ltd. +License: Expat + Files: build-aux/git-version-gen Copyright: diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml index 3c667e62d..73e724459 100644 --- a/debian/gitlab-ci.yml +++ b/debian/gitlab-ci.yml @@ -12,7 +12,9 @@ variables: debhelper libglib2.0-dev libxau-dev + locales meson + pandoc python3 python3.5 zlib1g diff --git a/debian/rules b/debian/rules index 8c670bcab..b7ff2608c 100755 --- a/debian/rules +++ b/debian/rules @@ -16,6 +16,7 @@ include /usr/share/dpkg/default.mk override_dh_auto_clean: rm -fr _build + rm -fr debian/locales override_dh_auto_configure: if python3 -c 'if __import__("sys").version_info < (3, 5): raise SystemExit(1)'; then \ @@ -25,6 +26,7 @@ override_dh_auto_configure: fi; \ meson _build \ --prefix=/usr/lib/pressure-vessel/relocatable \ + -Dman=true \ -Dpython=/usr/bin/$$python \ -Dsrcdir=src \ -Dversion=$(DEB_VERSION) \ @@ -34,7 +36,12 @@ override_dh_auto_build: ninja -C _build override_dh_auto_test: +ifeq ($(filter nocheck,$(DEB_BUILD_OPTIONS)),) + mkdir debian/locales + localedef -f UTF-8 -i en_US --no-archive debian/locales/en_US.UTF-8 + env LOCPATH=$(CURDIR)/debian/locales \ meson test -C _build --verbose +endif override_dh_auto_install: DESTDIR=$(CURDIR)/debian/tmp ninja -C _build install diff --git a/man/locale-gen.1.md b/man/locale-gen.1.md new file mode 100644 index 000000000..b8f500c36 --- /dev/null +++ b/man/locale-gen.1.md @@ -0,0 +1,86 @@ +--- +title: pressure-vessel-locale-gen +section: 1 +... + +# NAME + +pressure-vessel-locale-gen - generate additional locales + +# SYNOPSIS + +**pressure-vessel-locale-gen** [*LOCALE*…] + +# DESCRIPTION + +This tool generates locale files for the locales that might be necessary +for Steam games. It should be invoked with the current working directory +set to an empty directory. + +If the current working directory is non-empty, the behaviour is undefined. +Existing subdirectories corresponding to locales might be overwritten, or +might be kept. (The current implementation is that they are kept, even if +they are outdated or invalid.) + +# OPTIONS + +**--verbose** +: Be more verbose. + +# POSITIONAL ARGUMENTS + +*LOCALE* +: One or more additional POSIX locale names, such as **en_US.UTF-8** or + **it_IT@euro**. By default, **pressure-vessel-locale-gen** + generates all the locales required by the standard locale environment + variables such as **LC_ALL**, plus the value of *$HOST_LC_ALL* if + set, plus the American English locale **en_US.UTF-8** (which is + frequently assumed to be present, even though many operating systems + do not guarantee it). + +# OUTPUT + +The diagnostic output on standard error is not machine-readable. + +Locale files are generated in the current working directory. On exit, +if the current working directory is non-empty (exit status 72 or 73), +its path should be added to the **LOCPATH** environment variable. If +the current working directory is empty (exit status 0), +**LOCPATH** should not be modified. + +# EXIT STATUS + +0 +: All necessary locales were already available without altering + the **LOCPATH** environment variable. + +64 (**EX_USAGE** from **sysexits.h**) +: Invalid arguments were given. + +72 (**EX_OSFILE**) +: Not all of the necessary locales were already available, but all + were generated in the current working directory. + +73 (**EX_CANTCREAT**) +: At least one locale was not generated successfully. Some other + locales might have been generated in the current working directory + as usual. + +78 (**EX_CONFIG**) +: One of the locales given was invalid or could lead to path traversal. + Other locales might have been generated as usual. + +Anything else +: There was an internal error. + +# EXAMPLE + + $ mkdir locales + $ ( cd locales; pressure-vessel-locale-gen ) + $ if [ $? = 0 ]; then \ + ./bin/some-game; \ + else \ + LOCPATH="$(pwd)/locales" ./bin/some-game; \ + fi + +<!-- vim:set sw=4 sts=4 et: --> diff --git a/man/try-setlocale.1.md b/man/try-setlocale.1.md new file mode 100644 index 000000000..08714749e --- /dev/null +++ b/man/try-setlocale.1.md @@ -0,0 +1,60 @@ +--- +title: pressure-vessel-try-setlocale +section: 1 +... + +# NAME + +pressure-vessel-try-setlocale - check whether a locale works + +# SYNOPSIS + +**pressure-vessel-try-setlocale** [*LOCALE*] + +# DESCRIPTION + +This tool checks whether a locale works. If no locale is specified, +the standard locale environment variables such as **LANG**, **LC\_ALL** +and **LC\_MESSAGES** are used. + +# OPTIONS + +There are no options. + +# POSITIONAL ARGUMENTS + +*LOCALE* +: A POSIX locale name, such as **en_US.UTF-8** or **it_IT@euro**, + or an empty string to use the standard locale environment variables + (which is the default). + +# OUTPUT + +On failure, an unstructured diagnostic message is printed on standard +error. + +# EXIT STATUS + +0 +: The given locale is available. + +1 +: The given locale is not available. + +2 +: Invalid arguments were given. + +# EXAMPLES + +The output quoted here is from a system with no Canadian French locale +available, correctly configured for some other locale. + + $ pressure-vessel-try-setlocale; echo "exit status $?" + exit status 0 + $ pressure-vessel-try-setlocale fr_CA; echo "exit status $?" + setlocale "fr_CA": No such file or directory + exit status 1 + $ pressure-vessel-try-setlocale POSIX; echo "exit status $?" + exit status 0 + +<!-- vim:set sw=4 sts=4 et: --> diff --git a/meson.build b/meson.build index 6276537a4..295cae89a 100644 --- a/meson.build +++ b/meson.build @@ -38,7 +38,6 @@ if version == 'auto' version = git_version_gen.stdout().strip() endif - warning_cflags = [ '-Wall', '-Wextra', @@ -107,6 +106,7 @@ supported_warning_cflags = c_compiler.get_supported_arguments(project_warning_cf add_project_arguments(supported_warning_cflags, language : 'c') scripts = [ + 'pressure-vessel-locale-gen', 'pressure-vessel-test-ui', 'pressure-vessel-unruntime', 'pressure-vessel-unruntime-scout', @@ -118,11 +118,13 @@ tests = [ 'pycodestyle.sh', 'pyflakes.sh', 'shellcheck.sh', + 'test-locale-gen.sh', ] test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) +test_env.set('PRESSURE_VESSEL_UNINSTALLED', 'yes') foreach test_name : tests if prove.found() @@ -185,6 +187,52 @@ executable( install_rpath : '${ORIGIN}/../' + get_option('libdir'), ) +executable( + 'pressure-vessel-try-setlocale', + sources : [ + 'src/try-setlocale.c', + ], + install : true, + install_dir : get_option('bindir'), +) + +if get_option('man') + pandoc = find_program('pandoc', required : true) + + if run_command(pandoc, [ + '-f', 'markdown-smart', + '-t', 'man', + '/dev/null', + ]).returncode() == 0 + pandoc_markdown_nosmart = 'markdown-smart' + else + pandoc_markdown_nosmart = 'markdown' + endif + + foreach tool : ['locale-gen', 'try-setlocale'] + custom_target( + tool + '.1', + build_by_default : true, + command : [ + pandoc, + '-s', + '-o', '@OUTPUT@', + '-f', pandoc_markdown_nosmart, + '-t', 'man', + '@INPUT@', + ], + input : join_paths('man', tool + '.1.md'), + output : 'pressure-vessel-' + tool + '.1', + install : true, + install_dir : join_paths( + get_option('prefix'), + get_option('mandir'), + 'man1', + ), + ) + endforeach +endif + if get_option('srcdir') != '' conf_data = configuration_data() conf_data.set('prefix', get_option('prefix')) diff --git a/meson_options.txt b/meson_options.txt index fe6a3e81e..0931f78eb 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,4 @@ +option('man', type : 'boolean', value : false, description : 'enable man pages') option('python', type : 'string', value : 'python3') option('srcdir', type : 'string', value : '') option('version', type : 'string', value : 'auto') diff --git a/pressure-vessel-locale-gen b/pressure-vessel-locale-gen new file mode 100755 index 000000000..b9f660e46 --- /dev/null +++ b/pressure-vessel-locale-gen @@ -0,0 +1,364 @@ +#!/bin/sh + +# 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. + +set -e +set -u + +me="$0" +me="$(readlink -f "$0")" +here="${me%/*}" +me="${me##*/}" + +opt_verbose= + +# Look for try-setlocale helper executable next to this script +PATH="${here}:${PATH}" + +LOCALE_ALIAS="/usr/share/locale/locale.alias" + +if ! [ -f "$LOCALE_ALIAS" ]; then + LOCALE_ALIAS= +fi + +# We currently only look for I18NDIR data in /usr/share/i18n, the +# glibc default path. +# +# If we discover that some distros use a different default, then we +# should enhance this script to iterate through a search path. +# +# Please keep this in sync with srt_system_info_get_locale_issues(). +SUPPORTED="/usr/share/i18n/SUPPORTED" + +log () { + printf '%s\n' "$me: $*" >&2 +} + +verbose () { + if [ -n "$opt_verbose" ]; then + log "$@" + fi +} + +# Return nonzero if the given locale cannot be generated normally. +can_generate () { + local locale="$1" + + case "$locale" in + (C|C.UTF-8|C.utf8|POSIX|"") + return 1 + ;; + + (*..*|*/*) + log "Avoiding potential path traversal in '$locale'" + return_ex_config=1 + return 1 + ;; + + (*) + return 0 + ;; + esac +} + +generate_locale () { + local at_modifier= + local codeset= + local language= + local list= + local locale="$1" + local modifier= + local supported_codeset= + local territory= + local underscore_territory= + local without_codeset= + local status= + + language="$locale" + + case "$language" in + (*@*) + modifier="${language##*@}" + at_modifier="@${modifier}" + language="${language%@*}" + ;; + esac + + case "$language" in + (*.*) + codeset="${language##*.}" + language="${language%.*}" + ;; + esac + + case "$language" in + (*_*) + territory="${language##*_}" + underscore_territory="_${territory}" + language="${language%_*}" + ;; + esac + + # Speed up locale generation a bit by making e.g. en_GB.utf8 a + # symlink to en_GB.UTF-8 + if [ "${codeset}" = 'utf8' ]; then + codeset="UTF-8" + locale="${language}${underscore_territory}.UTF-8${at_modifier}" + ln -fns "$locale" "${language}${underscore_territory}.utf8${at_modifier}" + + if [ -d "$locale" ]; then + return 0 + fi + fi + + if [ -n "$codeset" ]; then + # We don't really need to parse $SUPPORTED to find out that + # en_US.UTF-8 is a UTF-8 locale. Do nothing. In particular, + # this works on distributions like Arch Linux that do not + # normally install $SUPPORTED. + : + else + for list in $SUPPORTED; do + if supported_codeset="$( + while read -r supported codeset; do + case "$supported" in + (\#*) + continue + ;; + esac + + if [ "$supported" = "$locale" ]; then + printf '%s' "$codeset" + fi + done < "$list" + )"; then + if [ -n "$supported_codeset" ]; then + break + fi + else + supported_codeset= + verbose "Unable to read $list" + fi + done + + if [ -n "$supported_codeset" ]; then + codeset="$supported_codeset" + else + log "Unable to find $locale in $SUPPORTED" + log "Assuming UTF-8 and hoping that works..." + codeset="UTF-8" + fi + fi + + without_codeset="${language}${underscore_territory}${at_modifier}" + + if localedef \ + ${LOCALE_ALIAS+-A "${LOCALE_ALIAS}"} \ + --no-archive \ + -c \ + -f "$codeset" \ + -i "$without_codeset" \ + ./"${locale}" \ + ; then + log "Generated locale $locale successfully" + else + status="$?" + + if [ "$status" = 1 ]; then + log "Generated locale $locale, with warnings" + else + log "Unable to generate locale $locale: $status" + fi + fi + + if ! LOCPATH="$(pwd)" pressure-vessel-try-setlocale "$locale"; then + log "Warning: $locale was generated but does not appear to work!" + fi +} + +usage () { + local code="$1" + shift + + if [ "$code" -ne 0 ]; then + exec >&2 + fi + + echo "Usage: $me [OPTIONS] [LOCALE...]" + echo + echo "Generate the locales required by the locale environment" + echo "variables, and any extra locales from the command line," + echo "in the current working directory." + echo + echo "Options:" + echo "--verbose Be more verbose" + echo + echo "Arguments:" + echo "LOCALE Zero or more locales in POSIX format," + echo " such as en_US.UTF-8, be_BY@latin" + exit "$code" +} + +getopt_temp="help" +getopt_temp="${getopt_temp},verbose" + +getopt_temp="$(getopt -o '' --long "$getopt_temp" -n "$me" -- "$@")" +eval "set -- $getopt_temp" +unset getopt_temp + +while [ "$#" -gt 0 ]; do + case "$1" in + (--help) + usage 0 + # not reached + ;; + + (--verbose) + opt_verbose=yes + shift + ;; + + (--) + shift + break + ;; + + (-*) + log "Unknown option: $1" + usage 64 # EX_USAGE from sysexits.h + # not reached + ;; + + (*) + break + ;; + esac +done + +one_missing= + +for var in \ + LC_ADDRESS \ + LC_CTYPE \ + LC_COLLATE \ + LC_IDENTIFICATION \ + LC_MEASUREMENT \ + LC_MESSAGES \ + LC_MONETARY \ + LC_NUMERIC \ + LC_NAME \ + LC_PAPER \ + LC_TELEPHONE \ + LC_TIME \ + \ + HOST_LC_ALL \ + LANG \ + LC_ALL \ +; do + locale="$(eval "printf '%s' \${${var}-}")" + + if [ -z "$locale" ] || ! can_generate "$locale"; then + continue + fi + + if ! pressure-vessel-try-setlocale "$locale"; then + one_missing=yes + break + fi +done + +for locale in en_US.UTF-8 "$@"; do + if ! can_generate "$locale" ; then + continue + fi + + if ! pressure-vessel-try-setlocale "$locale"; then + verbose "Missing locale $locale" + one_missing=yes + break + fi +done + +if [ -z "$one_missing" ]; then + verbose "No locales need to be generated" + exit 0 +fi + +# We have to generate all the locales we want, not just the ones that +# were missing, because they might have been in a locale archive, +# and setting LOCPATH disables use of the locale archive. + +return_ex_cantcreat= +return_ex_config= + +for var in \ + LC_ADDRESS \ + LC_CTYPE \ + LC_COLLATE \ + LC_IDENTIFICATION \ + LC_MEASUREMENT \ + LC_MESSAGES \ + LC_MONETARY \ + LC_NUMERIC \ + LC_NAME \ + LC_PAPER \ + LC_TELEPHONE \ + LC_TIME \ + \ + HOST_LC_ALL \ + LANG \ + LC_ALL \ +; do + locale="$(eval "printf '%s' \${${var}-}")" + + if [ -z "$locale" ] || ! can_generate "$locale" ; then + continue + fi + + if ! [ -d "$locale" ]; then + verbose "Generating locale $locale..." + generate_locale "$locale" || return_ex_cantcreat=yes + fi +done + +for locale in en_US.UTF-8 "$@"; do + if ! can_generate "$locale" ; then + continue + fi + + if ! [ -d "$locale" ]; then + verbose "Generating locale $locale..." + generate_locale "$locale" || return_ex_cantcreat=yes + fi +done + +if [ -n "$return_ex_config" ]; then + exit 78 # EX_CONFIG, "configuration error", from sysexits.h +elif [ -n "$return_ex_cantcreat" ]; then + exit 73 # EX_CANTCREATE, "can't create (user) output file" +else + exit 72 # EX_OSFILE, "OS file missing" +fi + +# vim:set sw=4 sts=4 et: diff --git a/src/try-setlocale.c b/src/try-setlocale.c new file mode 100644 index 000000000..3077ab9a1 --- /dev/null +++ b/src/try-setlocale.c @@ -0,0 +1,114 @@ +/* + * 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 "config.h" + +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Please do not add non-glibc dependencies here: this tool is designed + * to work outside the Steam Runtime environment. */ + +enum +{ + OPTION_HELP = 1 +}; + +struct option long_options[] = +{ + { "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 [LOCALE]\n", + program_invocation_short_name); + exit (code); +} + +int +main (int argc, + char **argv) +{ + const char *locale_name; + const char *locale_result; + int opt; + + while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1) + { + switch (opt) + { + case OPTION_HELP: + usage (0); + break; + + case '?': + default: + usage (2); + break; /* not reached */ + } + } + + if (argc < optind || argc > optind + 1) + { + usage (2); + } + + if (argc > optind) + locale_name = argv[optind]; + else + locale_name = ""; + + locale_result = setlocale (LC_ALL, locale_name); + + if (locale_result == NULL) + { + int saved_errno = errno; + + fprintf (stderr, "setlocale \"%s\": %s\n", + locale_name, strerror (saved_errno)); + return 1; + } + + return 0; +} diff --git a/src/wrap.c b/src/wrap.c index 109d31faf..2a49d77ed 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -31,6 +31,7 @@ #include <locale.h> #include <stdlib.h> +#include <sysexits.h> #include "libglnx.h" @@ -77,6 +78,9 @@ static void search_path_append (GString *search_path, const gchar *item) { + if (item == NULL || item[0] == '\0') + return; + if (search_path->len != 0) g_string_append (search_path, ":"); @@ -333,6 +337,118 @@ try_bind_dri (FlatpakBwrap *bwrap, return TRUE; } +/* + * Try to make sure we have all the locales we need, by running + * the helper from steam-runtime-tools in the container. If this + * fails, it isn't fatal - carry on anyway. + * + * @bwrap must be set up to have the same libc that we will be using + * for the container. + */ +static void +ensure_locales (gboolean on_host, + const char *tools_dir, + FlatpakBwrap *bwrap, + const char *overrides) +{ + g_autoptr(GError) local_error = NULL; + g_autoptr(FlatpakBwrap) run_locale_gen = NULL; + g_autofree gchar *locale_gen = NULL; + g_autofree gchar *locales = g_build_filename (overrides, "locales", NULL); + g_autoptr(GDir) dir = NULL; + int exit_status; + + /* bwrap can't own any fds yet, because if it did, + * flatpak_bwrap_append_bwrap() would steal them. */ + g_return_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0); + + g_mkdir (locales, 0700); + + run_locale_gen = flatpak_bwrap_new (NULL); + + if (on_host) + { + locale_gen = g_build_filename (tools_dir, + "pressure-vessel-locale-gen", + NULL); + + flatpak_bwrap_add_args (run_locale_gen, + bwrap->argv->pdata[0], + "--ro-bind", "/", "/", + NULL); + pv_bwrap_add_api_filesystems (run_locale_gen); + flatpak_bwrap_add_args (run_locale_gen, + "--bind", locales, locales, + "--chdir", locales, + locale_gen, + "--verbose", + NULL); + } + else + { + locale_gen = g_build_filename ("/run/host/tools", + "pressure-vessel-locale-gen", + NULL); + + flatpak_bwrap_append_bwrap (run_locale_gen, bwrap); + pv_bwrap_copy_tree (run_locale_gen, overrides, "/overrides"); + + if (!flatpak_bwrap_bundle_args (run_locale_gen, 1, -1, FALSE, + &local_error)) + { + g_warning ("Unable to set up locale-gen command: %s", + local_error->message); + g_clear_error (&local_error); + } + + flatpak_bwrap_add_args (run_locale_gen, + "--ro-bind", tools_dir, "/run/host/tools", + "--bind", locales, "/overrides/locales", + "--chdir", "/overrides/locales", + locale_gen, + "--verbose", + NULL); + } + + flatpak_bwrap_finish (run_locale_gen); + + /* locale-gen exits 72 (EX_OSFILE) if it had to correct for + * missing locales at OS level. This is not an error. */ + if (!pv_bwrap_run_sync (run_locale_gen, &exit_status, &local_error)) + { + if (exit_status == EX_OSFILE) + g_debug ("pressure-vessel-locale-gen created missing locales"); + else + g_warning ("Unable to generate locales: %s", local_error->message); + + g_clear_error (&local_error); + } + else + { + g_debug ("No locales generated"); + } + + dir = g_dir_open (locales, 0, NULL); + + /* If the directory is not empty, make it the container's LOCPATH */ + if (dir != NULL && g_dir_read_name (dir) != NULL) + { + g_autoptr(GString) locpath = NULL; + + g_debug ("%s is non-empty", locales); + + locpath = g_string_new ("/overrides/locales"); + search_path_append (locpath, g_getenv ("LOCPATH")); + flatpak_bwrap_add_args (bwrap, + "--setenv", "LOCPATH", locpath->str, + NULL); + } + else + { + g_debug ("%s is empty", locales); + } +} + static gboolean bind_runtime (FlatpakBwrap *bwrap, const char *tools_dir, @@ -370,6 +486,7 @@ bind_runtime (FlatpakBwrap *bwrap, gboolean any_architecture_works = FALSE; gboolean any_libc_from_host = FALSE; gboolean all_libc_from_host = TRUE; + g_autofree gchar *localedef = NULL; g_return_val_if_fail (tools_dir != NULL, FALSE); g_return_val_if_fail (runtime != NULL, FALSE); @@ -707,6 +824,23 @@ bind_runtime (FlatpakBwrap *bwrap, "--ro-bind", "/usr/share/i18n", "/usr/share/i18n", NULL); + + localedef = g_find_program_in_path ("localedef"); + + if (localedef == NULL) + { + g_warning ("Cannot find localedef in PATH"); + } + else + { + g_autofree gchar *target = g_build_filename ("/run/host", + localedef, NULL); + + flatpak_bwrap_add_args (bwrap, + "--symlink", target, + "/overrides/bin/localedef", + NULL); + } } else { @@ -716,6 +850,8 @@ bind_runtime (FlatpakBwrap *bwrap, if (!pv_bwrap_bind_usr (bwrap, "/", "/run/host", error)) return FALSE; + ensure_locales (any_libc_from_host, tools_dir, bwrap, overrides); + if (dri_path->len != 0) flatpak_bwrap_add_args (bwrap, "--setenv", "LIBGL_DRIVERS_PATH", dri_path->str, @@ -1248,8 +1384,10 @@ main (int argc, g_autofree gchar *tmpdir = NULL; g_autofree gchar *scratch = NULL; g_autofree gchar *overrides = NULL; + g_autofree gchar *overrides_bin = NULL; g_autofree gchar *bwrap_executable = NULL; g_autoptr(GString) ld_library_path = g_string_new (""); + g_autoptr(GString) bin_path = g_string_new (""); g_autoptr(GString) adjusted_ld_preload = g_string_new (""); g_autofree gchar *cwd_p = NULL; g_autofree gchar *cwd_l = NULL; @@ -1595,6 +1733,8 @@ main (int argc, g_mkdir (scratch, 0700); overrides = g_build_filename (tmpdir, "overrides", NULL); g_mkdir (overrides, 0700); + overrides_bin = g_build_filename (overrides, "bin", NULL); + g_mkdir (overrides_bin, 0700); bwrap = flatpak_bwrap_new (NULL); flatpak_bwrap_add_arg (bwrap, bwrap_executable); @@ -1611,6 +1751,13 @@ main (int argc, { g_debug ("Configuring runtime..."); + search_path_append (bin_path, "/overrides/bin"); + search_path_append (bin_path, g_getenv ("PATH")); + flatpak_bwrap_add_args (bwrap, + "--setenv", "PATH", + bin_path->str, + NULL); + /* TODO: Adapt the use_ld_so_cache code from Flatpak instead * of setting LD_LIBRARY_PATH, for better robustness against * games that set their own LD_LIBRARY_PATH ignoring what they diff --git a/tests/shellcheck.sh b/tests/shellcheck.sh index b8336ec44..85dbaff91 100755 --- a/tests/shellcheck.sh +++ b/tests/shellcheck.sh @@ -36,10 +36,10 @@ cd "$G_TEST_SRCDIR" n=0 for shell_script in \ - *.sh \ pressure-vessel-unruntime \ pressure-vessel-unruntime-scout \ pressure-vessel-unruntime-test-ui \ + pressure-vessel-locale-gen \ tests/*.sh \ ; do n=$((n + 1)) diff --git a/tests/test-locale-gen.sh b/tests/test-locale-gen.sh new file mode 100755 index 000000000..3e44f5758 --- /dev/null +++ b/tests/test-locale-gen.sh @@ -0,0 +1,236 @@ +#!/bin/sh + +# 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. + +set -e +set -u + +me="$0" +me="$(readlink -f "$0")" +here="${me%/*}" +me="${me##*/}" + +# TAP test helper functions inspired by Perl Test::More. + +test_num=0 +any_failed= + +skip_all () { + echo "1..0 # SKIP $*" + exit 0 +} + +diag () { + echo "# $*" +} + +ok () { + test_num=$(( test_num + 1 )) + echo "ok $test_num - $*" +} + +skip () { + test_num=$(( test_num + 1 )) + echo "ok $test_num # SKIP $*" +} + +not_ok () { + test_num=$(( test_num + 1 )) + any_failed=yes + echo "not ok $test_num - $*" +} + +done_testing () { + echo "1..$test_num" + if [ -n "$any_failed" ]; then + exit 1 + fi + exit 0 +} + +if ! tmpdir="$(mktemp -d)"; then + skip_all "Unable to create a temporary directory" +fi + +if [ -z "${G_TEST_SRCDIR-}" ]; then + G_TEST_SRCDIR="${here%/*}" +fi + +if [ -z "${G_TEST_BUILDDIR-}" ]; then + G_TEST_BUILDDIR="${here%/*}" +fi + +if [ -n "${PRESSURE_VESSEL_UNINSTALLED-}" ]; then + PATH="${G_TEST_BUILDDIR}:${G_TEST_SRCDIR}:${PATH}" +fi + +locale_gen="pressure-vessel-locale-gen" +try_setlocale="pressure-vessel-try-setlocale" + +mkdir "$tmpdir/1" + +one_missing= + +# Locales used in the test below, chosen to include several that you +# probably don't have all of. +for locale in \ + cs_CZ \ + cy_GB.UTF-8 \ + en_AU.UTF-8 \ + en_DK \ + en_GB.ISO-8859-15 \ + en_US.UTF-8 \ + hu_HU.UTF-8 \ + it_IT@euro \ + mn_MN \ + ms_MY \ + nb_NO \ + nan_TW@latin \ + pl_PL \ + ta_IN \ + ti_ER \ +; do + if "$try_setlocale" "$locale" >/dev/null; then + diag "You already have $locale" + else + diag "You do not already have $locale" + one_missing=yes + break + fi +done + +if [ -z "$one_missing" ]; then + skip "You have all the test locales, cannot test" +else + failed= + + # Deliberately setting environment variables in subshell: + # shellcheck disable=SC2030 + ( + cd "$tmpdir/1" + export LC_CTYPE=cy_GB.UTF-8 + export LC_COLLATE=cs_CZ + export LC_IDENTIFICATION=it_IT@euro + export LC_MEASUREMENT=mn_MN + export LC_MESSAGES=en_GB.ISO-8859-15 + export LC_MONETARY=ms_MY + export LC_NUMERIC=nb_NO + export LC_NAME=nan_TW@latin + export LC_PAPER=pl_PL + export LC_TELEPHONE=ta_IN + export LC_TIME=ti_ER + + export HOST_LC_ALL=hu_HU.UTF-8 + export LANG=en_DK + export LC_ALL=en_AU.UTF-8 + "$locale_gen" + ) || failed="$?" + + if [ "$failed" = 72 ]; then + ok "ran $locale_gen, got status 72" + elif [ -n "$failed" ]; then + not_ok "failed to run $locale_gen" + else + not_ok "$locale_gen did not report missing locales" + fi + + for locale in \ + cs_CZ \ + cy_GB.UTF-8 \ + en_AU.UTF-8 \ + en_DK \ + en_GB.ISO-8859-15 \ + en_US.UTF-8 \ + hu_HU.UTF-8 \ + it_IT@euro \ + mn_MN \ + ms_MY \ + nb_NO \ + nan_TW@latin \ + pl_PL \ + ta_IN \ + ti_ER \ + ; do + if [ -e "$tmpdir/1/$locale/LC_IDENTIFICATION" ]; then + ok "$tmpdir/1/$locale/LC_IDENTIFICATION exists" + else + not_ok "$tmpdir/1/$locale/LC_IDENTIFICATION not generated" + fi + done +fi + +mkdir "$tmpdir/2" + +if "$try_setlocale" "en_US.UTF-8" >/dev/null; then + failed= + + # Deliberately setting environment variables in subshell: + # shellcheck disable=SC2031 + ( + cd "$tmpdir/2" + unset LC_CTYPE + unset LC_COLLATE + unset LC_IDENTIFICATION + unset LC_MEASUREMENT + unset LC_MESSAGES + unset LC_MONETARY + unset LC_NUMERIC + unset LC_NAME + unset LC_PAPER + unset LC_TELEPHONE + unset LC_TIME + + export HOST_LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + "$locale_gen" + ) || failed=yes + + if [ -n "$failed" ]; then + not_ok "failed to run $locale_gen" + else + ok "ran $locale_gen" + fi + + failed= + + for x in "$tmpdir/2"/*; do + if [ -e "$x" ]; then + not_ok "$locale_gen generated $x but should not have generated anything" + failed=yes + fi + done + + if [ -z "$failed" ]; then + ok "$locale_gen did not redundantly generate en_US.UTF-8" + fi +else + skip "Cannot test with existing en_US.UTF-8 locale" +fi + +rm -fr "$tmpdir" + +done_testing + +# vim:set sw=4 sts=4 et: -- GitLab