blob: 8f18ecd0d2fd79cfbed4a260a278772a94624938 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#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 "localed-util.h"
#include "macro.h"
#include "mkdir-label.h"
#include "nulstr-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
static bool startswith_comma(const char *s, const char *prefix) {
assert(s);
assert(prefix);
s = startswith(s, prefix);
if (!s)
return false;
return IN_SET(*s, ',', '\0');
}
static const char* systemd_kbd_model_map(void) {
const char* s;
s = getenv("SYSTEMD_KBD_MODEL_MAP");
if (s)
return s;
return SYSTEMD_KBD_MODEL_MAP;
}
static const char* systemd_language_fallback_map(void) {
const char* s;
s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
if (s)
return s;
return SYSTEMD_LANGUAGE_FALLBACK_MAP;
}
void x11_context_clear(X11Context *xc) {
assert(xc);
xc->layout = mfree(xc->layout);
xc->options = mfree(xc->options);
xc->model = mfree(xc->model);
xc->variant = mfree(xc->variant);
}
void x11_context_replace(X11Context *dest, X11Context *src) {
assert(dest);
assert(src);
x11_context_clear(dest);
*dest = *src;
*src = (X11Context) {};
}
bool x11_context_isempty(const X11Context *xc) {
assert(xc);
return
isempty(xc->layout) &&
isempty(xc->model) &&
isempty(xc->variant) &&
isempty(xc->options);
}
void x11_context_empty_to_null(X11Context *xc) {
assert(xc);
/* Do not call x11_context_clear() for the passed object. */
xc->layout = empty_to_null(xc->layout);
xc->model = empty_to_null(xc->model);
xc->variant = empty_to_null(xc->variant);
xc->options = empty_to_null(xc->options);
}
bool x11_context_is_safe(const X11Context *xc) {
assert(xc);
return
(!xc->layout || string_is_safe(xc->layout)) &&
(!xc->model || string_is_safe(xc->model)) &&
(!xc->variant || string_is_safe(xc->variant)) &&
(!xc->options || string_is_safe(xc->options));
}
bool x11_context_equal(const X11Context *a, const X11Context *b) {
assert(a);
assert(b);
return
streq_ptr(a->layout, b->layout) &&
streq_ptr(a->model, b->model) &&
streq_ptr(a->variant, b->variant) &&
streq_ptr(a->options, b->options);
}
int x11_context_copy(X11Context *dest, const X11Context *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !x11_context_isempty(dest);
x11_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->layout, src->layout);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->model, src->model);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->variant, src->variant);
if (r < 0)
return r;
modified = modified || r > 0;
r = free_and_strdup(&dest->options, src->options);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
void vc_context_clear(VCContext *vc) {
assert(vc);
vc->keymap = mfree(vc->keymap);
vc->toggle = mfree(vc->toggle);
}
void vc_context_replace(VCContext *dest, VCContext *src) {
assert(dest);
assert(src);
vc_context_clear(dest);
*dest = *src;
*src = (VCContext) {};
}
bool vc_context_isempty(const VCContext *vc) {
assert(vc);
return
isempty(vc->keymap) &&
isempty(vc->toggle);
}
void vc_context_empty_to_null(VCContext *vc) {
assert(vc);
/* Do not call vc_context_clear() for the passed object. */
vc->keymap = empty_to_null(vc->keymap);
vc->toggle = empty_to_null(vc->toggle);
}
bool vc_context_equal(const VCContext *a, const VCContext *b) {
assert(a);
assert(b);
return
streq_ptr(a->keymap, b->keymap) &&
streq_ptr(a->toggle, b->toggle);
}
int vc_context_copy(VCContext *dest, const VCContext *src) {
bool modified;
int r;
assert(dest);
if (dest == src)
return 0;
if (!src) {
modified = !vc_context_isempty(dest);
vc_context_clear(dest);
return modified;
}
r = free_and_strdup(&dest->keymap, src->keymap);
if (r < 0)
return r;
modified = r > 0;
r = free_and_strdup(&dest->toggle, src->toggle);
if (r < 0)
return r;
modified = modified || r > 0;
return modified;
}
void context_clear(Context *c) {
assert(c);
locale_context_clear(&c->locale_context);
x11_context_clear(&c->x11_from_xorg);
x11_context_clear(&c->x11_from_vc);
vc_context_clear(&c->vc);
c->locale_cache = sd_bus_message_unref(c->locale_cache);
c->x11_cache = sd_bus_message_unref(c->x11_cache);
c->vc_cache = sd_bus_message_unref(c->vc_cache);
c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry);
};
X11Context *context_get_x11_context(Context *c) {
assert(c);
if (!x11_context_isempty(&c->x11_from_vc))
return &c->x11_from_vc;
if (!x11_context_isempty(&c->x11_from_xorg))
return &c->x11_from_xorg;
return &c->x11_from_vc;
}
int locale_read_data(Context *c, sd_bus_message *m) {
assert(c);
/* Do not try to re-read the file within single bus operation. */
if (m) {
if (m == c->locale_cache)
return 0;
sd_bus_message_unref(c->locale_cache);
c->locale_cache = sd_bus_message_ref(m);
}
return locale_context_load(&c->locale_context, LOCALE_LOAD_LOCALE_CONF | LOCALE_LOAD_ENVIRONMENT | LOCALE_LOAD_SIMPLIFY);
}
int vconsole_read_data(Context *c, sd_bus_message *m) {
_cleanup_close_ int fd = -EBADF;
struct stat st;
assert(c);
/* Do not try to re-read the file within single bus operation. */
if (m) {
if (m == c->vc_cache)
return 0;
sd_bus_message_unref(c->vc_cache);
c->vc_cache = sd_bus_message_ref(m);
}
fd = RET_NERRNO(open("/etc/vconsole.conf", O_CLOEXEC | O_PATH));
if (fd == -ENOENT) {
c->vc_stat = (struct stat) {};
vc_context_clear(&c->vc);
x11_context_clear(&c->x11_from_vc);
return 0;
}
if (fd < 0)
return fd;
if (fstat(fd, &st) < 0)
return -errno;
/* If the file is not changed, then we do not need to re-read */
if (stat_inode_unmodified(&c->vc_stat, &st))
return 0;
c->vc_stat = st;
vc_context_clear(&c->vc);
x11_context_clear(&c->x11_from_vc);
return parse_env_file_fd(fd, "/etc/vconsole.conf",
"KEYMAP", &c->vc.keymap,
"KEYMAP_TOGGLE", &c->vc.toggle,
"XKBLAYOUT", &c->x11_from_vc.layout,
"XKBMODEL", &c->x11_from_vc.model,
"XKBVARIANT", &c->x11_from_vc.variant,
"XKBOPTIONS", &c->x11_from_vc.options);
}
int x11_read_data(Context *c, sd_bus_message *m) {
_cleanup_close_ int fd = -EBADF, fd_ro = -EBADF;
_cleanup_fclose_ FILE *f = NULL;
bool in_section = false;
struct stat st;
int r;
assert(c);
/* Do not try to re-read the file within single bus operation. */
if (m) {
if (m == c->x11_cache)
return 0;
sd_bus_message_unref(c->x11_cache);
c->x11_cache = sd_bus_message_ref(m);
}
fd = RET_NERRNO(open("/etc/X11/xorg.conf.d/00-keyboard.conf", O_CLOEXEC | O_PATH));
if (fd == -ENOENT) {
c->x11_stat = (struct stat) {};
x11_context_clear(&c->x11_from_xorg);
return 0;
}
if (fd < 0)
return fd;
if (fstat(fd, &st) < 0)
return -errno;
/* If the file is not changed, then we do not need to re-read */
if (stat_inode_unmodified(&c->x11_stat, &st))
return 0;
c->x11_stat = st;
x11_context_clear(&c->x11_from_xorg);
fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
if (fd_ro < 0)
return fd_ro;
f = fdopen(fd_ro, "re");
if (!f)
return -errno;
TAKE_FD(fd_ro);
for (;;) {
_cleanup_free_ char *line = NULL;
char *l;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
l = strstrip(line);
if (IN_SET(l[0], 0, '#'))
continue;
if (in_section && first_word(l, "Option")) {
_cleanup_strv_free_ char **a = NULL;
r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return r;
if (strv_length(a) == 3) {
char **p = NULL;
if (streq(a[1], "XkbLayout"))
p = &c->x11_from_xorg.layout;
else if (streq(a[1], "XkbModel"))
p = &c->x11_from_xorg.model;
else if (streq(a[1], "XkbVariant"))
p = &c->x11_from_xorg.variant;
else if (streq(a[1], "XkbOptions"))
p = &c->x11_from_xorg.options;
if (p)
free_and_replace(*p, a[2]);
}
} else if (!in_section && first_word(l, "Section")) {
_cleanup_strv_free_ char **a = NULL;
r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return -ENOMEM;
if (strv_length(a) == 2 && streq(a[1], "InputClass"))
in_section = true;
} else if (in_section && first_word(l, "EndSection"))
in_section = false;
}
return 0;
}
int vconsole_write_data(Context *c) {
_cleanup_strv_free_ char **l = NULL;
const X11Context *xc;
int r;
assert(c);
xc = context_get_x11_context(c);
r = load_env_file(NULL, "/etc/vconsole.conf", &l);
if (r < 0 && r != -ENOENT)
return r;
r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap));
if (r < 0)
return r;
r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant));
if (r < 0)
return r;
r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options));
if (r < 0)
return r;
if (strv_isempty(l)) {
if (unlink("/etc/vconsole.conf") < 0)
return errno == ENOENT ? 0 : -errno;
c->vc_stat = (struct stat) {};
return 0;
}
r = write_env_file_label("/etc/vconsole.conf", l);
if (r < 0)
return r;
if (stat("/etc/vconsole.conf", &c->vc_stat) < 0)
return -errno;
return 0;
}
int x11_write_data(Context *c) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_(unlink_and_freep) char *temp_path = NULL;
const X11Context *xc;
int r;
assert(c);
xc = context_get_x11_context(c);
if (x11_context_isempty(xc)) {
if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
return errno == ENOENT ? 0 : -errno;
c->x11_stat = (struct stat) {};
return 0;
}
(void) mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
if (r < 0)
return r;
(void) fchmod(fileno(f), 0644);
fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
"# probably wise not to edit this file manually. Use localectl(1) to\n"
"# instruct systemd-localed to update it.\n"
"Section \"InputClass\"\n"
" Identifier \"system-keyboard\"\n"
" MatchIsKeyboard \"on\"\n", f);
if (!isempty(xc->layout))
fprintf(f, " Option \"XkbLayout\" \"%s\"\n", xc->layout);
if (!isempty(xc->model))
fprintf(f, " Option \"XkbModel\" \"%s\"\n", xc->model);
if (!isempty(xc->variant))
fprintf(f, " Option \"XkbVariant\" \"%s\"\n", xc->variant);
if (!isempty(xc->options))
fprintf(f, " Option \"XkbOptions\" \"%s\"\n", xc->options);
fputs("EndSection\n", f);
r = fflush_sync_and_check(f);
if (r < 0)
return r;
if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
return -errno;
if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &c->x11_stat) < 0)
return -errno;
return 0;
}
static int read_next_mapping(
const char *filename,
unsigned min_fields,
unsigned max_fields,
FILE *f,
unsigned *n,
char ***ret) {
assert(f);
assert(n);
assert(ret);
for (;;) {
_cleanup_strv_free_ char **b = NULL;
_cleanup_free_ char *line = NULL;
size_t length;
const char *l;
int r;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
(*n)++;
l = strstrip(line);
if (IN_SET(l[0], 0, '#'))
continue;
r = strv_split_full(&b, l, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return r;
length = strv_length(b);
if (length < min_fields || length > max_fields) {
log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
continue;
}
*ret = TAKE_PTR(b);
return 1;
}
*ret = NULL;
return 0;
}
int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
_cleanup_fclose_ FILE *f = NULL;
const char *map;
int r;
assert(vc);
assert(ret);
if (isempty(vc->keymap)) {
*ret = (X11Context) {};
return 0;
}
map = systemd_kbd_model_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0) {
*ret = (X11Context) {};
return 0;
}
if (!streq(vc->keymap, a[0]))
continue;
return x11_context_copy(ret,
&(X11Context) {
.layout = empty_or_dash_to_null(a[1]),
.model = empty_or_dash_to_null(a[2]),
.variant = empty_or_dash_to_null(a[3]),
.options = empty_or_dash_to_null(a[4]),
});
}
}
int find_converted_keymap(const X11Context *xc, char **ret) {
_cleanup_free_ char *n = NULL;
assert(xc);
assert(!isempty(xc->layout));
assert(ret);
if (xc->variant)
n = strjoin(xc->layout, "-", xc->variant);
else
n = strdup(xc->layout);
if (!n)
return -ENOMEM;
NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
_cleanup_free_ char *p = NULL, *pz = NULL;
bool uncompressed;
p = strjoin(dir, "xkb/", n, ".map");
pz = strjoin(dir, "xkb/", n, ".map.gz");
if (!p || !pz)
return -ENOMEM;
uncompressed = access(p, F_OK) == 0;
if (uncompressed || access(pz, F_OK) == 0) {
log_debug("Found converted keymap %s at %s", n, uncompressed ? p : pz);
*ret = TAKE_PTR(n);
return 1;
}
}
*ret = NULL;
return 0;
}
int find_legacy_keymap(const X11Context *xc, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *new_keymap = NULL;
unsigned best_matching = 0;
int r;
assert(xc);
assert(!isempty(xc->layout));
map = systemd_kbd_model_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL;
unsigned matching = 0;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
return r;
if (r == 0)
break;
/* Determine how well matching this entry is */
if (streq(xc->layout, a[1]))
/* If we got an exact match, this is the best */
matching = 10;
else {
/* We have multiple X layouts, look for an
* entry that matches our key with everything
* but the first layout stripped off. */
if (startswith_comma(xc->layout, a[1]))
matching = 5;
else {
_cleanup_free_ char *x = NULL;
/* If that didn't work, strip off the
* other layouts from the entry, too */
x = strdupcspn(a[1], ",");
if (!x)
return -ENOMEM;
if (startswith_comma(xc->layout, x))
matching = 1;
}
}
if (matching > 0) {
if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
matching++;
if (streq_ptr(xc->variant, a[3])) {
matching++;
if (streq_ptr(xc->options, a[4]))
matching++;
}
}
}
/* The best matching entry so far, then let's save that */
if (matching >= MAX(best_matching, 1u)) {
log_debug("Found legacy keymap %s with score %u", a[0], matching);
if (matching > best_matching) {
best_matching = matching;
r = free_and_strdup(&new_keymap, a[0]);
if (r < 0)
return r;
}
}
}
if (best_matching < 10 && !isempty(xc->layout)) {
_cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
/* The best match is only the first part of the X11
* keymap. Check if we have a converted map which
* matches just the first layout.
*/
l = strndup(xc->layout, strcspn(xc->layout, ","));
if (!l)
return -ENOMEM;
if (!isempty(xc->variant)) {
v = strndup(xc->variant, strcspn(xc->variant, ","));
if (!v)
return -ENOMEM;
}
r = find_converted_keymap(
&(X11Context) {
.layout = l,
.variant = v,
},
&converted);
if (r < 0)
return r;
if (r > 0)
free_and_replace(new_keymap, converted);
}
*ret = TAKE_PTR(new_keymap);
return !!*ret;
}
int find_language_fallback(const char *lang, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
unsigned n = 0;
int r;
assert(lang);
assert(ret);
map = systemd_language_fallback_map();
f = fopen(map, "re");
if (!f)
return -errno;
for (;;) {
_cleanup_strv_free_ char **a = NULL;
r = read_next_mapping(map, 2, 2, f, &n, &a);
if (r <= 0)
return r;
if (streq(lang, a[0])) {
assert(strv_length(a) == 2);
*ret = TAKE_PTR(a[1]);
return 1;
}
}
}
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
_cleanup_free_ char *keymap = NULL;
int r;
assert(xc);
assert(ret);
if (isempty(xc->layout)) {
*ret = (VCContext) {};
return 0;
}
r = find_converted_keymap(xc, &keymap);
if (r == 0)
r = find_legacy_keymap(xc, &keymap);
if (r < 0)
return r;
*ret = (VCContext) {
.keymap = TAKE_PTR(keymap),
};
return 0;
}
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), COPY_ALL_XATTRS);
if (r < 0)
log_debug_errno(r, "Failed to copy all xattrs from old to new /etc/locale.gen file, ignoring: %m");
}
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
}