[systemd-commits] 10 commits - .gitignore Makefile.am TODO configure.ac man/systemd.unit.xml src/core src/firstboot src/hostname src/locale src/shared src/sleep src/timedate units/.gitignore units/systemd-firstboot.service.in

Lennart Poettering lennart at kemper.freedesktop.org
Mon Jul 7 06:26:33 PDT 2014


 .gitignore                          |    2 
 Makefile.am                         |   36 +
 TODO                                |    5 
 configure.ac                        |    9 
 man/systemd.unit.xml                |    9 
 src/core/execute.c                  |    2 
 src/core/main.c                     |    9 
 src/core/manager.c                  |   12 
 src/core/manager.h                  |    1 
 src/core/shutdown.c                 |    2 
 src/firstboot/Makefile              |    1 
 src/firstboot/firstboot-generator.c |   71 ++
 src/firstboot/firstboot.c           |  930 ++++++++++++++++++++++++++++++++++++
 src/hostname/hostnamed.c            |    3 
 src/locale/localectl.c              |  184 -------
 src/locale/localed.c                |    3 
 src/shared/architecture.c           |    2 
 src/shared/env-util.c               |    4 
 src/shared/fileio.c                 |   26 -
 src/shared/locale-util.c            |  205 +++++++
 src/shared/locale-util.h            |   25 
 src/shared/time-util.c              |  103 +++
 src/shared/time-util.h              |    3 
 src/shared/util.c                   |   83 ++-
 src/shared/util.h                   |    7 
 src/sleep/sleep.c                   |    4 
 src/timedate/timedatectl.c          |   60 --
 src/timedate/timedated.c            |   53 --
 units/.gitignore                    |    1 
 units/systemd-firstboot.service.in  |   23 
 30 files changed, 1552 insertions(+), 326 deletions(-)

New commits:
commit 418b9be50018303cde79b423d4701b7fd86ddbdc
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 15:05:37 2014 +0200

    firstboot: add new component to query basic system settings on first boot, or when creating OS images offline
    
    A new tool "systemd-firstboot" can be used either interactively on boot,
    where it will query basic locale, timezone, hostname, root password
    information and set it. Or it can be used non-interactively from the
    command line when prepareing disk images for booting. When used
    non-inertactively the tool can either copy settings from the host, or
    take settings on the command line.
    
    $ systemd-firstboot --root=/path/to/my/new/root --copy-locale --copy-root-password --hostname=waldi
    
    The tool will be automatically invoked (interactively) now on first boot
    if /etc is found unpopulated.
    
    This also creates the infrastructure for generators to be notified via
    an environment variable whether they are running on the first boot, or
    not.

diff --git a/.gitignore b/.gitignore
index 822f5a6..1ba3d06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,6 +64,8 @@
 /systemd-delta
 /systemd-detect-virt
 /systemd-efi-boot-generator
+/systemd-firstboot
+/systemd-firstboot-generator
 /systemd-fsck
 /systemd-fstab-generator
 /systemd-getty-generator
diff --git a/Makefile.am b/Makefile.am
index 0bf803a..321379c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1873,6 +1873,40 @@ INSTALL_DIRS += \
 endif
 
 # ------------------------------------------------------------------------------
+if ENABLE_FIRSTBOOT
+systemd_firstboot_SOURCES = \
+	src/firstboot/firstboot.c
+
+systemd_firstboot_LDADD = \
+	libsystemd-units.la \
+	libsystemd-label.la \
+	libsystemd-capability.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la \
+	-lcrypt
+
+rootbin_PROGRAMS += \
+	systemd-firstboot
+
+nodist_systemunit_DATA += \
+	units/systemd-firstboot.service
+
+EXTRA_DIST += \
+	units/systemd-firstboot.service.in
+
+systemgenerator_PROGRAMS += \
+	systemd-firstboot-generator
+
+systemd_firstboot_generator_SOURCES = \
+	src/firstboot/firstboot-generator.c
+
+systemd_firstboot_generator_LDADD = \
+	libsystemd-label.la \
+	libsystemd-shared.la
+
+endif
+
+# ------------------------------------------------------------------------------
 systemd_machine_id_setup_SOURCES = \
 	src/machine-id-setup/machine-id-setup-main.c \
 	src/core/machine-id-setup.c \
diff --git a/configure.ac b/configure.ac
index 2ee73ca..19af217 100644
--- a/configure.ac
+++ b/configure.ac
@@ -842,6 +842,14 @@ fi
 AM_CONDITIONAL(ENABLE_SYSUSERS, [test "$have_sysusers" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_firstboot=no
+AC_ARG_ENABLE(firstboot, AS_HELP_STRING([--disable-firstboot], [disable firstboot support]))
+if test "x$enable_firstboot" != "xno"; then
+        have_firstboot=yes
+fi
+AM_CONDITIONAL(ENABLE_FIRSTBOOT, [test "$have_firstboot" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_randomseed=no
 AC_ARG_ENABLE(randomseed, AS_HELP_STRING([--disable-randomseed], [disable randomseed tools]))
 if test "x$enable_randomseed" != "xno"; then
@@ -1291,6 +1299,7 @@ AC_MSG_RESULT([
         quotacheck:              ${have_quotacheck}
         tmpfiles:                ${have_tmpfiles}
         sysusers:                ${have_sysusers}
+        firstboot:               ${have_firstboot}
         randomseed:              ${have_randomseed}
         backlight:               ${have_backlight}
         rfkill:                  ${have_rfkill}
diff --git a/src/core/execute.c b/src/core/execute.c
index 88d094e..6e76bd5 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -571,7 +571,7 @@ static int ask_for_confirmation(char *response, char **argv) {
         if (!line)
                 return -ENOMEM;
 
-        r = ask(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+        r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
 
         restore_confirm_stdio(&saved_stdin, &saved_stdout);
 
diff --git a/src/core/main.c b/src/core/main.c
index a732c69..e1fc3f3 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1648,6 +1648,7 @@ int main(int argc, char *argv[]) {
         m->initrd_timestamp = initrd_timestamp;
         m->security_start_timestamp = security_start_timestamp;
         m->security_finish_timestamp = security_finish_timestamp;
+        m->is_first_boot = empty_etc;
 
         manager_set_default_rlimits(m, arg_default_rlimit);
         manager_environment_add(m, NULL, arg_default_environment);
diff --git a/src/core/manager.c b/src/core/manager.c
index 0cb2044..9d078c0 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -2476,6 +2476,9 @@ void manager_check_finished(Manager *m) {
         /* Turn off confirm spawn now */
         m->confirm_spawn = false;
 
+        /* This is no longer the first boot */
+        m->is_first_boot = false;
+
         if (dual_timestamp_is_set(&m->finish_timestamp))
                 return;
 
@@ -2628,6 +2631,7 @@ void manager_run_generators(Manager *m) {
         _cleanup_closedir_ DIR *d = NULL;
         const char *generator_path;
         const char *argv[5];
+        const char *env[2];
         int r;
 
         assert(m);
@@ -2661,8 +2665,14 @@ void manager_run_generators(Manager *m) {
         argv[3] = m->generator_unit_path_late;
         argv[4] = NULL;
 
+        if (m->is_first_boot) {
+                env[0] = (char*) "SYSTEMD_FIRST_BOOT=1";
+                env[1] = NULL;
+        } else
+                env[0] = NULL;
+
         RUN_WITH_UMASK(0022)
-                execute_directory(generator_path, d, DEFAULT_TIMEOUT_USEC, (char**) argv);
+                execute_directory(generator_path, d, DEFAULT_TIMEOUT_USEC, (char**) argv, (char**) env);
 
 finish:
         trim_generator_dir(m, &m->generator_unit_path);
diff --git a/src/core/manager.h b/src/core/manager.h
index f2c1b0d..eff639d 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -243,6 +243,7 @@ struct Manager {
         bool default_cpu_accounting;
         bool default_memory_accounting;
         bool default_blockio_accounting;
+        bool is_first_boot;
 
         usec_t default_timer_accuracy_usec;
 
diff --git a/src/core/shutdown.c b/src/core/shutdown.c
index fde3ce9..e7771c9 100644
--- a/src/core/shutdown.c
+++ b/src/core/shutdown.c
@@ -375,7 +375,7 @@ int main(int argc, char *argv[]) {
         arguments[0] = NULL;
         arguments[1] = arg_verb;
         arguments[2] = NULL;
-        execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments);
+        execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments, NULL);
 
         if (!in_container && !in_initrd() &&
             access("/run/initramfs/shutdown", X_OK) == 0) {
diff --git a/src/firstboot/Makefile b/src/firstboot/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/firstboot/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/firstboot/firstboot-generator.c b/src/firstboot/firstboot-generator.c
new file mode 100644
index 0000000..6d23f40
--- /dev/null
+++ b/src/firstboot/firstboot-generator.c
@@ -0,0 +1,71 @@
+/*-*- 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 "util.h"
+#include "mkdir.h"
+
+static const char *arg_dest = "/tmp";
+
+static bool is_first_boot(void) {
+        const char *e;
+
+        e = getenv("SYSTEMD_FIRST_BOOT");
+        if (!e)
+                return false;
+
+        return parse_boolean(e) > 0;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+
+        if (argc > 1 && argc != 4) {
+                log_error("This program takes three or no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc > 1)
+                arg_dest = argv[2];
+
+        log_set_target(LOG_TARGET_SAFE);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (is_first_boot()) {
+                const char *t;
+
+                t = strappenda(arg_dest, "/default.target.wants/systemd-firstboot.service");
+
+                mkdir_parents(t, 0755);
+                if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-firstboot.service", t) < 0 && errno != EEXIST) {
+                        log_error("Failed to create firstboot service symlinks %s: %m", t);
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = 0;
+
+finish:
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
new file mode 100644
index 0000000..56893d0
--- /dev/null
+++ b/src/firstboot/firstboot.c
@@ -0,0 +1,930 @@
+/*-*- 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 <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <shadow.h>
+
+#include "strv.h"
+#include "fileio.h"
+#include "copy.h"
+#include "build.h"
+#include "mkdir.h"
+#include "time-util.h"
+#include "path-util.h"
+#include "locale-util.h"
+#include "ask-password-api.h"
+
+static char *arg_root = NULL;
+static char *arg_locale = NULL;  /* $LANG */
+static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
+static char *arg_timezone = NULL;
+static char *arg_hostname = NULL;
+static sd_id128_t arg_machine_id = {};
+static char *arg_root_password = NULL;
+static bool arg_prompt_locale = false;
+static bool arg_prompt_timezone = false;
+static bool arg_prompt_hostname = false;
+static bool arg_prompt_root_password = false;
+static bool arg_copy_locale = false;
+static bool arg_copy_timezone = false;
+static bool arg_copy_root_password = false;
+
+#define prefix_roota(p) (arg_root ? (const char*) strappenda(arg_root, p) : (const char*) p)
+
+static void clear_string(char *x) {
+
+        if (!x)
+                return;
+
+        /* A delicious drop of snake-oil! */
+        memset(x, 'x', strlen(x));
+}
+
+static bool press_any_key(void) {
+        char k = 0;
+        bool need_nl = true;
+
+        printf("-- Press any key to proceed --");
+        fflush(stdout);
+
+        read_one_char(stdin, &k, (usec_t) -1, &need_nl);
+
+        if (need_nl)
+                putchar('\n');
+
+        return k != 'q';
+}
+
+static void print_welcome(void) {
+        _cleanup_free_ char *pretty_name = NULL;
+        const char *os_release = NULL;
+        static bool done = false;
+        int r;
+
+        if (done)
+                return;
+
+        os_release = prefix_roota("/etc/os-release");
+        r = parse_env_file(os_release, NEWLINE,
+                           "PRETTY_NAME", &pretty_name,
+                           NULL);
+        if (r == -ENOENT) {
+
+                os_release = prefix_roota("/usr/lib/os-release");
+                r = parse_env_file(os_release, NEWLINE,
+                                   "PRETTY_NAME", &pretty_name,
+                                   NULL);
+        }
+
+        if (r < 0 && r != -ENOENT)
+                log_warning("Failed to read os-release file: %s", strerror(-r));
+
+        printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
+               isempty(pretty_name) ? "Linux" : pretty_name);
+
+        press_any_key();
+
+        done = true;
+}
+
+static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
+        unsigned n, per_column, i, j;
+        unsigned break_lines, break_modulo;
+
+        assert(n_columns > 0);
+
+        n = strv_length(x);
+        per_column = (n + n_columns - 1) / n_columns;
+
+        break_lines = lines();
+        if (break_lines > 2)
+                break_lines--;
+
+        /* The first page gets two extra lines, since we want to show
+         * a title */
+        break_modulo = break_lines;
+        if (break_modulo > 3)
+                break_modulo -= 3;
+
+        for (i = 0; i < per_column; i++) {
+
+                for (j = 0; j < n_columns; j ++) {
+                        _cleanup_free_ char *e = NULL;
+
+                        if (j * per_column + i >= n)
+                                break;
+
+                        e = ellipsize(x[j * per_column + i], width, percentage);
+                        if (!e)
+                                return log_oom();
+
+                        printf("%4u) %-*s", j * per_column + i + 1, width, e);
+                }
+
+                putchar('\n');
+
+                /* on the first screen we reserve 2 extra lines for the title */
+                if (i % break_lines == break_modulo) {
+                        if (!press_any_key())
+                                return 0;
+                }
+        }
+
+        return 0;
+}
+
+static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
+        int r;
+
+        assert(text);
+        assert(is_valid);
+        assert(ret);
+
+        for (;;) {
+                _cleanup_free_ char *p = NULL;
+                unsigned u;
+
+                r = ask_string(&p, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET), text);
+                if (r < 0) {
+                        log_error("Failed to query user: %s", strerror(-r));
+                        return r;
+                }
+
+                if (isempty(p)) {
+                        log_warning("No data entered, skipping.");
+                        return 0;
+                }
+
+                r = safe_atou(p, &u);
+                if (r >= 0) {
+                        char *c;
+
+                        if (u <= 0 || u > strv_length(l)) {
+                                log_error("Specified entry number out of range.");
+                                continue;
+                        }
+
+                        log_info("Selected '%s'.", l[u-1]);
+
+                        c = strdup(l[u-1]);
+                        if (!c)
+                                return log_oom();
+
+                        free(*ret);
+                        *ret = c;
+                        return 0;
+                }
+
+                if (!is_valid(p)) {
+                        log_error("Entered data invalid.");
+                        continue;
+                }
+
+                free(*ret);
+                *ret = p;
+                p = 0;
+                return 0;
+        }
+}
+
+static int prompt_locale(void) {
+        _cleanup_strv_free_ char **locales = NULL;
+        int r;
+
+        if (arg_locale || arg_locale_messages)
+                return 0;
+
+        if (!arg_prompt_locale)
+                return 0;
+
+        r = get_locales(&locales);
+        if (r < 0) {
+                log_error("Cannot query locales list: %s", strerror(-r));
+                return r;
+        }
+
+        print_welcome();
+
+        printf("\nAvailable Locales:\n\n");
+        r = show_menu(locales, 3, 22, 60);
+        if (r < 0)
+                return r;
+
+        putchar('\n');
+
+        r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
+        if (r < 0)
+                return r;
+
+        if (isempty(arg_locale))
+                return 0;
+
+        r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int process_locale(void) {
+        const char *etc_localeconf;
+        char* locales[3];
+        unsigned i = 0;
+        int r;
+
+        etc_localeconf = prefix_roota("/etc/locale.conf");
+        if (faccessat(AT_FDCWD, etc_localeconf, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        if (arg_copy_locale && arg_root) {
+
+                mkdir_parents(etc_localeconf, 0755);
+                r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644);
+                if (r != -ENOENT) {
+                        if (r < 0) {
+                                log_error("Failed to copy %s: %s", etc_localeconf, strerror(-r));
+                                return r;
+                        }
+
+                        log_info("%s copied.", etc_localeconf);
+                        return 0;
+                }
+        }
+
+        r = prompt_locale();
+        if (r < 0)
+                return r;
+
+        if (!isempty(arg_locale))
+                locales[i++] = strappenda("LANG=", arg_locale);
+        if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
+                locales[i++] = strappenda("LC_MESSAGES=", arg_locale_messages);
+
+        if (i == 0)
+                return 0;
+
+        locales[i] = NULL;
+
+        mkdir_parents(etc_localeconf, 0755);
+        r = write_env_file(etc_localeconf, locales);
+        if (r < 0) {
+                log_error("Failed to write %s: %s", etc_localeconf, strerror(-r));
+                return r;
+        }
+
+        log_info("%s written.", etc_localeconf);
+        return 0;
+}
+
+static int prompt_timezone(void) {
+        _cleanup_strv_free_ char **zones = NULL;
+        int r;
+
+        if (arg_timezone)
+                return 0;
+
+        if (!arg_prompt_timezone)
+                return 0;
+
+        r = get_timezones(&zones);
+        if (r < 0) {
+                log_error("Cannot query timezone list: %s", strerror(-r));
+                return r;
+        }
+
+        print_welcome();
+
+        printf("\nAvailable Time Zones:\n\n");
+        r = show_menu(zones, 3, 22, 30);
+        if (r < 0)
+                return r;
+
+        putchar('\n');
+
+        r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int process_timezone(void) {
+        const char *etc_localtime, *e;
+        int r;
+
+        etc_localtime = prefix_roota("/etc/localtime");
+        if (faccessat(AT_FDCWD, etc_localtime, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        if (arg_copy_timezone && arg_root) {
+                _cleanup_free_ char *p = NULL;
+
+                r = readlink_malloc("/etc/localtime", &p);
+                if (r != -ENOENT) {
+                        if (r < 0) {
+                                log_error("Failed to read host timezone: %s", strerror(-r));
+                                return r;
+                        }
+
+                        mkdir_parents(etc_localtime, 0755);
+                        if (symlink(p, etc_localtime) < 0) {
+                                log_error("Failed to create %s symlink: %m", etc_localtime);
+                                return -errno;
+                        }
+
+                        log_info("%s copied.", etc_localtime);
+                        return 0;
+                }
+        }
+
+        r = prompt_timezone();
+        if (r < 0)
+                return r;
+
+        if (isempty(arg_timezone))
+                return 0;
+
+        e = strappenda("../usr/share/zoneinfo/", arg_timezone);
+
+        mkdir_parents(etc_localtime, 0755);
+        if (symlink(e, etc_localtime) < 0) {
+                log_error("Failed to create %s symlink: %m", etc_localtime);
+                return -errno;
+        }
+
+        log_info("%s written", etc_localtime);
+        return 0;
+}
+
+static int prompt_hostname(void) {
+        int r;
+
+        if (arg_hostname)
+                return 0;
+
+        if (!arg_prompt_hostname)
+                return 0;
+
+        print_welcome();
+        putchar('\n');
+
+        for (;;) {
+                _cleanup_free_ char *h = NULL;
+
+                r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET));
+                if (r < 0) {
+                        log_error("Failed to query hostname: %s", strerror(-r));
+                        return r;
+                }
+
+                if (isempty(h)) {
+                        log_warning("No hostname entered, skipping.");
+                        break;
+                }
+
+                if (!hostname_is_valid(h)) {
+                        log_error("Specified hostname invalid.");
+                        continue;
+                }
+
+                arg_hostname = h;
+                h = NULL;
+                break;
+        }
+
+        return 0;
+}
+
+static int process_hostname(void) {
+        const char *etc_hostname;
+        int r;
+
+        etc_hostname = prefix_roota("/etc/hostname");
+        if (faccessat(AT_FDCWD, etc_hostname, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        r = prompt_hostname();
+        if (r < 0)
+                return r;
+
+        if (isempty(arg_hostname))
+                return 0;
+
+        mkdir_parents(etc_hostname, 0755);
+        r = write_string_file(etc_hostname, arg_hostname);
+        if (r < 0) {
+                log_error("Failed to write %s: %s", etc_hostname, strerror(-r));
+                return r;
+        }
+
+        log_info("%s written.", etc_hostname);
+        return 0;
+}
+
+static int process_machine_id(void) {
+        const char *etc_machine_id;
+        char id[SD_ID128_STRING_MAX];
+        int r;
+
+        etc_machine_id = prefix_roota("/etc/machine-id");
+        if (faccessat(AT_FDCWD, etc_machine_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        if (!arg_root)
+                return 0;
+
+        if (sd_id128_equal(arg_machine_id, SD_ID128_NULL))
+                return 0;
+
+        mkdir_parents(etc_machine_id, 0755);
+        r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id));
+        if (r < 0) {
+                log_error("Failed to write machine id: %s", strerror(-r));
+                return r;
+        }
+
+        log_info("%s written.", etc_machine_id);
+        return 0;
+}
+
+static int prompt_root_password(void) {
+        const char *msg1, *msg2, *etc_shadow;
+        int r;
+
+        if (arg_root_password)
+                return 0;
+
+        if (!arg_prompt_root_password)
+                return 0;
+
+        etc_shadow = prefix_roota("/etc/shadow");
+        if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        print_welcome();
+        putchar('\n');
+
+        msg1 = strappenda(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
+        msg2 = strappenda(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter new root password again: ");
+
+        for (;;) {
+                _cleanup_free_ char *a = NULL, *b = NULL;
+
+                r = ask_password_tty(msg1, 0, NULL, &a);
+                if (r < 0) {
+                        log_error("Failed to query root password: %s", strerror(-r));
+                        return r;
+                }
+
+                if (isempty(a)) {
+                        log_warning("No password entered, skipping.");
+                        break;
+                }
+
+                r = ask_password_tty(msg2, 0, NULL, &b);
+                if (r < 0) {
+                        log_error("Failed to query root password: %s", strerror(-r));
+                        clear_string(a);
+                        return r;
+                }
+
+                if (!streq(a, b)) {
+                        log_error("Entered passwords did not match, please try again.");
+                        clear_string(a);
+                        clear_string(b);
+                        continue;
+                }
+
+                clear_string(b);
+                arg_root_password = a;
+                a = NULL;
+                break;
+        }
+
+        return 0;
+}
+
+static int write_root_shadow(const char *path, const struct spwd *p) {
+        _cleanup_fclose_ FILE *f = NULL;
+        assert(path);
+        assert(p);
+
+        mkdir_parents(path, 0755);
+        f = fopen(path, "wex");
+        if (!f)
+                return -errno;
+
+        errno = 0;
+        if (putspent(p, f) != 0)
+                return errno ? -errno : -EIO;
+
+        return fflush_and_check(f);
+}
+
+static int process_root_password(void) {
+
+        static const char table[] =
+                "abcdefghijklmnopqrstuvwxyz"
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                "0123456789"
+                "./";
+
+        struct spwd item = {
+                .sp_namp = (char*) "root",
+                .sp_min = 0,
+                .sp_max = 99999,
+                .sp_warn = 7,
+                .sp_inact = -1,
+                .sp_expire = -1,
+                .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
+        };
+        char salt[3+16+1+1];
+        uint8_t raw[16];
+        unsigned i;
+        char *j;
+
+        const char *etc_shadow;
+        int r;
+
+        etc_shadow = prefix_roota("/etc/shadow");
+        if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        if (arg_copy_root_password && arg_root) {
+                struct spwd *p;
+
+                errno = 0;
+                p = getspnam("root");
+                if (p || errno != ENOENT) {
+                        if (!p) {
+                                if (!errno)
+                                        errno = EIO;
+
+                                log_error("Failed to find shadow entry for root: %m");
+                                return -errno;
+                        }
+
+                        r = write_root_shadow(etc_shadow, p);
+                        if (r < 0) {
+                                log_error("Failed to write %s: %s", etc_shadow, strerror(-r));
+                                return r;
+                        }
+
+                        log_info("%s copied.", etc_shadow);
+                        return 0;
+                }
+        }
+
+        r = prompt_root_password();
+        if (r < 0)
+                return r;
+
+        if (!arg_root_password)
+                return 0;
+
+        r = dev_urandom(raw, 16);
+        if (r < 0) {
+                log_error("Failed to get salt: %s", strerror(-r));
+                return r;
+        }
+
+        /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
+        assert_cc(sizeof(table) == 64 + 1);
+        j = stpcpy(salt, "$6$");
+        for (i = 0; i < 16; i++)
+                j[i] = table[raw[i] & 63];
+        j[i++] = '$';
+        j[i] = 0;
+
+        errno = 0;
+        item.sp_pwdp = crypt(arg_root_password, salt);
+        if (!item.sp_pwdp) {
+                if (!errno)
+                        errno = -EINVAL;
+
+                log_error("Failed to encrypt password: %m");
+                return -errno;
+        }
+
+        item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
+
+        r = write_root_shadow(etc_shadow, &item);
+        if (r < 0) {
+                log_error("Failed to write %s: %s", etc_shadow, strerror(-r));
+                return r;
+        }
+
+        log_info("%s written.", etc_shadow);
+        return 0;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "Configures basic settings of the system.\n\n"
+               "  -h --help                    Show this help\n"
+               "     --version                 Show package version\n"
+               "     --root=PATH               Operate on an alternate filesystem root\n"
+               "     --locale=LOCALE           Set primary locale (LANG=)\n"
+               "     --locale-messages=LOCALE  Set message locale (LC_MESSAGES=)\n"
+               "     --timezone=TIMEZONE       Set timezone\n"
+               "     --hostname=NAME           Set host name\n"
+               "     --machine-ID=ID           Set machine ID\n"
+               "     --root-password=PASSWORD  Set root password\n"
+               "     --root-password-file=FILE Set root password from file\n"
+               "     --prompt-locale           Prompt the user for locale settings\n"
+               "     --prompt-timezone         Prompt the user for timezone\n"
+               "     --prompt-hostname         Prompt the user for hostname\n"
+               "     --prompt-root-password    Prompt the user for root password\n"
+               "     --prompt                  Prompt for locale, timezone, hostname, root password\n"
+               "     --copy-locale             Copy locale from host\n"
+               "     --copy-timezone           Copy timezone from host\n"
+               "     --copy-root-password      Copy root password from host\n"
+               "     --copy                    Copy locale, timezone, root password\n"
+               "     --setup-machine-id        Generate a new random machine ID\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_ROOT,
+                ARG_LOCALE,
+                ARG_LOCALE_MESSAGES,
+                ARG_TIMEZONE,
+                ARG_HOSTNAME,
+                ARG_MACHINE_ID,
+                ARG_ROOT_PASSWORD,
+                ARG_ROOT_PASSWORD_FILE,
+                ARG_PROMPT,
+                ARG_PROMPT_LOCALE,
+                ARG_PROMPT_TIMEZONE,
+                ARG_PROMPT_HOSTNAME,
+                ARG_PROMPT_ROOT_PASSWORD,
+                ARG_COPY,
+                ARG_COPY_LOCALE,
+                ARG_COPY_TIMEZONE,
+                ARG_COPY_ROOT_PASSWORD,
+                ARG_SETUP_MACHINE_ID,
+        };
+
+        static const struct option options[] = {
+                { "help",                 no_argument,       NULL, 'h'                      },
+                { "version",              no_argument,       NULL, ARG_VERSION              },
+                { "root",                 required_argument, NULL, ARG_ROOT                 },
+                { "locale",               required_argument, NULL, ARG_LOCALE               },
+                { "locale-messages",      required_argument, NULL, ARG_LOCALE_MESSAGES      },
+                { "timezone",             required_argument, NULL, ARG_TIMEZONE             },
+                { "hostname",             required_argument, NULL, ARG_HOSTNAME             },
+                { "machine-id",           required_argument, NULL, ARG_MACHINE_ID           },
+                { "root-password",        required_argument, NULL, ARG_ROOT_PASSWORD        },
+                { "root-password-file",   required_argument, NULL, ARG_ROOT_PASSWORD_FILE   },
+                { "prompt",               no_argument,       NULL, ARG_PROMPT               },
+                { "prompt-locale",        no_argument,       NULL, ARG_PROMPT_LOCALE        },
+                { "prompt-timezone",      no_argument,       NULL, ARG_PROMPT_TIMEZONE      },
+                { "prompt-hostname",      no_argument,       NULL, ARG_PROMPT_HOSTNAME      },
+                { "prompt-root-password", no_argument,       NULL, ARG_PROMPT_ROOT_PASSWORD },
+                { "copy",                 no_argument,       NULL, ARG_COPY                 },
+                { "copy-locale",          no_argument,       NULL, ARG_COPY_LOCALE          },
+                { "copy-timezone",        no_argument,       NULL, ARG_COPY_TIMEZONE        },
+                { "copy-root-password",   no_argument,       NULL, ARG_COPY_ROOT_PASSWORD   },
+                { "setup-machine-id",     no_argument,       NULL, ARG_SETUP_MACHINE_ID     },
+                {}
+        };
+
+        int r, c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        return help();
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_ROOT:
+                        free(arg_root);
+                        arg_root = path_make_absolute_cwd(optarg);
+                        if (!arg_root)
+                                return log_oom();
+
+                        path_kill_slashes(arg_root);
+
+                        if (path_equal(arg_root, "/")) {
+                                free(arg_root);
+                                arg_root = NULL;
+                        }
+
+                        break;
+
+                case ARG_LOCALE:
+                        if (!locale_is_valid(optarg)) {
+                                log_error("Locale %s is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        free(arg_locale);
+                        arg_locale = strdup(optarg);
+                        if (!arg_locale)
+                                return log_oom();
+
+                        break;
+
+                case ARG_LOCALE_MESSAGES:
+                        if (!locale_is_valid(optarg)) {
+                                log_error("Locale %s is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        free(arg_locale_messages);
+                        arg_locale_messages = strdup(optarg);
+                        if (!arg_locale_messages)
+                                return log_oom();
+
+                        break;
+
+                case ARG_TIMEZONE:
+                        if (!timezone_is_valid(optarg)) {
+                                log_error("Timezone %s is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        free(arg_timezone);
+                        arg_timezone = strdup(optarg);
+                        if (!arg_timezone)
+                                return log_oom();
+
+                        break;
+
+                case ARG_ROOT_PASSWORD:
+                        free(arg_root_password);
+                        arg_root_password = strdup(optarg);
+                        if (!arg_root_password)
+                                return log_oom();
+
+                        break;
+
+                case ARG_ROOT_PASSWORD_FILE:
+                        free(arg_root_password);
+                        arg_root_password  = NULL;
+
+                        r = read_one_line_file(optarg, &arg_root_password);
+                        if (r < 0) {
+                                log_error("Failed to read %s: %s", optarg, strerror(-r));
+                                return r;
+                        }
+
+                        break;
+
+                case ARG_HOSTNAME:
+                        if (!hostname_is_valid(optarg)) {
+                                log_error("Host name %s is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        free(arg_hostname);
+                        arg_hostname = strdup(optarg);
+                        if (!arg_hostname)
+                                return log_oom();
+
+                        break;
+
+                case ARG_MACHINE_ID:
+                        if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
+                                log_error("Failed to parse machine id %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case ARG_PROMPT:
+                        arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
+                        break;
+
+                case ARG_PROMPT_LOCALE:
+                        arg_prompt_locale = true;
+                        break;
+
+                case ARG_PROMPT_TIMEZONE:
+                        arg_prompt_timezone = true;
+                        break;
+
+                case ARG_PROMPT_HOSTNAME:
+                        arg_prompt_hostname = true;
+                        break;
+
+                case ARG_PROMPT_ROOT_PASSWORD:
+                        arg_prompt_root_password = true;
+                        break;
+
+                case ARG_COPY:
+                        arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true;
+
+                case ARG_COPY_LOCALE:
+                        arg_copy_locale = true;
+                        break;
+
+                case ARG_COPY_TIMEZONE:
+                        arg_copy_timezone = true;
+                        break;
+
+                case ARG_COPY_ROOT_PASSWORD:
+                        arg_copy_root_password = true;
+                        break;
+
+                case ARG_SETUP_MACHINE_ID:
+
+                        r = sd_id128_randomize(&arg_machine_id);
+                        if (r < 0) {
+                                log_error("Failed to generate randomized machine ID: %s", strerror(-r));
+                                return r;
+                        }
+
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        r = process_locale();
+        if (r < 0)
+                goto finish;
+
+        r = process_timezone();
+        if (r < 0)
+                goto finish;
+
+        r = process_hostname();
+        if (r < 0)
+                goto finish;
+
+        r = process_machine_id();
+        if (r < 0)
+                goto finish;
+
+        r = process_root_password();
+        if (r < 0)
+                goto finish;
+
+finish:
+        free(arg_root);
+        free(arg_locale);
+        free(arg_locale_messages);
+        free(arg_timezone);
+        free(arg_hostname);
+        clear_string(arg_root_password);
+        free(arg_root_password);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/shared/util.c b/src/shared/util.c
index d223ecf..bef8730 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -1624,7 +1624,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         return 0;
 }
 
-int ask(char *ret, const char *replies, const char *text, ...) {
+int ask_char(char *ret, const char *replies, const char *text, ...) {
         int r;
 
         assert(ret);
@@ -1672,6 +1672,49 @@ int ask(char *ret, const char *replies, const char *text, ...) {
         }
 }
 
+int ask_string(char **ret, const char *text, ...) {
+        assert(ret);
+        assert(text);
+
+        for (;;) {
+                char line[LINE_MAX];
+                va_list ap;
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+                fflush(stdout);
+
+                errno = 0;
+                if (!fgets(line, sizeof(line), stdin))
+                        return errno ? -errno : -EIO;
+
+                if (!endswith(line, "\n"))
+                        putchar('\n');
+                else {
+                        char *s;
+
+                        if (isempty(line))
+                                continue;
+
+                        truncate_nl(line);
+                        s = strdup(line);
+                        if (!s)
+                                return -ENOMEM;
+
+                        *ret = s;
+                        return 0;
+                }
+        }
+}
+
 int reset_terminal_fd(int fd, bool switch_to_text) {
         struct termios termios;
         int r = 0;
@@ -3752,7 +3795,7 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
         return endswith(de->d_name, suffix);
 }
 
-void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv[]) {
+void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv[], char *env[]) {
         pid_t executor_pid;
         int r;
 
@@ -3783,6 +3826,14 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv
 
                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
 
+                if (!strv_isempty(env)) {
+                        char **i;
+
+                        STRV_FOREACH(i, env)
+                                putenv(*i);
+                }
+
+
                 if (!d) {
                         d = _d = opendir(directory);
                         if (!d) {
@@ -3807,7 +3858,8 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv
                         if (!dirent_is_file(de))
                                 continue;
 
-                        if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+                        path = strjoin(directory, "/", de->d_name, NULL);
+                        if (!path) {
                                 log_oom();
                                 _exit(EXIT_FAILURE);
                         }
diff --git a/src/shared/util.h b/src/shared/util.h
index 8544940..7c9842b 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -505,7 +505,7 @@ bool tty_is_console(const char *tty) _pure_;
 int vtnr_from_tty(const char *tty);
 const char *default_term_for_tty(const char *tty);
 
-void execute_directory(const char *directory, DIR *_d, usec_t timeout, char *argv[]);
+void execute_directory(const char *directory, DIR *_d, usec_t timeout, char *argv[], char *env[]);
 
 int kill_and_sigcont(pid_t pid, int sig);
 
diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c
index 5adbea5..3b0e927 100644
--- a/src/sleep/sleep.c
+++ b/src/sleep/sleep.c
@@ -110,7 +110,7 @@ static int execute(char **modes, char **states) {
         arguments[1] = (char*) "pre";
         arguments[2] = arg_verb;
         arguments[3] = NULL;
-        execute_directory(SYSTEM_SLEEP_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments);
+        execute_directory(SYSTEM_SLEEP_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments, NULL);
 
         log_struct(LOG_INFO,
                    MESSAGE_ID(SD_MESSAGE_SLEEP_START),
@@ -129,7 +129,7 @@ static int execute(char **modes, char **states) {
                    NULL);
 
         arguments[1] = (char*) "post";
-        execute_directory(SYSTEM_SLEEP_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments);
+        execute_directory(SYSTEM_SLEEP_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments, NULL);
 
         return r;
 }
diff --git a/units/.gitignore b/units/.gitignore
index 8ae6ca8..5d68927 100644
--- a/units/.gitignore
+++ b/units/.gitignore
@@ -23,6 +23,7 @@
 /systemd-backlight at .service
 /systemd-binfmt.service
 /systemd-bus-proxyd at .service
+/systemd-firstboot.service
 /systemd-fsck-root.service
 /systemd-fsck at .service
 /systemd-halt.service
diff --git a/units/systemd-firstboot.service.in b/units/systemd-firstboot.service.in
new file mode 100644
index 0000000..e7ae745
--- /dev/null
+++ b/units/systemd-firstboot.service.in
@@ -0,0 +1,23 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=First Boot Wizard
+Documentation=man:systemd-firstboot(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service systemd-remount-fs.service systemd-sysusers.service
+Before=sysinit.target shutdown.target
+ConditionPathIsReadWrite=/etc
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-firstboot --prompt-locale --prompt-timezone --prompt-root-password
+StandardOutput=tty
+StandardInput=tty
+StandardError=tty

commit 037c26d0aeb750ca9c8d605884ea1db7baecfea8
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 14:59:06 2014 +0200

    architecture: add string table entries for mips-le archs which were missing

diff --git a/src/shared/architecture.c b/src/shared/architecture.c
index 6cdca4e..dc45f35 100644
--- a/src/shared/architecture.c
+++ b/src/shared/architecture.c
@@ -153,7 +153,9 @@ static const char *const architecture_table[_ARCHITECTURE_MAX] = {
         [ARCHITECTURE_SPARC] = "sparc",
         [ARCHITECTURE_SPARC64] = "sparc64",
         [ARCHITECTURE_MIPS] = "mips",
+        [ARCHITECTURE_MIPS_LE] = "mips-le",
         [ARCHITECTURE_MIPS64] = "mips64",
+        [ARCHITECTURE_MIPS64_LE] = "mips64-le",
         [ARCHITECTURE_ALPHA] = "alpha",
         [ARCHITECTURE_ARM] = "arm",
         [ARCHITECTURE_ARM_BE] = "arm-be",

commit ac8ddf8c964f813464ef32cad1fcb7b61b692a01
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 14:58:36 2014 +0200

    man: chroot jails are no longer detected by ConditionVirtualization=

diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index be0873c..cd3279c 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -990,7 +990,6 @@
                                 <varname>oracle</varname>,
                                 <varname>xen</varname>,
                                 <varname>bochs</varname>,
-                                <varname>chroot</varname>,
                                 <varname>uml</varname>,
                                 <varname>openvz</varname>,
                                 <varname>lxc</varname>,

commit f1e4d93f573087655ab1d0adb725102d5d2c1960
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 14:58:13 2014 +0200

    man: add missing archs to ConditionArchitecture= description

diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 960fb90..be0873c 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -931,7 +931,9 @@
                                 <varname>x86</varname>,
                                 <varname>x86-64</varname>,
                                 <varname>ppc</varname>,
+                                <varname>ppc-le</varname>,
                                 <varname>ppc64</varname>,
+                                <varname>ppc64-le</varname>,
                                 <varname>ia64</varname>,
                                 <varname>parisc</varname>,
                                 <varname>parisc64</varname>,
@@ -940,7 +942,9 @@
                                 <varname>sparc</varname>,
                                 <varname>sparc64</varname>,
                                 <varname>mips</varname>,
+                                <varname>mips-le</varname>,
                                 <varname>mips64</varname>,
+                                <varname>mips64-le</varname>,
                                 <varname>alpha</varname>,
                                 <varname>arm</varname>,
                                 <varname>arm-be</varname>,
@@ -948,7 +952,9 @@
                                 <varname>arm64-be</varname>,
                                 <varname>sh</varname>,
                                 <varname>sh64</varname>,
-                                <varname>m86k</varname> to test
+                                <varname>m86k</varname>,
+                                <varname>tilegx</varname>,
+                                <varname>cris</varname> to test
                                 against a specific architecture. The
                                 architecture is determined from the
                                 information returned by

commit 736937e5aab60888f0bd2ca75674105f7b7fd0f0
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 12:05:41 2014 +0200

    fileio: simplify write_env_file()

diff --git a/src/shared/fileio.c b/src/shared/fileio.c
index b1de590..b0ab780 100644
--- a/src/shared/fileio.c
+++ b/src/shared/fileio.c
@@ -756,35 +756,31 @@ static void write_env_var(FILE *f, const char *v) {
 }
 
 int write_env_file(const char *fname, char **l) {
-        char **i;
-        _cleanup_free_ char *p = NULL;
         _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        char **i;
         int r;
 
+        assert(fname);
+
         r = fopen_temporary(fname, &f, &p);
         if (r < 0)
                 return r;
 
         fchmod_umask(fileno(f), 0644);
 
-        errno = 0;
         STRV_FOREACH(i, l)
                 write_env_var(f, *i);
 
-        fflush(f);
+        r = fflush_and_check(f);
+        if (r >= 0) {
+                if (rename(p, fname) >= 0)
+                        return 0;
 
-        if (ferror(f))
-                r = errno ? -errno : -EIO;
-        else {
-                if (rename(p, fname) < 0)
-                        r = -errno;
-                else
-                        r = 0;
+                r = -errno;
         }
 
-        if (r < 0)
-                unlink(p);
-
+        unlink(p);
         return r;
 }
 

commit 6294aa76d818e831de4592b41a37e225fd0871f9
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 12:04:55 2014 +0200

    util: don't consider tabs special in string_has_cc() anymore
    
    Instead, take a list of exceptions to our usual CC check

diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index 14629dd..514554d 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -550,8 +550,7 @@ static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop
 
                 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
-                if (prop == PROP_PRETTY_HOSTNAME &&
-                    (string_has_cc(name) || chars_intersect(name, "\t")))
+                if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
                 if (prop == PROP_CHASSIS && !valid_chassis(name))
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
diff --git a/src/shared/env-util.c b/src/shared/env-util.c
index b2e4553..20b208f 100644
--- a/src/shared/env-util.c
+++ b/src/shared/env-util.c
@@ -78,7 +78,9 @@ bool env_value_is_valid(const char *e) {
         if (!utf8_is_valid(e))
                 return false;
 
-        if (string_has_cc(e))
+        /* bash allows tabs in environment variables, and so should
+         * we */
+        if (string_has_cc(e, "\t"))
                 return false;
 
         /* POSIX says the overall size of the environment block cannot
diff --git a/src/shared/fileio.c b/src/shared/fileio.c
index fb1c1bc..b1de590 100644
--- a/src/shared/fileio.c
+++ b/src/shared/fileio.c
@@ -738,7 +738,7 @@ static void write_env_var(FILE *f, const char *v) {
         p++;
         fwrite(v, 1, p-v, f);
 
-        if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
+        if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
                 fputc('\"', f);
 
                 for (; *p; p++) {
diff --git a/src/shared/util.c b/src/shared/util.c
index d25ee66..d223ecf 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -5350,16 +5350,14 @@ bool filename_is_safe(const char *p) {
 bool string_is_safe(const char *p) {
         const char *t;
 
-        assert(p);
+        if (!p)
+                return false;
 
         for (t = p; *t; t++) {
                 if (*t > 0 && *t < ' ')
                         return false;
 
-                if (*t == 127)
-                        return false;
-
-                if (strchr("\\\"\'", *t))
+                if (strchr("\\\"\'\0x7f", *t))
                         return false;
         }
 
@@ -5367,16 +5365,19 @@ bool string_is_safe(const char *p) {
 }
 
 /**
- * Check if a string contains control characters.
- * Spaces and tabs are not considered control characters.
+ * Check if a string contains control characters. If 'ok' is non-NULL
+ * it may be a string containing additional CCs to be considered OK.
  */
-bool string_has_cc(const char *p) {
+bool string_has_cc(const char *p, const char *ok) {
         const char *t;
 
         assert(p);
 
         for (t = p; *t; t++) {
-                if (*t > 0 && *t < ' ' && *t != '\t')
+                if (ok && strchr(ok, *t))
+                        return false;
+
+                if (*t > 0 && *t < ' ')
                         return true;
 
                 if (*t == 127)
diff --git a/src/shared/util.h b/src/shared/util.h
index e23069c..8544940 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -382,7 +382,8 @@ bool fstype_is_network(const char *fstype);
 int chvt(int vt);
 
 int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl);
-int ask(char *ret, const char *replies, const char *text, ...) _printf_(3, 4);
+int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4);
+int ask_string(char **ret, const char *text, ...) _printf_(2, 3);
 
 int reset_terminal_fd(int fd, bool switch_to_text);
 int reset_terminal(const char *name);
@@ -692,7 +693,7 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t a, size_
 bool filename_is_safe(const char *p) _pure_;
 bool path_is_safe(const char *p) _pure_;
 bool string_is_safe(const char *p) _pure_;
-bool string_has_cc(const char *p) _pure_;
+bool string_has_cc(const char *p, const char *ok) _pure_;
 
 /**
  * Check if a string contains any glob patterns.

commit 7568345034f2890af745747783c5abfbf6eccf0f
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 11:49:48 2014 +0200

    shared: make timezone and locale enumeration and validation generic
    
    This way we can reuse it other code thatn just localectl/localed +
    timedatectl/timedated.

diff --git a/Makefile.am b/Makefile.am
index e9be6fa..0bf803a 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 2632305..8acc212 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 23da149..d6ffe67 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 0000000..8d2c363
--- /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 0000000..7be9af2
--- /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 8e5de77..fc79c56 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 34ba6c1..792cd27 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 a8769e4..203b5be 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 204031f..5d3b8c4 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),

commit a940778fb1dd16479f455bab3ac6cbdbc5b06165
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 11:48:06 2014 +0200

    update TODO

diff --git a/TODO b/TODO
index f09e853..7d577bb 100644
--- a/TODO
+++ b/TODO
@@ -35,10 +35,11 @@ Features:
 
 * logind: allow users to kill or lock their own sessions
 
+* add new gpt type for btrfs volumes
+
 * support empty /etc boots nicely:
   - nspawn/gpt-generator: introduce new gpt partition type for /usr
-  - nspawn: add --mode=auto,stateful,stateless,volatile
-  - fstab-generator: support auot/stateful/stateless/volatile on the kernel cmdline, too
+  - fstab-generator: support systemd.volatile=yes|no|state on the kernel cmdline, too, similar to nspawn's --volatile=
   - fstab-generator: add support for usr= in addition to root= on the kernel cmdline
 
 * generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them.

commit 3408ba015aee3a88c91962c028738be757779519
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 11:47:46 2014 +0200

    main: explain our /etc empty check a bit in a comment

diff --git a/src/core/main.c b/src/core/main.c
index 1ca8999..a732c69 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1543,6 +1543,14 @@ int main(int argc, char *argv[]) {
                 if (in_initrd())
                         log_info("Running in initial RAM disk.");
 
+                /* Let's check whether /etc is already populated. We
+                 * don't actually really check for that, but use
+                 * /etc/machine-id as flag file. This allows container
+                 * managers and installers to provision a couple of
+                 * files already. If the container manager wants to
+                 * provision the machine ID itself it should pass
+                 * $container_uuid to PID 1.*/
+
                 empty_etc = access("/etc/machine-id", F_OK) < 0;
                 if (empty_etc)
                         log_info("Running with unpopulated /etc.");

commit 3a8a916338d8446b938f3cf40f6aae0c611892e3
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jul 7 11:47:10 2014 +0200

    util: consider 0x7F a control chracter (which it is: DEL)
    
    Let's better be safe than sorry.

diff --git a/src/shared/util.c b/src/shared/util.c
index 3d875c7..d25ee66 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -1608,8 +1608,9 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                         return -ETIMEDOUT;
         }
 
+        errno = 0;
         if (!fgets(line, sizeof(line), f))
-                return -EIO;
+                return errno ? -errno : -EIO;
 
         truncate_nl(line);
 
@@ -5355,6 +5356,9 @@ bool string_is_safe(const char *p) {
                 if (*t > 0 && *t < ' ')
                         return false;
 
+                if (*t == 127)
+                        return false;
+
                 if (strchr("\\\"\'", *t))
                         return false;
         }
@@ -5371,10 +5375,14 @@ bool string_has_cc(const char *p) {
 
         assert(p);
 
-        for (t = p; *t; t++)
+        for (t = p; *t; t++) {
                 if (*t > 0 && *t < ' ' && *t != '\t')
                         return true;
 
+                if (*t == 127)
+                        return true;
+        }
+
         return false;
 }
 



More information about the systemd-commits mailing list