#!/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_output_dir=. 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. # # Instead of just /usr/share/i18n/SUPPORTED, we also try to parse # /etc/locale.gen because in some distributions, e.g. Arch Linux, # /usr/share/i18n/SUPPORTED might not be present. # # 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="/etc/locale.gen /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" \ "${opt_output_dir}/${language}${underscore_territory}.utf8${at_modifier}" if [ -d "$opt_output_dir/$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" \ "${opt_output_dir}/${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="${opt_output_dir}" 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 "--output-dir DIR, -o DIR" echo " Create locales in the given directory [default: .]" 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},output-dir:,output-directory:" getopt_temp="${getopt_temp},verbose" getopt_temp="$(getopt -o 'o:' --long "$getopt_temp" -n "$me" -- "$@")" eval "set -- $getopt_temp" unset getopt_temp while [ "$#" -gt 0 ]; do case "$1" in (-o | --output-dir | --output-directory) opt_output_dir="$2" shift 2 ;; (--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: