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')
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')
#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"
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
+}
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);
#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;
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));
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);
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);
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;
bool modified = false;
int interactive, r;
char **i;
+ bool use_localegen;
assert(m);
assert(c);
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]);
/* 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;
}
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");