chiark / gitweb /
shared: make timezone and locale enumeration and validation generic
authorLennart Poettering <lennart@poettering.net>
Mon, 7 Jul 2014 09:49:48 +0000 (11:49 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 7 Jul 2014 13:25:55 +0000 (15:25 +0200)
This way we can reuse it other code thatn just localectl/localed +
timedatectl/timedated.

Makefile.am
src/locale/localectl.c
src/locale/localed.c
src/shared/locale-util.c [new file with mode: 0644]
src/shared/locale-util.h [new file with mode: 0644]
src/shared/time-util.c
src/shared/time-util.h
src/timedate/timedatectl.c
src/timedate/timedated.c

index e9be6fa..0bf803a 100644 (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 \
index 2632305..8acc212 100644 (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();
-
-        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;
index 23da149..d6ffe67 100644 (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;
 
diff --git a/src/shared/locale-util.c b/src/shared/locale-util.c
new file mode 100644 (file)
index 0000000..8d2c363
--- /dev/null
@@ -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 (file)
index 0000000..7be9af2
--- /dev/null
@@ -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);
index 8e5de77..fc79c56 100644 (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;
+}
index 34ba6c1..792cd27 100644 (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);
index a8769e4..203b5be 100644 (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;
-        }
-
-        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;
index 204031f..5d3b8c4 100644 (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),