localectl.c revision 4d7859d173282e16bb75254c2b4ec14a915ef30b
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering/***
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering This file is part of systemd.
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering Copyright 2012 Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering Copyright 2013 Kay Sievers
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering systemd is free software; you can redistribute it and/or modify it
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering under the terms of the GNU Lesser General Public License as published by
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering (at your option) any later version.
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering systemd is distributed in the hope that it will be useful, but
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering Lesser General Public License for more details.
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering You should have received a copy of the GNU Lesser General Public License
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering***/
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering#include <locale.h>
4871690d9e32608bbd9b18505b5326c2079c9690Allin Cottrell#include <stdlib.h>
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering#include <stdbool.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <unistd.h>
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include <getopt.h>
6482f6269c87d2249e52e889a63adbdd50f2d691Ronny Chevalier#include <string.h>
afc5dbf37fd2399d37976388d9dd9ab470ecf446Lennart Poettering#include <ftw.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <sys/mman.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <fcntl.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
0b452006de98294d1690f045f6ea2f7f6630ec3bRonny Chevalier#include "sd-bus.h"
15a5e95075a7f6007dd97b2a165c8ed16fe683dfLennart Poettering#include "bus-util.h"
288a74cce597f81d3ba01d8a5ca7d2ba5b654b7eRonny Chevalier#include "bus-error.h"
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering#include "bus-message.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "util.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "spawn-polkit-agent.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "build.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "strv.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "pager.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "set.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "path-util.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog#include "utf8.h"
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic bool arg_no_pager = false;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic bool arg_ask_password = true;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic char *arg_host = NULL;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic bool arg_convert = true;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poetteringstatic void pager_open_if_enabled(void) {
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (arg_no_pager)
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering return;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b3154df7e2773332bb814e167187367a0ccae4aLennart Poettering pager_open(false);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering}
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic void polkit_agent_open_if_enabled(void) {
5ffa8c818120e35c89becd938d160235c069dd12Zbigniew Jędrzejewski-Szmek
5ffa8c818120e35c89becd938d160235c069dd12Zbigniew Jędrzejewski-Szmek /* Open the polkit agent as a child process if necessary */
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (!arg_ask_password)
fb4729006a7174472e8a435b0887e532cd6217fcZbigniew Jędrzejewski-Szmek return;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering polkit_agent_open();
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering}
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poetteringtypedef struct StatusInfo {
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering char **locale;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering const char *vconsole_keymap;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering const char *vconsole_keymap_toggle;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog const char *x11_layout;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog const char *x11_model;
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog const char *x11_variant;
5ffa8c818120e35c89becd938d160235c069dd12Zbigniew Jędrzejewski-Szmek const char *x11_options;
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek} StatusInfo;
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskogstatic void print_status_info(StatusInfo *i) {
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog assert(i);
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog if (strv_isempty(i->locale))
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering puts(" System Locale: n/a\n");
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering else {
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering char **j;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf(" System Locale: %s\n", i->locale[0]);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering STRV_FOREACH(j, i->locale + 1)
5ffa8c818120e35c89becd938d160235c069dd12Zbigniew Jędrzejewski-Szmek printf(" %s\n", *j);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering }
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (!isempty(i->vconsole_keymap_toggle))
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf(" X11 Layout: %s\n", strna(i->x11_layout));
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (!isempty(i->x11_model))
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf(" X11 Model: %s\n", i->x11_model);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (!isempty(i->x11_variant))
ad79565d6b37bcc93cf773a39b975e5b85d122daUmut Tezduyar Lindskog printf(" X11 Variant: %s\n", i->x11_variant);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (!isempty(i->x11_options))
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering printf(" X11 Options: %s\n", i->x11_options);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering}
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poetteringstatic int status_read_property(const char *name, sd_bus_message *property, StatusInfo *i) {
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering char type;
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering const char *contents;
56f64d95763a799ba4475daf44d8e9f72a1bd474Michal Schmidt int r;
fb4729006a7174472e8a435b0887e532cd6217fcZbigniew Jędrzejewski-Szmek
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering assert(name);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering assert(property);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering
56f64d95763a799ba4475daf44d8e9f72a1bd474Michal Schmidt r = sd_bus_message_peek_type(property, &type, &contents);
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering if (r < 0) {
03e334a1c7dc8c20c38902aa039440763acc9b17Lennart Poettering log_error("Could not determine type of message: %s", strerror(-r));
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering return r;
}
switch (type) {
case SD_BUS_TYPE_STRING: {
const char *s;
sd_bus_message_read_basic(property, type, &s);
if (isempty(s))
break;
if (streq(name, "VConsoleKeymap"))
i->vconsole_keymap = s;
else if (streq(name, "VConsoleKeymapToggle"))
i->vconsole_keymap_toggle = s;
else if (streq(name, "X11Layout"))
i->x11_layout = s;
else if (streq(name, "X11Model"))
i->x11_model = s;
else if (streq(name, "X11Variant"))
i->x11_variant = s;
else if (streq(name, "X11Options"))
i->x11_options = s;
break;
}
case SD_BUS_TYPE_ARRAY: {
_cleanup_strv_free_ char **l = NULL;
if (!streq(contents, "s"))
break;
if (!streq(name, "Locale"))
break;
r = bus_message_read_strv_extend(property, &l);
if (r < 0)
break;
strv_free(i->locale);
i->locale = l;
l = NULL;
break;
}
}
return r;
}
static int show_status(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
StatusInfo info = {};
assert(args);
r = sd_bus_call_method( bus,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.DBus.Properties",
"GetAll",
&error,
&reply,
"s", "");
if (r < 0) {
log_error("Could not get properties: %s", bus_error_message(&error, -r));
return r;
}
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
goto fail;
while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
const char *name;
const char *contents;
r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name);
if (r < 0)
goto fail;
r = sd_bus_message_peek_type(reply, NULL, &contents);
if (r < 0)
goto fail;
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents);
if (r < 0)
goto fail;
r = status_read_property(name, reply, &info);
if (r < 0) {
log_error("Failed to parse reply.");
return r;
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
goto fail;
r = sd_bus_message_exit_container(reply);
if (r < 0)
goto fail;
}
print_status_info(&info);
fail:
strv_free(info.locale);
return r;
}
static int set_locale(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(bus);
assert(args);
polkit_agent_open_if_enabled();
r = sd_bus_message_new_method_call(bus,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
"SetLocale", &m);
if (r < 0)
return r;
r = sd_bus_message_append_strv(m, args + 1);
if (r < 0)
return r;
r = sd_bus_message_append(m, "b", arg_ask_password);
if (r < 0)
return r;
r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, NULL);
if (r < 0) {
log_error("Failed to issue method call: %s", strerror(-r));
return r;
}
return 0;
}
static int add_locales_from_archive(Set *locales) {
/* Stolen from glibc... */
struct locarhead {
uint32_t magic;
/* Serial number. */
uint32_t serial;
/* Name hash table. */
uint32_t namehash_offset;
uint32_t namehash_used;
uint32_t namehash_size;
/* String table. */
uint32_t string_offset;
uint32_t string_used;
uint32_t string_size;
/* Table with locale records. */
uint32_t locrectab_offset;
uint32_t locrectab_used;
uint32_t locrectab_size;
/* MD5 sum hash table. */
uint32_t sumhash_offset;
uint32_t sumhash_used;
uint32_t sumhash_size;
};
struct namehashent {
/* Hash value of the name. */
uint32_t hashval;
/* Offset of the name in the string table. */
uint32_t name_offset;
/* Offset of the locale record. */
uint32_t locrec_offset;
};
const struct locarhead *h;
const struct namehashent *e;
const void *p = MAP_FAILED;
_cleanup_close_ int fd = -1;
size_t sz = 0;
struct stat st;
unsigned i;
int r;
fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0) {
if (errno != ENOENT)
log_error("Failed to open locale archive: %m");
r = -errno;
goto finish;
}
if (fstat(fd, &st) < 0) {
log_error("fstat() failed: %m");
r = -errno;
goto finish;
}
if (!S_ISREG(st.st_mode)) {
log_error("Archive file is not regular");
r = -EBADMSG;
goto finish;
}
if (st.st_size < (off_t) sizeof(struct locarhead)) {
log_error("Archive has invalid size");
r = -EBADMSG;
goto finish;
}
p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
log_error("Failed to map archive: %m");
r = -errno;
goto finish;
}
h = (const struct locarhead *) p;
if (h->magic != 0xde020109 ||
h->namehash_offset + h->namehash_size > st.st_size ||
h->string_offset + h->string_size > st.st_size ||
h->locrectab_offset + h->locrectab_size > st.st_size ||
h->sumhash_offset + h->sumhash_size > st.st_size) {
log_error("Invalid archive file.");
r = -EBADMSG;
goto finish;
}
e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
for (i = 0; i < h->namehash_size; i++) {
char *z;
if (e[i].locrec_offset == 0)
continue;
if (!utf8_is_valid((char*) p + e[i].name_offset))
continue;
z = strdup((char*) p + e[i].name_offset);
if (!z) {
r = log_oom();
goto finish;
}
r = set_consume(locales, z);
if (r < 0) {
log_error("Failed to add locale: %s", strerror(-r));
goto finish;
}
}
r = 0;
finish:
if (p != MAP_FAILED)
munmap((void*) p, sz);
return r;
}
static int add_locales_from_libdir (Set *locales) {
_cleanup_closedir_ DIR *dir;
struct dirent *entry;
int r;
dir = opendir("/usr/lib/locale");
if (!dir) {
log_error("Failed to open locale directory: %m");
return -errno;
}
errno = 0;
while ((entry = readdir(dir))) {
char *z;
if (entry->d_type != DT_DIR)
continue;
if (ignore_file(entry->d_name))
continue;
z = strdup(entry->d_name);
if (!z)
return log_oom();
r = set_consume(locales, z);
if (r < 0 && r != -EEXIST) {
log_error("Failed to add locale: %s", strerror(-r));
return r;
}
errno = 0;
}
if (errno > 0) {
log_error("Failed to read locale directory: %m");
return -errno;
}
return 0;
}
static int list_locales(sd_bus *bus, char **args, unsigned n) {
_cleanup_set_free_ Set *locales;
_cleanup_strv_free_ char **l = NULL;
int r;
locales = set_new(string_hash_func, string_compare_func);
if (!locales)
return log_oom();
r = add_locales_from_archive(locales);
if (r < 0 && r != -ENOENT)
return r;
r = add_locales_from_libdir(locales);
if (r < 0)
return r;
l = set_get_strv(locales);
if (!l)
return log_oom();
strv_sort(l);
pager_open_if_enabled();
strv_print(l);
return 0;
}
static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
const char *map, *toggle_map;
assert(bus);
assert(args);
if (n > 3) {
log_error("Too many arguments.");
return -EINVAL;
}
polkit_agent_open_if_enabled();
map = args[1];
toggle_map = n > 2 ? args[2] : "";
return sd_bus_call_method(bus,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
"SetVConsoleKeyboard",
&error,
NULL,
"ssbb", map, toggle_map, arg_convert, arg_ask_password);
}
static Set *keymaps = NULL;
static int nftw_cb(
const char *fpath,
const struct stat *sb,
int tflag,
struct FTW *ftwbuf) {
char *p, *e;
int r;
if (tflag != FTW_F)
return 0;
if (!endswith(fpath, ".map") &&
!endswith(fpath, ".map.gz"))
return 0;
p = strdup(path_get_file_name(fpath));
if (!p)
return log_oom();
e = endswith(p, ".map");
if (e)
*e = 0;
e = endswith(p, ".map.gz");
if (e)
*e = 0;
r = set_consume(keymaps, p);
if (r < 0 && r != -EEXIST) {
log_error("Can't add keymap: %s", strerror(-r));
return r;
}
return 0;
}
static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
_cleanup_strv_free_ char **l = NULL;
keymaps = set_new(string_hash_func, string_compare_func);
if (!keymaps)
return log_oom();
nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
l = set_get_strv(keymaps);
if (!l) {
set_free_free(keymaps);
return log_oom();
}
set_free(keymaps);
if (strv_isempty(l)) {
log_error("Couldn't find any console keymaps.");
return -ENOENT;
}
strv_sort(l);
pager_open_if_enabled();
strv_print(l);
return 0;
}
static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
const char *layout, *model, *variant, *options;
assert(bus);
assert(args);
if (n > 5) {
log_error("Too many arguments.");
return -EINVAL;
}
polkit_agent_open_if_enabled();
layout = args[1];
model = n > 2 ? args[2] : "";
variant = n > 3 ? args[3] : "";
options = n > 4 ? args[4] : "";
return sd_bus_call_method(bus,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
"SetX11Keyboard",
&error,
NULL,
"ssssbb", layout, model, variant, options,
arg_convert, arg_ask_password);
}
static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **list = NULL;
char line[LINE_MAX];
enum {
NONE,
MODELS,
LAYOUTS,
VARIANTS,
OPTIONS
} state = NONE, look_for;
int r;
if (n > 2) {
log_error("Too many arguments.");
return -EINVAL;
}
f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
if (!f) {
log_error("Failed to open keyboard mapping list. %m");
return -errno;
}
if (streq(args[0], "list-x11-keymap-models"))
look_for = MODELS;
else if (streq(args[0], "list-x11-keymap-layouts"))
look_for = LAYOUTS;
else if (streq(args[0], "list-x11-keymap-variants"))
look_for = VARIANTS;
else if (streq(args[0], "list-x11-keymap-options"))
look_for = OPTIONS;
else
assert_not_reached("Wrong parameter");
FOREACH_LINE(line, f, break) {
char *l, *w;
l = strstrip(line);
if (isempty(l))
continue;
if (l[0] == '!') {
if (startswith(l, "! model"))
state = MODELS;
else if (startswith(l, "! layout"))
state = LAYOUTS;
else if (startswith(l, "! variant"))
state = VARIANTS;
else if (startswith(l, "! option"))
state = OPTIONS;
else
state = NONE;
continue;
}
if (state != look_for)
continue;
w = l + strcspn(l, WHITESPACE);
if (n > 1) {
char *e;
if (*w == 0)
continue;
*w = 0;
w++;
w += strspn(w, WHITESPACE);
e = strchr(w, ':');
if (!e)
continue;
*e = 0;
if (!streq(w, args[1]))
continue;
} else
*w = 0;
r = strv_extend(&list, l);
if (r < 0)
return log_oom();
}
if (strv_isempty(list)) {
log_error("Couldn't find any entries.");
return -ENOENT;
}
strv_sort(list);
strv_uniq(list);
pager_open_if_enabled();
strv_print(list);
return 0;
}
static int help(void) {
printf("%s [OPTIONS...] COMMAND ...\n\n"
"Query or change system locale and keyboard settings.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-convert Don't convert keyboard mappings\n"
" --no-pager Do not pipe output into a pager\n"
" --no-ask-password Do not prompt for password\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n\n"
"Commands:\n"
" status Show current locale settings\n"
" set-locale LOCALE... Set system locale\n"
" list-locales Show known locales\n"
" set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
" list-keymaps Show known virtual console keyboard mappings\n"
" set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
" Set X11 keyboard mapping\n"
" list-x11-keymap-models Show known X11 keyboard mapping models\n"
" list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
" list-x11-keymap-variants [LAYOUT]\n"
" Show known X11 keyboard mapping variants\n"
" list-x11-keymap-options Show known X11 keyboard mapping options\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_CONVERT,
ARG_NO_ASK_PASSWORD
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "no-convert", no_argument, NULL, ARG_NO_CONVERT },
{ NULL, 0, NULL, 0 }
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hH:M:P", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(SYSTEMD_FEATURES);
return 0;
case ARG_NO_CONVERT:
arg_convert = false;
break;
case ARG_NO_PAGER:
arg_no_pager = true;
break;
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
break;
case 'M':
arg_transport = BUS_TRANSPORT_CONTAINER;
arg_host = optarg;
break;
case '?':
return -EINVAL;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
}
return 1;
}
static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
static const struct {
const char* verb;
const enum {
MORE,
LESS,
EQUAL
} argc_cmp;
const int argc;
int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
} verbs[] = {
{ "status", LESS, 1, show_status },
{ "set-locale", MORE, 2, set_locale },
{ "list-locales", EQUAL, 1, list_locales },
{ "set-keymap", MORE, 2, set_vconsole_keymap },
{ "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
{ "set-x11-keymap", MORE, 2, set_x11_keymap },
{ "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
{ "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
{ "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
{ "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
};
int left;
unsigned i;
assert(argc >= 0);
assert(argv);
left = argc - optind;
if (left <= 0)
/* Special rule: no arguments means "status" */
i = 0;
else {
if (streq(argv[optind], "help")) {
help();
return 0;
}
for (i = 0; i < ELEMENTSOF(verbs); i++)
if (streq(argv[optind], verbs[i].verb))
break;
if (i >= ELEMENTSOF(verbs)) {
log_error("Unknown operation %s", argv[optind]);
return -EINVAL;
}
}
switch (verbs[i].argc_cmp) {
case EQUAL:
if (left != verbs[i].argc) {
log_error("Invalid number of arguments.");
return -EINVAL;
}
break;
case MORE:
if (left < verbs[i].argc) {
log_error("Too few arguments.");
return -EINVAL;
}
break;
case LESS:
if (left > verbs[i].argc) {
log_error("Too many arguments.");
return -EINVAL;
}
break;
default:
assert_not_reached("Unknown comparison operator.");
}
return verbs[i].dispatch(bus, argv + optind, left);
}
int main(int argc, char*argv[]) {
int r, ret = EXIT_FAILURE;
_cleanup_bus_unref_ sd_bus *bus = NULL;
setlocale(LC_ALL, "");
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r < 0)
goto finish;
else if (r == 0) {
ret = EXIT_SUCCESS;
goto finish;
}
r = bus_open_transport(arg_transport, arg_host, false, &bus);
if (r < 0) {
log_error("Failed to create bus connection: %s", strerror(-r));
ret = EXIT_FAILURE;
goto finish;
}
r = localectl_main(bus, argc, argv);
ret = r < 0 ? EXIT_FAILURE : r;
finish:
pager_close();
return ret;
}