shared: make timezone and locale enumeration and validation generic

This way we can reuse it other code thatn just localectl/localed +
timedatectl/timedated.
This commit is contained in:
Lennart Poettering 2014-07-07 11:49:48 +02:00
parent a940778fb1
commit 7568345034
9 changed files with 353 additions and 285 deletions

View File

@ -727,6 +727,8 @@ libsystemd_shared_la_SOURCES = \
src/shared/path-util.h \
src/shared/time-util.c \
src/shared/time-util.h \
src/shared/locale-util.c \
src/shared/locale-util.h \
src/shared/hashmap.c \
src/shared/hashmap.h \
src/shared/siphash24.c \

View File

@ -43,6 +43,7 @@
#include "path-util.h"
#include "utf8.h"
#include "def.h"
#include "locale-util.h"
static bool arg_no_pager = false;
static bool arg_ask_password = true;
@ -177,192 +178,19 @@ static int set_locale(sd_bus *bus, char **args, unsigned n) {
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();
assert(args);
r = add_locales_from_archive(locales);
if (r < 0 && r != -ENOENT)
r = get_locales(&l);
if (r < 0) {
log_error("Failed to read list of locales: %s", strerror(-r));
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;

View File

@ -38,6 +38,7 @@
#include "bus-error.h"
#include "bus-message.h"
#include "event-util.h"
#include "locale-util.h"
enum {
/* We don't list LC_ALL here on purpose. People should be
@ -848,7 +849,7 @@ static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_
k = strlen(names[p]);
if (startswith(*i, names[p]) &&
(*i)[k] == '=' &&
string_is_safe((*i) + k + 1)) {
locale_is_valid((*i) + k + 1)) {
valid = true;
passed[p] = true;

205
src/shared/locale-util.c Normal file
View File

@ -0,0 +1,205 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/mman.h>
#include "set.h"
#include "util.h"
#include "utf8.h"
#include "strv.h"
#include "locale-util.h"
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)
return errno == ENOENT ? 0 : -errno;
if (fstat(fd, &st) < 0)
return -errno;
if (!S_ISREG(st.st_mode))
return -EBADMSG;
if (st.st_size < (off_t) sizeof(struct locarhead))
return -EBADMSG;
p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
return -errno;
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) {
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 = -ENOMEM;
goto finish;
}
r = set_consume(locales, z);
if (r < 0)
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 = NULL;
struct dirent *entry;
int r;
dir = opendir("/usr/lib/locale");
if (!dir)
return errno == ENOENT ? 0 : -errno;
FOREACH_DIRENT(entry, dir, return -errno) {
char *z;
if (entry->d_type != DT_DIR)
continue;
z = strdup(entry->d_name);
if (!z)
return -ENOMEM;
r = set_consume(locales, z);
if (r < 0 && r != -EEXIST)
return r;
}
return 0;
}
int get_locales(char ***ret) {
_cleanup_set_free_ Set *locales = NULL;
_cleanup_strv_free_ char **l = NULL;
int r;
locales = set_new(string_hash_func, string_compare_func);
if (!locales)
return -ENOMEM;
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 -ENOMEM;
strv_sort(l);
*ret = l;
l = NULL;
return 0;
}
bool locale_is_valid(const char *name) {
if (isempty(name))
return false;
if (strlen(name) >= 128)
return false;
if (!utf8_is_valid(name))
return false;
if (!filename_is_safe(name))
return false;
if (!string_is_safe(name))
return false;
return true;
}

25
src/shared/locale-util.h Normal file
View File

@ -0,0 +1,25 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
int get_locales(char ***l);
bool locale_is_valid(const char *name);

View File

@ -25,6 +25,7 @@
#include "util.h"
#include "time-util.h"
#include "strv.h"
usec_t now(clockid_t clock_id) {
struct timespec ts;
@ -826,3 +827,105 @@ bool ntp_synced(void) {
return true;
}
int get_timezones(char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
size_t n_zones = 0, n_allocated = 0;
assert(ret);
zones = strv_new("UTC", NULL);
if (!zones)
return -ENOMEM;
n_allocated = 2;
n_zones = 1;
f = fopen("/usr/share/zoneinfo/zone.tab", "re");
if (f) {
char l[LINE_MAX];
FOREACH_LINE(l, f, return -errno) {
char *p, *w;
size_t k;
p = strstrip(l);
if (isempty(p) || *p == '#')
continue;
/* Skip over country code */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Skip over coordinates */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Found timezone name */
k = strcspn(p, WHITESPACE);
if (k <= 0)
continue;
w = strndup(p, k);
if (!w)
return -ENOMEM;
if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) {
free(w);
return -ENOMEM;
}
zones[n_zones++] = w;
zones[n_zones] = NULL;
}
strv_sort(zones);
} else if (errno != ENOENT)
return -errno;
*ret = zones;
zones = NULL;
return 0;
}
bool timezone_is_valid(const char *name) {
bool slash = false;
const char *p, *t;
struct stat st;
if (!name || *name == 0 || *name == '/')
return false;
for (p = name; *p; p++) {
if (!(*p >= '0' && *p <= '9') &&
!(*p >= 'a' && *p <= 'z') &&
!(*p >= 'A' && *p <= 'Z') &&
!(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
return false;
if (*p == '/') {
if (slash)
return false;
slash = true;
} else
slash = false;
}
if (slash)
return false;
t = strappenda("/usr/share/zoneinfo/", name);
if (stat(t, &st) < 0)
return false;
if (!S_ISREG(st.st_mode))
return false;
return true;
}

View File

@ -95,3 +95,6 @@ int parse_sec(const char *t, usec_t *usec);
int parse_nsec(const char *t, nsec_t *nsec);
bool ntp_synced(void);
int get_timezones(char ***l);
bool timezone_is_valid(const char *name);

View File

@ -355,69 +355,19 @@ static int set_ntp(sd_bus *bus, char **args, unsigned n) {
}
static int list_timezones(sd_bus *bus, char **args, unsigned n) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
size_t n_zones = 0;
int r;
assert(args);
assert(n == 1);
f = fopen("/usr/share/zoneinfo/zone.tab", "re");
if (!f) {
log_error("Failed to open time zone database: %m");
return -errno;
r = get_timezones(&zones);
if (r < 0) {
log_error("Failed to read list of time zones: %s", strerror(-r));
return r;
}
for (;;) {
char l[LINE_MAX], *p, **z, *w;
size_t k;
if (!fgets(l, sizeof(l), f)) {
if (feof(f))
break;
log_error("Failed to read time zone database: %m");
return -errno;
}
p = strstrip(l);
if (isempty(p) || *p == '#')
continue;
/* Skip over country code */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Skip over coordinates */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Found timezone name */
k = strcspn(p, WHITESPACE);
if (k <= 0)
continue;
w = strndup(p, k);
if (!w)
return log_oom();
z = realloc(zones, sizeof(char*) * (n_zones + 2));
if (!z) {
free(w);
return log_oom();
}
zones = z;
zones[n_zones++] = w;
}
if (zones)
zones[n_zones] = NULL;
pager_open_if_enabled();
strv_sort(zones);
strv_print(zones);
return 0;

View File

@ -22,6 +22,7 @@
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/capability.h>
#include "sd-id128.h"
#include "sd-messages.h"
@ -58,54 +59,6 @@ static void context_free(Context *c, sd_bus *bus) {
bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
}
static bool valid_timezone(const char *name) {
const char *p;
char *t;
bool slash = false;
int r;
struct stat st;
assert(name);
if (*name == '/' || *name == 0)
return false;
for (p = name; *p; p++) {
if (!(*p >= '0' && *p <= '9') &&
!(*p >= 'a' && *p <= 'z') &&
!(*p >= 'A' && *p <= 'Z') &&
!(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
return false;
if (*p == '/') {
if (slash)
return false;
slash = true;
} else
slash = false;
}
if (slash)
return false;
t = strappend("/usr/share/zoneinfo/", name);
if (!t)
return false;
r = stat(t, &st);
free(t);
if (r < 0)
return false;
if (!S_ISREG(st.st_mode))
return false;
return true;
}
static int context_read_data(Context *c) {
_cleanup_free_ char *t = NULL;
int r;
@ -502,7 +455,7 @@ static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, s
if (r < 0)
return r;
if (!valid_timezone(z))
if (!timezone_is_valid(z))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
if (streq_ptr(z, c->zone))
@ -737,8 +690,6 @@ static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus
return sd_bus_reply_method_return(m, NULL);
}
#include <sys/capability.h>
static const sd_bus_vtable timedate_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),