summaryrefslogtreecommitdiffstats
path: root/debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch')
-rw-r--r--debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch448
1 files changed, 448 insertions, 0 deletions
diff --git a/debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch b/debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch
new file mode 100644
index 0000000..6752b9d
--- /dev/null
+++ b/debian/patches/localed-Run-locale-gen-if-available-to-generate-missing-l.patch
@@ -0,0 +1,448 @@
+From: Matthias Klumpp <matthias@tenstral.net>
+Date: Fri, 8 Jan 2021 23:59:38 +0100
+Subject: localed: Run locale-gen if available to generate missing locale
+
+This change improves integration with distributions using locale-gen to
+generate missing locale on-demand, like Debian-based distributions
+(Debian/Ubuntu/PureOS/Tanglu/...) and Arch Linux.
+We only ever enable new locales for generation, and never disable them.
+Furthermore, we only generate UTF-8 locale.
+
+This feature is only used if explicitly enabled at compile-time, and
+will also be inert at runtime if the locale-gen binary is missing.
+
+(cherry picked from commit 8f20232fcb52dbe6255f3df6101fc057af90bcfa)
+---
+ meson.build | 8 ++
+ meson_options.txt | 2 +
+ src/locale/keymap-util.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++
+ src/locale/keymap-util.h | 4 +
+ src/locale/localectl.c | 6 +-
+ src/locale/localed.c | 59 ++++++++++++-
+ 6 files changed, 286 insertions(+), 4 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index 580964c..cf93f38 100644
+--- a/meson.build
++++ b/meson.build
+@@ -833,6 +833,14 @@ if default_locale == ''
+ endif
+ conf.set_quoted('SYSTEMD_DEFAULT_LOCALE', default_locale)
+
++localegen_path = get_option('localegen-path')
++have = false
++if localegen_path != ''
++ conf.set_quoted('LOCALEGEN_PATH', localegen_path)
++ have = true
++endif
++conf.set10('HAVE_LOCALEGEN', have)
++
+ conf.set_quoted('GETTEXT_PACKAGE', meson.project_name())
+
+ service_watchdog = get_option('service-watchdog')
+diff --git a/meson_options.txt b/meson_options.txt
+index 2435cce..4ffdf7f 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -238,6 +238,8 @@ option('gshadow', type : 'boolean',
+ description : 'support for shadow group')
+ option('default-locale', type : 'string', value : '',
+ description : 'default locale used when /etc/locale.conf does not exist')
++option('localegen-path', type : 'string', value : '',
++ description : 'absolute path to the locale-gen binary in case the system is using locale-gen')
+ option('service-watchdog', type : 'string', value : '3min',
+ description : 'default watchdog setting for systemd services')
+
+diff --git a/src/locale/keymap-util.c b/src/locale/keymap-util.c
+index cb8153f..697133a 100644
+--- a/src/locale/keymap-util.c
++++ b/src/locale/keymap-util.c
+@@ -6,18 +6,21 @@
+ #include <unistd.h>
+
+ #include "bus-polkit.h"
++#include "copy.h"
+ #include "env-file-label.h"
+ #include "env-file.h"
+ #include "env-util.h"
+ #include "fd-util.h"
+ #include "fileio-label.h"
+ #include "fileio.h"
++#include "fs-util.h"
+ #include "kbd-util.h"
+ #include "keymap-util.h"
+ #include "locale-util.h"
+ #include "macro.h"
+ #include "mkdir.h"
+ #include "nulstr-util.h"
++#include "process-util.h"
+ #include "string-util.h"
+ #include "strv.h"
+ #include "tmpfile-util.h"
+@@ -780,3 +783,211 @@ int x11_convert_to_vconsole(Context *c) {
+
+ return modified;
+ }
++
++bool locale_gen_check_available(void) {
++#if HAVE_LOCALEGEN
++ if (access(LOCALEGEN_PATH, X_OK) < 0) {
++ if (errno != ENOENT)
++ log_warning_errno(errno, "Unable to determine whether " LOCALEGEN_PATH " exists and is executable, assuming it is not: %m");
++ return false;
++ }
++ if (access("/etc/locale.gen", F_OK) < 0) {
++ if (errno != ENOENT)
++ log_warning_errno(errno, "Unable to determine whether /etc/locale.gen exists, assuming it does not: %m");
++ return false;
++ }
++ return true;
++#else
++ return false;
++#endif
++}
++
++#if HAVE_LOCALEGEN
++static bool locale_encoding_is_utf8_or_unspecified(const char *locale) {
++ const char *c = strchr(locale, '.');
++ return !c || strcaseeq(c, ".UTF-8") || strcasestr(locale, ".UTF-8@");
++}
++
++static int locale_gen_locale_supported(const char *locale_entry) {
++ /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported,
++ * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case
++ * the distributor has not provided us with a SUPPORTED file to check
++ * locale for validity. */
++
++ _cleanup_fclose_ FILE *f = NULL;
++ int r;
++
++ assert(locale_entry);
++
++ /* Locale templates without country code are never supported */
++ if (!strstr(locale_entry, "_"))
++ return -EINVAL;
++
++ f = fopen("/usr/share/i18n/SUPPORTED", "re");
++ if (!f) {
++ if (errno == ENOENT)
++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
++ "Unable to check validity of locale entry %s: /usr/share/i18n/SUPPORTED does not exist",
++ locale_entry);
++ return -errno;
++ }
++
++ for (;;) {
++ _cleanup_free_ char *line = NULL;
++
++ r = read_line(f, LONG_LINE_MAX, &line);
++ if (r < 0)
++ return log_debug_errno(r, "Failed to read /usr/share/i18n/SUPPORTED: %m");
++ if (r == 0)
++ return 0;
++
++ line = strstrip(line);
++ if (strcaseeq_ptr(line, locale_entry))
++ return 1;
++ }
++}
++#endif
++
++int locale_gen_enable_locale(const char *locale) {
++#if HAVE_LOCALEGEN
++ _cleanup_fclose_ FILE *fr = NULL, *fw = NULL;
++ _cleanup_(unlink_and_freep) char *temp_path = NULL;
++ _cleanup_free_ char *locale_entry = NULL;
++ bool locale_enabled = false, first_line = false;
++ bool write_new = false;
++ int r;
++
++ if (isempty(locale))
++ return 0;
++
++ if (locale_encoding_is_utf8_or_unspecified(locale)) {
++ locale_entry = strjoin(locale, " UTF-8");
++ if (!locale_entry)
++ return -ENOMEM;
++ } else
++ return -ENOEXEC; /* We do not process non-UTF-8 locale */
++
++ r = locale_gen_locale_supported(locale_entry);
++ if (r == 0)
++ return -EINVAL;
++ if (r < 0 && r != -EOPNOTSUPP)
++ return r;
++
++ fr = fopen("/etc/locale.gen", "re");
++ if (!fr) {
++ if (errno != ENOENT)
++ return -errno;
++ write_new = true;
++ }
++
++ r = fopen_temporary("/etc/locale.gen", &fw, &temp_path);
++ if (r < 0)
++ return r;
++
++ if (write_new)
++ (void) fchmod(fileno(fw), 0644);
++ else {
++ /* apply mode & xattrs of the original file to new file */
++ r = copy_access(fileno(fr), fileno(fw));
++ if (r < 0)
++ return r;
++ r = copy_xattr(fileno(fr), fileno(fw));
++ if (r < 0)
++ return r;
++ }
++
++ if (!write_new) {
++ /* The config file ends with a line break, which we do not want to include before potentially appending a new locale
++ * instead of uncommenting an existing line. By prepending linebreaks, we can avoid buffering this file but can still write
++ * a nice config file without empty lines */
++ first_line = true;
++ for (;;) {
++ _cleanup_free_ char *line = NULL;
++ char *line_locale;
++
++ r = read_line(fr, LONG_LINE_MAX, &line);
++ if (r < 0)
++ return r;
++ if (r == 0)
++ break;
++
++ if (locale_enabled) {
++ /* Just complete writing the file if the new locale was already enabled */
++ if (!first_line)
++ fputc('\n', fw);
++ fputs(line, fw);
++ first_line = false;
++ continue;
++ }
++
++ line = strstrip(line);
++ if (isempty(line)) {
++ fputc('\n', fw);
++ first_line = false;
++ continue;
++ }
++
++ line_locale = line;
++ if (line_locale[0] == '#')
++ line_locale = strstrip(line_locale + 1);
++ else if (strcaseeq_ptr(line_locale, locale_entry))
++ return 0; /* the file already had our locale activated, so skip updating it */
++
++ if (strcaseeq_ptr(line_locale, locale_entry)) {
++ /* Uncomment existing line for new locale */
++ if (!first_line)
++ fputc('\n', fw);
++ fputs(locale_entry, fw);
++ locale_enabled = true;
++ first_line = false;
++ continue;
++ }
++
++ /* The line was not for the locale we want to enable, just copy it */
++ if (!first_line)
++ fputc('\n', fw);
++ fputs(line, fw);
++ first_line = false;
++ }
++ }
++
++ /* Add locale to enable to the end of the file if it was not found as commented line */
++ if (!locale_enabled) {
++ if (!write_new)
++ fputc('\n', fw);
++ fputs(locale_entry, fw);
++ }
++ fputc('\n', fw);
++
++ r = fflush_sync_and_check(fw);
++ if (r < 0)
++ return r;
++
++ if (rename(temp_path, "/etc/locale.gen") < 0)
++ return -errno;
++ temp_path = mfree(temp_path);
++
++ return 0;
++#else
++ return -EOPNOTSUPP;
++#endif
++}
++
++int locale_gen_run(void) {
++#if HAVE_LOCALEGEN
++ pid_t pid;
++ int r;
++
++ r = safe_fork("(sd-localegen)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, &pid);
++ if (r < 0)
++ return r;
++ if (r == 0) {
++ execl(LOCALEGEN_PATH, LOCALEGEN_PATH, NULL);
++ _exit(EXIT_FAILURE);
++ }
++
++ return 0;
++#else
++ return -EOPNOTSUPP;
++#endif
++}
+diff --git a/src/locale/keymap-util.h b/src/locale/keymap-util.h
+index 4997647..c087dbc 100644
+--- a/src/locale/keymap-util.h
++++ b/src/locale/keymap-util.h
+@@ -42,3 +42,7 @@ int x11_convert_to_vconsole(Context *c);
+ int x11_write_data(Context *c);
+ void locale_simplify(char *locale[_VARIABLE_LC_MAX]);
+ int locale_write_data(Context *c, char ***settings);
++
++bool locale_gen_check_available(void);
++int locale_gen_enable_locale(const char *locale);
++int locale_gen_run(void);
+diff --git a/src/locale/localectl.c b/src/locale/localectl.c
+index 7d2e887..7d3d3f8 100644
+--- a/src/locale/localectl.c
++++ b/src/locale/localectl.c
+@@ -26,6 +26,9 @@
+ #include "verbs.h"
+ #include "virt.h"
+
++/* Enough time for locale-gen to finish server-side (in case it is in use) */
++#define LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
++
+ static PagerFlags arg_pager_flags = 0;
+ static bool arg_ask_password = true;
+ static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
+@@ -176,7 +179,8 @@ static int set_locale(int argc, char **argv, void *userdata) {
+ if (r < 0)
+ return bus_log_create_error(r);
+
+- r = sd_bus_call(bus, m, 0, &error, NULL);
++ /* We use a longer timeout for the method call in case localed is running locale-gen */
++ r = sd_bus_call(bus, m, LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+
+diff --git a/src/locale/localed.c b/src/locale/localed.c
+index 736dacd..12073bd 100644
+--- a/src/locale/localed.c
++++ b/src/locale/localed.c
+@@ -262,6 +262,7 @@ static int property_get_xkb(
+ static int process_locale_list_item(
+ const char *assignment,
+ char *new_locale[static _VARIABLE_LC_MAX],
++ bool use_localegen,
+ sd_bus_error *error) {
+
+ assert(assignment);
+@@ -283,7 +284,7 @@ static int process_locale_list_item(
+
+ if (!locale_is_valid(e))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s is not valid, refusing.", e);
+- if (locale_is_installed(e) <= 0)
++ if (!use_localegen && locale_is_installed(e) <= 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s not installed, refusing.", e);
+ if (new_locale[p])
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale variable %s set twice, refusing.", name);
+@@ -298,6 +299,47 @@ static int process_locale_list_item(
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale assignment %s not valid, refusing.", assignment);
+ }
+
++static int locale_gen_process_locale(char *new_locale[static _VARIABLE_LC_MAX],
++ sd_bus_error *error) {
++ int r;
++ assert(new_locale);
++
++ for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
++ if (p == VARIABLE_LANGUAGE)
++ continue;
++ if (isempty(new_locale[p]))
++ continue;
++ if (locale_is_installed(new_locale[p]))
++ continue;
++
++ r = locale_gen_enable_locale(new_locale[p]);
++ if (r == -ENOEXEC) {
++ log_error_errno(r, "Refused to enable locale for generation: %m");
++ return sd_bus_error_setf(error,
++ SD_BUS_ERROR_INVALID_ARGS,
++ "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s",
++ new_locale[p]);
++ } else if (r == -EINVAL) {
++ log_error_errno(r, "Failed to enable invalid locale %s for generation.", new_locale[p]);
++ return sd_bus_error_setf(error,
++ SD_BUS_ERROR_INVALID_ARGS,
++ "Can not enable locale generation for invalid locale: %s",
++ new_locale[p]);
++ } else if (r < 0) {
++ log_error_errno(r, "Failed to enable locale for generation: %m");
++ return sd_bus_error_set_errnof(error, r, "Failed to enable locale generation: %m");
++ }
++
++ r = locale_gen_run();
++ if (r < 0) {
++ log_error_errno(r, "Failed to generate locale: %m");
++ return sd_bus_error_set_errnof(error, r, "Failed to generate locale: %m");
++ }
++ }
++
++ return 0;
++}
++
+ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ _cleanup_(locale_variables_freep) char *new_locale[_VARIABLE_LC_MAX] = {};
+ _cleanup_strv_free_ char **settings = NULL, **l = NULL;
+@@ -305,6 +347,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+ bool modified = false;
+ int interactive, r;
+ char **i;
++ bool use_localegen;
+
+ assert(m);
+ assert(c);
+@@ -317,11 +360,13 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+ if (r < 0)
+ return r;
+
++ use_localegen = locale_gen_check_available();
++
+ /* If single locale without variable name is provided, then we assume it is LANG=. */
+ if (strv_length(l) == 1 && !strchr(l[0], '=')) {
+ if (!locale_is_valid(l[0]))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid locale specification: %s", l[0]);
+- if (locale_is_installed(l[0]) <= 0)
++ if (!use_localegen && locale_is_installed(l[0]) <= 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified locale is not installed: %s", l[0]);
+
+ new_locale[VARIABLE_LANG] = strdup(l[0]);
+@@ -333,7 +378,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+
+ /* Check whether a variable is valid */
+ STRV_FOREACH(i, l) {
+- r = process_locale_list_item(*i, new_locale, error);
++ r = process_locale_list_item(*i, new_locale, use_localegen, error);
+ if (r < 0)
+ return r;
+ }
+@@ -392,9 +437,17 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
++ /* Generate locale in case it is missing and the system is using locale-gen */
++ if (use_localegen) {
++ r = locale_gen_process_locale(new_locale, error);
++ if (r < 0)
++ return r;
++ }
++
+ for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
+ free_and_replace(c->locale[p], new_locale[p]);
+
++ /* Write locale configuration */
+ r = locale_write_data(c, &settings);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set locale: %m");