summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2014-07-07 11:49:48 +0200
committerLennart Poettering <lennart@poettering.net>2014-07-07 15:25:55 +0200
commit7568345034f2890af745747783c5abfbf6eccf0f (patch)
tree711cb1a32730435553d00fe2b1fcc222d5bcc803
parenta940778fb1dd16479f455bab3ac6cbdbc5b06165 (diff)
shared: make timezone and locale enumeration and validation generic
This way we can reuse it other code thatn just localectl/localed + timedatectl/timedated.
-rw-r--r--Makefile.am2
-rw-r--r--src/locale/localectl.c184
-rw-r--r--src/locale/localed.c3
-rw-r--r--src/shared/locale-util.c205
-rw-r--r--src/shared/locale-util.h25
-rw-r--r--src/shared/time-util.c103
-rw-r--r--src/shared/time-util.h3
-rw-r--r--src/timedate/timedatectl.c60
-rw-r--r--src/timedate/timedated.c53
9 files changed, 353 insertions, 285 deletions
diff --git a/Makefile.am b/Makefile.am
index e9be6facc6..0bf803a1f3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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 \
diff --git a/src/locale/localectl.c b/src/locale/localectl.c
index 2632305dcc..8acc212033 100644
--- a/src/locale/localectl.c
+++ b/src/locale/localectl.c
@@ -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();
-
- r = add_locales_from_archive(locales);
- if (r < 0 && r != -ENOENT)
- return r;
+ assert(args);
- r = add_locales_from_libdir(locales);
- if (r < 0)
+ r = get_locales(&l);
+ if (r < 0) {
+ log_error("Failed to read list of locales: %s", strerror(-r));
return r;
-
- l = set_get_strv(locales);
- if (!l)
- return log_oom();
-
- strv_sort(l);
+ }
pager_open_if_enabled();
-
strv_print(l);
return 0;
diff --git a/src/locale/localed.c b/src/locale/localed.c
index 23da149b0e..d6ffe67520 100644
--- a/src/locale/localed.c
+++ b/src/locale/localed.c
@@ -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;
diff --git a/src/shared/locale-util.c b/src/shared/locale-util.c
new file mode 100644
index 0000000000..8d2c363036
--- /dev/null
+++ b/src/shared/locale-util.c
@@ -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;
+}
diff --git a/src/shared/locale-util.h b/src/shared/locale-util.h
new file mode 100644
index 0000000000..7be9af2b4e
--- /dev/null
+++ b/src/shared/locale-util.h
@@ -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);
diff --git a/src/shared/time-util.c b/src/shared/time-util.c
index 8e5de77757..fc79c569f4 100644
--- a/src/shared/time-util.c
+++ b/src/shared/time-util.c
@@ -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;
+}
diff --git a/src/shared/time-util.h b/src/shared/time-util.h
index 34ba6c11be..792cd27489 100644
--- a/src/shared/time-util.h
+++ b/src/shared/time-util.h
@@ -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);
diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c
index a8769e4180..203b5be6dd 100644
--- a/src/timedate/timedatectl.c
+++ b/src/timedate/timedatectl.c
@@ -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;
- }
-
- 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;
+ r = get_timezones(&zones);
+ if (r < 0) {
+ log_error("Failed to read list of time zones: %s", strerror(-r));
+ return r;
}
- if (zones)
- zones[n_zones] = NULL;
-
pager_open_if_enabled();
-
- strv_sort(zones);
strv_print(zones);
return 0;
diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c
index 204031fe77..5d3b8c41e6 100644
--- a/src/timedate/timedated.c
+++ b/src/timedate/timedated.c
@@ -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),