chiark / gitweb /
rtc in localtime: use settimeofday(NULL, tz) instead of hwclock(8)
[elogind.git] / src / util.c
index 98422b236d263cd9f0f7e1580e5b5696361c1ed7..4046938fcdbef876c9dfdee5f3bfc8d3faf52cfa 100644 (file)
@@ -50,6 +50,9 @@
 #include <linux/kd.h>
 #include <dlfcn.h>
 #include <sys/wait.h>
+#include <sys/capability.h>
+#include <sys/time.h>
+#include <linux/rtc.h>
 
 #include "macro.h"
 #include "util.h"
 #include "strv.h"
 #include "label.h"
 #include "exit-status.h"
+#include "hashmap.h"
+
+size_t page_size(void) {
+        static __thread size_t pgsz = 0;
+        long r;
+
+        if (pgsz)
+                return pgsz;
+
+        assert_se((r = sysconf(_SC_PAGESIZE)) > 0);
+
+        pgsz = (size_t) r;
+
+        return pgsz;
+}
 
 bool streq_ptr(const char *a, const char *b) {
 
@@ -250,7 +268,7 @@ int parse_boolean(const char *v) {
 }
 
 int parse_pid(const char *s, pid_t* ret_pid) {
-        unsigned long ul;
+        unsigned long ul = 0;
         pid_t pid;
         int r;
 
@@ -437,14 +455,14 @@ char **split_path_and_make_absolute(const char *p) {
 int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
         int r;
         FILE *f;
-        char fn[132], line[256], *p;
+        char fn[PATH_MAX], line[LINE_MAX], *p;
         long unsigned ppid;
 
-        assert(pid >= 0);
+        assert(pid > 0);
         assert(_ppid);
 
         assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1));
-        fn[sizeof(fn)-1] = 0;
+        char_array_0(fn);
 
         if (!(f = fopen(fn, "r")))
                 return -errno;
@@ -480,6 +498,64 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
         return 0;
 }
 
+int get_starttime_of_pid(pid_t pid, unsigned long long *st) {
+        int r;
+        FILE *f;
+        char fn[PATH_MAX], line[LINE_MAX], *p;
+
+        assert(pid > 0);
+        assert(st);
+
+        assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1));
+        char_array_0(fn);
+
+        if (!(f = fopen(fn, "r")))
+                return -errno;
+
+        if (!(fgets(line, sizeof(line), f))) {
+                r = -errno;
+                fclose(f);
+                return r;
+        }
+
+        fclose(f);
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        if (!(p = strrchr(line, ')')))
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%*d "  /* ppid */
+                   "%*d "  /* pgrp */
+                   "%*d "  /* session */
+                   "%*d "  /* tty_nr */
+                   "%*d "  /* tpgid */
+                   "%*u "  /* flags */
+                   "%*u "  /* minflt */
+                   "%*u "  /* cminflt */
+                   "%*u "  /* majflt */
+                   "%*u "  /* cmajflt */
+                   "%*u "  /* utime */
+                   "%*u "  /* stime */
+                   "%*d "  /* cutime */
+                   "%*d "  /* cstime */
+                   "%*d "  /* priority */
+                   "%*d "  /* nice */
+                   "%*d "  /* num_threads */
+                   "%*d "  /* itrealvalue */
+                   "%llu "  /* starttime */,
+                   st) != 1)
+                return -EIO;
+
+        return 0;
+}
+
 int write_one_line_file(const char *fn, const char *line) {
         FILE *f;
         int r;
@@ -495,7 +571,19 @@ int write_one_line_file(const char *fn, const char *line) {
                 goto finish;
         }
 
-        r = 0;
+        if (!endswith(line, "\n"))
+                fputc('\n', f);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                if (errno != 0)
+                        r = -errno;
+                else
+                        r = -EIO;
+        } else
+                r = 0;
+
 finish:
         fclose(f);
         return r;
@@ -522,6 +610,8 @@ int read_one_line_file(const char *fn, char **line) {
                 goto finish;
         }
 
+        truncate_nl(c);
+
         *line = c;
         r = 0;
 
@@ -679,6 +769,96 @@ fail:
         return r;
 }
 
+int load_env_file(
+                const char *fname,
+                char ***rl) {
+
+        FILE *f;
+        char **m = 0;
+        int r;
+
+        assert(fname);
+        assert(rl);
+
+        if (!(f = fopen(fname, "re")))
+                return -errno;
+
+        while (!feof(f)) {
+                char l[LINE_MAX], *p, *u;
+                char **t;
+
+                if (!fgets(l, sizeof(l), f)) {
+                        if (feof(f))
+                                break;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                p = strstrip(l);
+
+                if (!*p)
+                        continue;
+
+                if (strchr(COMMENTS, *p))
+                        continue;
+
+                if (!(u = normalize_env_assignment(p))) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                t = strv_append(m, u);
+                free(u);
+
+                if (!t) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                strv_free(m);
+                m = t;
+        }
+
+        r = 0;
+
+        *rl = m;
+        m = NULL;
+
+finish:
+        if (f)
+                fclose(f);
+
+        strv_free(m);
+
+        return r;
+}
+
+int write_env_file(const char *fname, char **l) {
+
+        char **i;
+        FILE *f;
+        int r;
+
+        f = fopen(fname, "we");
+        if (!f)
+                return -errno;
+
+        STRV_FOREACH(i, l) {
+                fputs(*i, f);
+                fputc('\n', f);
+        }
+
+        fflush(f);
+
+        r = ferror(f) ? -errno : 0;
+        fclose(f);
+
+        return r;
+}
+
 char *truncate_nl(char *s) {
         assert(s);
 
@@ -702,7 +882,6 @@ int get_process_name(pid_t pid, char **name) {
         if (r < 0)
                 return r;
 
-        truncate_nl(*name);
         return 0;
 }
 
@@ -1032,6 +1211,26 @@ char **strv_path_canonicalize(char **l) {
         return l;
 }
 
+char **strv_path_remove_empty(char **l) {
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        for (f = t = l; *f; f++) {
+
+                if (dir_is_empty(*f) > 0) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
 int reset_all_signal_handlers(void) {
         int sig;
 
@@ -1744,8 +1943,9 @@ int close_all_fds(const int except[], unsigned n_except) {
                 if (ignore_file(de->d_name))
                         continue;
 
-                if ((r = safe_atoi(de->d_name, &fd)) < 0)
-                        goto finish;
+                if (safe_atoi(de->d_name, &fd) < 0)
+                        /* Let's better ignore this, just in case */
+                        continue;
 
                 if (fd < 3)
                         continue;
@@ -1768,16 +1968,13 @@ int close_all_fds(const int except[], unsigned n_except) {
                                 continue;
                 }
 
-                if ((r = close_nointr(fd)) < 0) {
+                if (close_nointr(fd) < 0) {
                         /* Valgrind has its own FD and doesn't want to have it closed */
-                        if (errno != EBADF)
-                                goto finish;
+                        if (errno != EBADF && r == 0)
+                                r = -errno;
                 }
         }
 
-        r = 0;
-
-finish:
         closedir(d);
         return r;
 }
@@ -1946,7 +2143,7 @@ bool fstype_is_network(const char *fstype) {
 int chvt(int vt) {
         int fd, r = 0;
 
-        if ((fd = open("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+        if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
                 return -errno;
 
         if (vt < 0) {
@@ -1971,7 +2168,7 @@ int chvt(int vt) {
 int read_one_char(FILE *f, char *ret, bool *need_nl) {
         struct termios old_termios, new_termios;
         char c;
-        char line[1024];
+        char line[LINE_MAX];
 
         assert(f);
         assert(ret);
@@ -2066,7 +2263,7 @@ int ask(char *ret, const char *replies, const char *text, ...) {
         }
 }
 
-int reset_terminal(int fd) {
+int reset_terminal_fd(int fd) {
         struct termios termios;
         int r = 0;
         long arg;
@@ -2128,10 +2325,47 @@ finish:
         return r;
 }
 
+int reset_terminal(const char *name) {
+        int fd, r;
+
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = reset_terminal_fd(fd);
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
 int open_terminal(const char *name, int mode) {
         int fd, r;
+        unsigned c = 0;
+
+        /*
+         * If a TTY is in the process of being closed opening it might
+         * cause EIO. This is horribly awful, but unlikely to be
+         * changed in the kernel. Hence we work around this problem by
+         * retrying a couple of times.
+         *
+         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
+         */
+
+        for (;;) {
+                if ((fd = open(name, mode)) >= 0)
+                        break;
+
+                if (errno != EIO)
+                        return -errno;
+
+                if (c >= 20)
+                        return -errno;
+
+                usleep(50 * USEC_PER_MSEC);
+                c++;
+        }
 
-        if ((fd = open(name, mode)) < 0)
+        if (fd < 0)
                 return -errno;
 
         if ((r = isatty(fd)) < 0) {
@@ -2155,7 +2389,7 @@ int flush_fd(int fd) {
         pollfd.events = POLLIN;
 
         for (;;) {
-                char buf[1024];
+                char buf[LINE_MAX];
                 ssize_t l;
                 int r;
 
@@ -2224,8 +2458,8 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst
                 /* We pass here O_NOCTTY only so that we can check the return
                  * value TIOCSCTTY and have a reliable way to figure out if we
                  * successfully became the controlling process of the tty */
-                if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0)
-                        return -errno;
+                if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+                        return fd;
 
                 /* First, try to get the tty */
                 r = ioctl(fd, TIOCSCTTY, force);
@@ -2292,7 +2526,7 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst
         if (notify >= 0)
                 close_nointr_nofail(notify);
 
-        if ((r = reset_terminal(fd)) < 0)
+        if ((r = reset_terminal_fd(fd)) < 0)
                 log_warning("Failed to reset terminal: %s", strerror(-r));
 
         return fd;
@@ -2605,31 +2839,13 @@ int make_stdio(int fd) {
         return 0;
 }
 
-bool is_clean_exit(int code, int status) {
-
-        if (code == CLD_EXITED)
-                return status == 0;
-
-        /* If a daemon does not implement handlers for some of the
-         * signals that's not considered an unclean shutdown */
-        if (code == CLD_KILLED)
-                return
-                        status == SIGHUP ||
-                        status == SIGINT ||
-                        status == SIGTERM ||
-                        status == SIGPIPE;
-
-        return false;
-}
-
-bool is_clean_exit_lsb(int code, int status) {
+int make_null_stdio(void) {
+        int null_fd;
 
-        if (is_clean_exit(code, status))
-                return true;
+        if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0)
+                return -errno;
 
-        return
-                code == CLD_EXITED &&
-                (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED);
+        return make_stdio(null_fd);
 }
 
 bool is_device_path(const char *path) {
@@ -2763,28 +2979,139 @@ char* getlogname_malloc(void) {
         return name;
 }
 
-int getttyname_malloc(char **r) {
-        char path[PATH_MAX], *p, *c;
+int getttyname_malloc(int fd, char **r) {
+        char path[PATH_MAX], *c;
         int k;
 
         assert(r);
 
-        if ((k = ttyname_r(STDIN_FILENO, path, sizeof(path))) != 0)
+        if ((k = ttyname_r(fd, path, sizeof(path))) != 0)
                 return -k;
 
         char_array_0(path);
 
-        p = path;
-        if (startswith(path, "/dev/"))
-                p += 5;
-
-        if (!(c = strdup(p)))
+        if (!(c = strdup(startswith(path, "/dev/") ? path + 5 : path)))
                 return -ENOMEM;
 
         *r = c;
         return 0;
 }
 
+int getttyname_harder(int fd, char **r) {
+        int k;
+        char *s;
+
+        if ((k = getttyname_malloc(fd, &s)) < 0)
+                return k;
+
+        if (streq(s, "tty")) {
+                free(s);
+                return get_ctty(r, NULL);
+        }
+
+        *r = s;
+        return 0;
+}
+
+int get_ctty_devnr(dev_t *d) {
+        int k;
+        char line[LINE_MAX], *p;
+        unsigned long ttynr;
+        FILE *f;
+
+        if (!(f = fopen("/proc/self/stat", "r")))
+                return -errno;
+
+        if (!(fgets(line, sizeof(line), f))) {
+                k = -errno;
+                fclose(f);
+                return k;
+        }
+
+        fclose(f);
+
+        if (!(p = strrchr(line, ')')))
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%*d "  /* ppid */
+                   "%*d "  /* pgrp */
+                   "%*d "  /* session */
+                   "%lu ", /* ttynr */
+                   &ttynr) != 1)
+                return -EIO;
+
+        *d = (dev_t) ttynr;
+        return 0;
+}
+
+int get_ctty(char **r, dev_t *_devnr) {
+        int k;
+        char fn[PATH_MAX], *s, *b, *p;
+        dev_t devnr;
+
+        assert(r);
+
+        if ((k = get_ctty_devnr(&devnr)) < 0)
+                return k;
+
+        snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr));
+        char_array_0(fn);
+
+        if ((k = readlink_malloc(fn, &s)) < 0) {
+
+                if (k != -ENOENT)
+                        return k;
+
+                /* This is an ugly hack */
+                if (major(devnr) == 136) {
+                        if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0)
+                                return -ENOMEM;
+
+                        *r = b;
+                        if (_devnr)
+                                *_devnr = devnr;
+
+                        return 0;
+                }
+
+                /* Probably something like the ptys which have no
+                 * symlink in /dev/char. Let's return something
+                 * vaguely useful. */
+
+                if (!(b = strdup(fn + 5)))
+                        return -ENOMEM;
+
+                *r = b;
+                if (_devnr)
+                        *_devnr = devnr;
+
+                return 0;
+        }
+
+        if (startswith(s, "/dev/"))
+                p = s + 5;
+        else if (startswith(s, "../"))
+                p = s + 3;
+        else
+                p = s;
+
+        b = strdup(p);
+        free(s);
+
+        if (!b)
+                return -ENOMEM;
+
+        *r = b;
+        if (_devnr)
+                *_devnr = devnr;
+
+        return 0;
+}
+
 static int rm_rf_children(int fd, bool only_dirs) {
         DIR *d;
         int ret = 0;
@@ -2970,74 +3297,172 @@ void status_printf(const char *format, ...) {
 }
 
 void status_welcome(void) {
+        char *pretty_name = NULL, *ansi_color = NULL;
+        const char *const_pretty = NULL, *const_color = NULL;
+        int r;
 
-#if defined(TARGET_FEDORA)
-        char *r;
+        if ((r = parse_env_file("/etc/os-release", NEWLINE,
+                                "PRETTY_NAME", &pretty_name,
+                                "ANSI_COLOR", &ansi_color,
+                                NULL)) < 0) {
 
-        if (read_one_line_file("/etc/system-release", &r) < 0)
-                return;
+                if (r != -ENOENT)
+                        log_warning("Failed to read /etc/os-release: %s", strerror(-r));
+        }
 
-        truncate_nl(r);
+#if defined(TARGET_FEDORA)
+        if (!pretty_name) {
+                if ((r = read_one_line_file("/etc/system-release", &pretty_name)) < 0) {
 
-        /* This tries to mimic the color magic the old Red Hat sysinit
-         * script did. */
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/system-release: %s", strerror(-r));
+                }
+        }
 
-        if (startswith(r, "Red Hat"))
-                status_printf("Welcome to \x1B[0;31m%s\x1B[0m!\n", r); /* Red for RHEL */
-        else if (startswith(r, "Fedora"))
-                status_printf("Welcome to \x1B[0;34m%s\x1B[0m!\n", r); /* Blue for Fedora */
-        else
-                status_printf("Welcome to %s!\n", r);
+        if (!ansi_color && pretty_name) {
 
-        free(r);
+                /* This tries to mimic the color magic the old Red Hat sysinit
+                 * script did. */
+
+                if (startswith(pretty_name, "Red Hat"))
+                        const_color = "0;31"; /* Red for RHEL */
+                else if (startswith(pretty_name, "Fedora"))
+                        const_color = "0;34"; /* Blue for Fedora */
+        }
 
 #elif defined(TARGET_SUSE)
-        char *r;
 
-        if (read_one_line_file("/etc/SuSE-release", &r) < 0)
-                return;
+        if (!pretty_name) {
+                if ((r = read_one_line_file("/etc/SuSE-release", &pretty_name)) < 0) {
 
-        truncate_nl(r);
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/SuSE-release: %s", strerror(-r));
+                }
+        }
 
-        status_printf("Welcome to \x1B[0;32m%s\x1B[0m!\n", r); /* Green for SUSE */
-        free(r);
+        if (!ansi_color)
+                const_color = "0;32"; /* Green for openSUSE */
 
 #elif defined(TARGET_GENTOO)
-        char *r;
 
-        if (read_one_line_file("/etc/gentoo-release", &r) < 0)
-                return;
+        if (!pretty_name) {
+                if ((r = read_one_line_file("/etc/gentoo-release", &pretty_name)) < 0) {
 
-        truncate_nl(r);
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/gentoo-release: %s", strerror(-r));
+                }
+        }
 
-        status_printf("Welcome to \x1B[1;34m%s\x1B[0m!\n", r); /* Light Blue for Gentoo */
+        if (!ansi_color)
+                const_color = "1;34"; /* Light Blue for Gentoo */
 
-        free(r);
+#elif defined(TARGET_ALTLINUX)
 
-#elif defined(TARGET_DEBIAN)
-        char *r;
+        if (!pretty_name) {
+                if ((r = read_one_line_file("/etc/altlinux-release", &pretty_name)) < 0) {
 
-        if (read_one_line_file("/etc/debian_version", &r) < 0)
-                return;
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/altlinux-release: %s", strerror(-r));
+                }
+        }
 
-        truncate_nl(r);
+        if (!ansi_color)
+                const_color = "0;36"; /* Cyan for ALTLinux */
 
-        status_printf("Welcome to Debian \x1B[1;31m%s\x1B[0m!\n", r); /* Light Red for Debian */
 
-        free(r);
-#elif defined(TARGET_ARCH)
-        status_printf("Welcome to \x1B[1;36mArch Linux\x1B[0m!\n"); /* Cyan for Arch */
-#else
-#warning "You probably should add a welcome text logic here."
-#endif
-}
+#elif defined(TARGET_DEBIAN)
 
-char *replace_env(const char *format, char **env) {
-        enum {
-                WORD,
-                CURLY,
-                VARIABLE
-        } state = WORD;
+        if (!pretty_name) {
+                char *version;
+
+                if ((r = read_one_line_file("/etc/debian_version", &version)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/debian_version: %s", strerror(-r));
+                } else {
+                        pretty_name = strappend("Debian ", version);
+                        free(version);
+
+                        if (!pretty_name)
+                                log_warning("Failed to allocate Debian version string.");
+                }
+        }
+
+        if (!ansi_color)
+                const_color = "1;31"; /* Light Red for Debian */
+
+#elif defined(TARGET_UBUNTU)
+
+        if ((r = parse_env_file("/etc/lsb-release", NEWLINE,
+                                "DISTRIB_DESCRIPTION", &pretty_name,
+                                NULL)) < 0) {
+
+                if (r != -ENOENT)
+                        log_warning("Failed to read /etc/lsb-release: %s", strerror(-r));
+        }
+
+        if (!ansi_color)
+                const_color = "0;33"; /* Orange/Brown for Ubuntu */
+
+#elif defined(TARGET_MANDRIVA)
+
+        if (!pretty_name) {
+                char *s, *p;
+
+                if ((r = read_one_line_file("/etc/mandriva-release", &s) < 0)) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/mandriva-release: %s", strerror(-r));
+                } else {
+                        p = strstr(s, " release ");
+                        if (p) {
+                                *p = '\0';
+                                p += 9;
+                                p[strcspn(p, " ")] = '\0';
+
+                                /* This corresponds to standard rc.sysinit */
+                                if (asprintf(&pretty_name, "%s\x1B[0;39m %s", s, p) > 0)
+                                        const_color = "1;36";
+                                else
+                                        log_warning("Failed to allocate Mandriva version string.");
+                        } else
+                                log_warning("Failed to parse /etc/mandriva-release");
+                        free(s);
+                }
+        }
+#elif defined(TARGET_MEEGO)
+
+        if (!pretty_name) {
+                if ((r = read_one_line_file("/etc/meego-release", &pretty_name)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/meego-release: %s", strerror(-r));
+                }
+        }
+
+       if (!ansi_color)
+               const_color = "1;35"; /* Bright Magenta for MeeGo */
+#endif
+
+        if (!pretty_name && !const_pretty)
+                const_pretty = "Linux";
+
+        if (!ansi_color && !const_color)
+                const_color = "1";
+
+        status_printf("\nWelcome to \x1B[%sm%s\x1B[0m!\n\n",
+                      const_color ? const_color : ansi_color,
+                      const_pretty ? const_pretty : pretty_name);
+
+        free(ansi_color);
+        free(pretty_name);
+}
+
+char *replace_env(const char *format, char **env) {
+        enum {
+                WORD,
+                CURLY,
+                VARIABLE
+        } state = WORD;
 
         const char *e, *word = format;
         char *r = NULL, *k;
@@ -3246,7 +3671,7 @@ int touch(const char *path) {
 
         assert(path);
 
-        if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0)
+        if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644)) < 0)
                 return -errno;
 
         close_nointr_nofail(fd);
@@ -3266,6 +3691,43 @@ char *unquote(const char *s, const char* quotes) {
         return strdup(s);
 }
 
+char *normalize_env_assignment(const char *s) {
+        char *name, *value, *p, *r;
+
+        p = strchr(s, '=');
+
+        if (!p) {
+                if (!(r = strdup(s)))
+                        return NULL;
+
+                return strstrip(r);
+        }
+
+        if (!(name = strndup(s, p - s)))
+                return NULL;
+
+        if (!(p = strdup(p+1))) {
+                free(name);
+                return NULL;
+        }
+
+        value = unquote(strstrip(p), QUOTES);
+        free(p);
+
+        if (!value) {
+                free(name);
+                return NULL;
+        }
+
+        if (asprintf(&r, "%s=%s", name, value) < 0)
+                r = NULL;
+
+        free(value);
+        free(name);
+
+        return r;
+}
+
 int wait_for_terminate(pid_t pid, siginfo_t *status) {
         assert(pid >= 1);
         assert(status);
@@ -3300,7 +3762,7 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) {
         if (status.si_code == CLD_EXITED) {
                 if (status.si_status != 0) {
                         log_warning("%s failed with error code %i.", name, status.si_status);
-                        return -EPROTO;
+                        return status.si_status;
                 }
 
                 log_debug("%s succeeded.", name);
@@ -3319,6 +3781,12 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) {
 }
 
 void freeze(void) {
+
+        /* Make sure nobody waits for us on a socket anymore */
+        close_all_fds(NULL, 0);
+
+        sync();
+
         for (;;)
                 pause();
 }
@@ -3335,8 +3803,19 @@ bool null_or_empty(struct stat *st) {
         return false;
 }
 
-DIR *xopendirat(int fd, const char *name) {
-        return fdopendir(openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+DIR *xopendirat(int fd, const char *name, int flags) {
+        int nfd;
+        DIR *d;
+
+        if ((nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags)) < 0)
+                return NULL;
+
+        if (!(d = fdopendir(nfd))) {
+                close_nointr_nofail(nfd);
+                return NULL;
+        }
+
+        return d;
 }
 
 int signal_from_string_try_harder(const char *s) {
@@ -3350,170 +3829,687 @@ int signal_from_string_try_harder(const char *s) {
         return signo;
 }
 
-int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase) {
-        struct termios old_termios, new_termios;
-        char passphrase[LINE_MAX];
-        size_t p = 0;
-        int r, ttyfd = -1, notify = -1;
-        struct pollfd pollfd[2];
-        bool reset_tty = false;
-        enum {
-                POLL_TTY,
-                POLL_INOTIFY
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
+
+        assert(f);
+        assert(name);
+        assert(t);
+
+        if (!dual_timestamp_is_set(t))
+                return;
+
+        fprintf(f, "%s=%llu %llu\n",
+                name,
+                (unsigned long long) t->realtime,
+                (unsigned long long) t->monotonic);
+}
+
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
+        unsigned long long a, b;
+
+        assert(value);
+        assert(t);
+
+        if (sscanf(value, "%lli %llu", &a, &b) != 2)
+                log_debug("Failed to parse finish timestamp value %s", value);
+        else {
+                t->realtime = a;
+                t->monotonic = b;
+        }
+}
+
+char *fstab_node_to_udev_node(const char *p) {
+        char *dn, *t, *u;
+        int r;
+
+        /* FIXME: to follow udev's logic 100% we need to leave valid
+         * UTF8 chars unescaped */
+
+        if (startswith(p, "LABEL=")) {
+
+                if (!(u = unquote(p+6, "\"\'")))
+                        return NULL;
+
+                t = xescape(u, "/ ");
+                free(u);
+
+                if (!t)
+                        return NULL;
+
+                r = asprintf(&dn, "/dev/disk/by-label/%s", t);
+                free(t);
+
+                if (r < 0)
+                        return NULL;
+
+                return dn;
+        }
+
+        if (startswith(p, "UUID=")) {
+
+                if (!(u = unquote(p+5, "\"\'")))
+                        return NULL;
+
+                t = xescape(u, "/ ");
+                free(u);
+
+                if (!t)
+                        return NULL;
+
+                r = asprintf(&dn, "/dev/disk/by-uuid/%s", t);
+                free(t);
+
+                if (r < 0)
+                        return NULL;
+
+                return dn;
+        }
+
+        return strdup(p);
+}
+
+void filter_environ(const char *prefix) {
+        int i, j;
+        assert(prefix);
+
+        if (!environ)
+                return;
+
+        for (i = 0, j = 0; environ[i]; i++) {
+
+                if (startswith(environ[i], prefix))
+                        continue;
+
+                environ[j++] = environ[i];
+        }
+
+        environ[j] = NULL;
+}
+
+bool tty_is_vc(const char *tty) {
+        assert(tty);
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        return startswith(tty, "tty") &&
+                tty[3] >= '0' && tty[3] <= '9';
+}
+
+const char *default_term_for_tty(const char *tty) {
+        char *active = NULL;
+        const char *term;
+
+        assert(tty);
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        /* Resolve where /dev/console is pointing when determining
+         * TERM */
+        if (streq(tty, "console"))
+                if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) {
+                        /* If multiple log outputs are configured the
+                         * last one is what /dev/console points to */
+                        if ((tty = strrchr(active, ' ')))
+                                tty++;
+                        else
+                                tty = active;
+                }
+
+        term = tty_is_vc(tty) ? "TERM=linux" : "TERM=vt100";
+        free(active);
+
+        return term;
+}
+
+/* Returns a short identifier for the various VM implementations */
+int detect_vm(const char **id) {
+
+#if defined(__i386__) || defined(__x86_64__)
+
+        /* Both CPUID and DMI are x86 specific interfaces... */
+
+        static const char *const dmi_vendors[] = {
+                "/sys/class/dmi/id/sys_vendor",
+                "/sys/class/dmi/id/board_vendor",
+                "/sys/class/dmi/id/bios_vendor"
         };
 
-        assert(message);
-        assert(_passphrase);
+        static const char dmi_vendor_table[] =
+                "QEMU\0"                  "qemu\0"
+                /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+                "VMware\0"                "vmware\0"
+                "VMW\0"                   "vmware\0"
+                "Microsoft Corporation\0" "microsoft\0"
+                "innotek GmbH\0"          "oracle\0"
+                "Xen\0"                   "xen\0"
+                "Bochs\0"                 "bochs\0";
+
+        static const char cpuid_vendor_table[] =
+                "XenVMMXenVMM\0"          "xen\0"
+                "KVMKVMKVM\0"             "kvm\0"
+                /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+                "VMwareVMware\0"          "vmware\0"
+                /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
+                "Microsoft Hv\0"          "microsoft\0";
+
+        uint32_t eax, ecx;
+        union {
+                uint32_t sig32[3];
+                char text[13];
+        } sig;
+        unsigned i;
+        const char *j, *k;
+        bool hypervisor;
+
+        /* http://lwn.net/Articles/301888/ */
+        zero(sig);
+
+#if defined (__i386__)
+#define REG_a "eax"
+#define REG_b "ebx"
+#elif defined (__amd64__)
+#define REG_a "rax"
+#define REG_b "rbx"
+#endif
 
-        if (flag_file) {
-                if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
-                        r = -errno;
-                        goto finish;
+        /* First detect whether there is a hypervisor */
+        eax = 1;
+        __asm__ __volatile__ (
+                /* ebx/rbx is being used for PIC! */
+                "  push %%"REG_b"         \n\t"
+                "  cpuid                  \n\t"
+                "  pop %%"REG_b"          \n\t"
+
+                : "=a" (eax), "=c" (ecx)
+                : "0" (eax)
+        );
+
+        hypervisor = !!(ecx & 0x80000000U);
+
+        if (hypervisor) {
+
+                /* There is a hypervisor, see what it is */
+                eax = 0x40000000U;
+                __asm__ __volatile__ (
+                        /* ebx/rbx is being used for PIC! */
+                        "  push %%"REG_b"         \n\t"
+                        "  cpuid                  \n\t"
+                        "  mov %%ebx, %1          \n\t"
+                        "  pop %%"REG_b"          \n\t"
+
+                        : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
+                        : "0" (eax)
+                );
+
+                NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
+                        if (streq(sig.text, j)) {
+
+                                if (id)
+                                        *id = k;
+
+                                return 1;
+                        }
+        }
+
+        for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
+                char *s;
+                int r;
+                const char *found = NULL;
+
+                if ((r = read_one_line_file(dmi_vendors[i], &s)) < 0) {
+                        if (r != -ENOENT)
+                                return r;
+
+                        continue;
                 }
 
-                if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
-                        r = -errno;
-                        goto finish;
+                NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
+                        if (startswith(s, j))
+                                found = k;
+                free(s);
+
+                if (found) {
+                        if (id)
+                                *id = found;
+
+                        return 1;
                 }
         }
 
-        if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) {
+        if (hypervisor) {
+                if (id)
+                        *id = "other";
 
-                if (tcgetattr(ttyfd, &old_termios) < 0) {
-                        r = -errno;
-                        goto finish;
+                return 1;
+        }
+
+#endif
+        return 0;
+}
+
+int detect_container(const char **id) {
+        FILE *f;
+
+        /* Unfortunately many of these operations require root access
+         * in one way or another */
+
+        if (geteuid() != 0)
+                return -EPERM;
+
+        if (running_in_chroot() > 0) {
+
+                if (id)
+                        *id = "chroot";
+
+                return 1;
+        }
+
+        /* /proc/vz exists in container and outside of the container,
+         * /proc/bc only outside of the container. */
+        if (access("/proc/vz", F_OK) >= 0 &&
+            access("/proc/bc", F_OK) < 0) {
+
+                if (id)
+                        *id = "openvz";
+
+                return 1;
+        }
+
+        if ((f = fopen("/proc/self/cgroup", "r"))) {
+
+                for (;;) {
+                        char line[LINE_MAX], *p;
+
+                        if (!fgets(line, sizeof(line), f))
+                                break;
+
+                        if (!(p = strchr(strstrip(line), ':')))
+                                continue;
+
+                        if (strncmp(p, ":ns:", 4))
+                                continue;
+
+                        if (!streq(p, ":ns:/")) {
+                                fclose(f);
+
+                                if (id)
+                                        *id = "pidns";
+
+                                return 1;
+                        }
                 }
 
-                loop_write(ttyfd, "\x1B[1m", 4, false);
-                loop_write(ttyfd, message, strlen(message), false);
-                loop_write(ttyfd, ": ", 2, false);
-                loop_write(ttyfd, "\x1B[0m", 4, false);
+                fclose(f);
+        }
 
-                new_termios = old_termios;
-                new_termios.c_lflag &= ~(ICANON|ECHO);
-                new_termios.c_cc[VMIN] = 1;
-                new_termios.c_cc[VTIME] = 0;
+        return 0;
+}
 
-                if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
-                        r = -errno;
-                        goto finish;
+/* Returns a short identifier for the various VM/container implementations */
+int detect_virtualization(const char **id) {
+        static __thread const char *cached_id = NULL;
+        const char *_id;
+        int r;
+
+        if (cached_id) {
+
+                if (cached_id == (const char*) -1)
+                        return 0;
+
+                if (id)
+                        *id = cached_id;
+
+                return 1;
+        }
+
+        if ((r = detect_container(&_id)) != 0)
+                goto finish;
+
+        r = detect_vm(&_id);
+
+finish:
+        if (r > 0) {
+                cached_id = _id;
+
+                if (id)
+                        *id = _id;
+        } else if (r == 0)
+                cached_id = (const char*) -1;
+
+        return r;
+}
+
+void execute_directory(const char *directory, DIR *d, char *argv[]) {
+        DIR *_d = NULL;
+        struct dirent *de;
+        Hashmap *pids = NULL;
+
+        assert(directory);
+
+        /* Executes all binaries in a directory in parallel and waits
+         * until all they all finished. */
+
+        if (!d) {
+                if (!(_d = opendir(directory))) {
+
+                        if (errno == ENOENT)
+                                return;
+
+                        log_error("Failed to enumerate directory %s: %m", directory);
+                        return;
                 }
 
-                reset_tty = true;
+                d = _d;
         }
 
-        zero(pollfd);
+        if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) {
+                log_error("Failed to allocate set.");
+                goto finish;
+        }
 
-        pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
-        pollfd[POLL_TTY].events = POLLIN;
-        pollfd[POLL_INOTIFY].fd = notify;
-        pollfd[POLL_INOTIFY].events = POLLIN;
+        while ((de = readdir(d))) {
+                char *path;
+                pid_t pid;
+                int k;
 
-        for (;;) {
-                char c;
-                int sleep_for = -1, k;
-                ssize_t n;
+                if (ignore_file(de->d_name))
+                        continue;
 
-                if (until > 0) {
-                        usec_t y;
+                if (de->d_type != DT_REG &&
+                    de->d_type != DT_LNK &&
+                    de->d_type != DT_UNKNOWN)
+                        continue;
 
-                        y = now(CLOCK_MONOTONIC);
+                if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+                        log_error("Out of memory");
+                        continue;
+                }
 
-                        if (y > until) {
-                                r = -ETIMEDOUT;
-                                goto finish;
-                        }
+                if ((pid = fork()) < 0) {
+                        log_error("Failed to fork: %m");
+                        free(path);
+                        continue;
+                }
+
+                if (pid == 0) {
+                        char *_argv[2];
+                        /* Child */
+
+                        if (!argv) {
+                                _argv[0] = path;
+                                _argv[1] = NULL;
+                                argv = _argv;
+                        } else
+                                if (!argv[0])
+                                        argv[0] = path;
 
-                        sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+                        execv(path, argv);
+
+                        log_error("Failed to execute %s: %m", path);
+                        _exit(EXIT_FAILURE);
                 }
 
-                if (flag_file)
-                        if (access(flag_file, F_OK) < 0) {
-                                r = -errno;
-                                goto finish;
-                        }
+                log_debug("Spawned %s as %lu", path, (unsigned long) pid);
+
+                if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) {
+                        log_error("Failed to add PID to set: %s", strerror(-k));
+                        free(path);
+                }
+        }
+
+        while (!hashmap_isempty(pids)) {
+                siginfo_t si;
+                char *path;
 
-                if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
+                zero(si);
+                if (waitid(P_ALL, 0, &si, WEXITED) < 0) {
 
                         if (errno == EINTR)
                                 continue;
 
-                        r = -errno;
-                        goto finish;
-                } else if (k == 0) {
-                        r = -ETIMEDOUT;
+                        log_error("waitid() failed: %m");
                         goto finish;
                 }
 
-                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
-                        flush_fd(notify);
+                if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
+                        if (!is_clean_exit(si.si_code, si.si_status)) {
+                                if (si.si_code == CLD_EXITED)
+                                        log_error("%s exited with exit status %i.", path, si.si_status);
+                                else
+                                        log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status));
+                        } else
+                                log_debug("%s exited successfully.", path);
 
-                if (pollfd[POLL_TTY].revents == 0)
-                        continue;
+                        free(path);
+                }
+        }
 
-                if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) {
+finish:
+        if (_d)
+                closedir(_d);
 
-                        if (errno == EINTR || errno == EAGAIN)
-                                continue;
+        if (pids)
+                hashmap_free_free(pids);
+}
 
-                        r = -errno;
-                        goto finish;
+int kill_and_sigcont(pid_t pid, int sig) {
+        int r;
 
-                } else if (n == 0)
-                        break;
+        r = kill(pid, sig) < 0 ? -errno : 0;
 
-                if (c == '\n')
-                        break;
-                else if (c == 21) {
+        if (r >= 0)
+                kill(pid, SIGCONT);
 
-                        while (p > 0) {
-                                p--;
+        return r;
+}
 
-                                if (ttyfd >= 0)
-                                        loop_write(ttyfd, "\b \b", 3, false);
-                        }
+bool nulstr_contains(const char*nulstr, const char *needle) {
+        const char *i;
 
-                } else if (c == '\b' || c == 127) {
-                        if (p > 0) {
-                                p--;
+        if (!nulstr)
+                return false;
 
-                                if (ttyfd >= 0)
-                                        loop_write(ttyfd, "\b \b", 3, false);
-                        }
-                } else {
-                        passphrase[p++] = c;
+        NULSTR_FOREACH(i, nulstr)
+                if (streq(i, needle))
+                        return true;
 
-                        if (ttyfd >= 0)
-                                loop_write(ttyfd, "*", 1, false);
-                }
-        }
+        return false;
+}
 
-        if (ttyfd >= 0)
-                loop_write(ttyfd, "\n", 1, false);
+bool plymouth_running(void) {
+        return access("/run/plymouth/pid", F_OK) >= 0;
+}
 
-        passphrase[p] = 0;
+void parse_syslog_priority(char **p, int *priority) {
+        int a = 0, b = 0, c = 0;
+        int k;
 
-        if (!(*_passphrase = strdup(passphrase))) {
-                r = -ENOMEM;
-                goto finish;
-        }
+        assert(p);
+        assert(*p);
+        assert(priority);
 
-        r = 0;
+        if ((*p)[0] != '<')
+                return;
 
-finish:
-        if (notify >= 0)
-                close_nointr_nofail(notify);
+        if (!strchr(*p, '>'))
+                return;
+
+        if ((*p)[2] == '>') {
+                c = undecchar((*p)[1]);
+                k = 3;
+        } else if ((*p)[3] == '>') {
+                b = undecchar((*p)[1]);
+                c = undecchar((*p)[2]);
+                k = 4;
+        } else if ((*p)[4] == '>') {
+                a = undecchar((*p)[1]);
+                b = undecchar((*p)[2]);
+                c = undecchar((*p)[3]);
+                k = 5;
+        } else
+                return;
 
-        if (ttyfd >= 0) {
-                if (reset_tty)
-                        tcsetattr(ttyfd, TCSADRAIN, &old_termios);
+        if (a < 0 || b < 0 || c < 0)
+                return;
 
-                close_nointr_nofail(ttyfd);
-        }
+        *priority = a*100+b*10+c;
+        *p += k;
+}
+
+int have_effective_cap(int value) {
+        cap_t cap;
+        cap_flag_value_t fv;
+        int r;
+
+        if (!(cap = cap_get_proc()))
+                return -errno;
+
+        if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0)
+                r = -errno;
+        else
+                r = fv == CAP_SET;
 
+        cap_free(cap);
         return r;
 }
 
+char* strshorten(char *s, size_t l) {
+        assert(s);
+
+        if (l < strlen(s))
+                s[l] = 0;
+
+        return s;
+}
+
+static bool hostname_valid_char(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-' ||
+                c == '_' ||
+                c == '.';
+}
+
+bool hostname_is_valid(const char *s) {
+        const char *p;
+
+        if (isempty(s))
+                return false;
+
+        for (p = s; *p; p++)
+                if (!hostname_valid_char(*p))
+                        return false;
+
+        if (p-s > HOST_NAME_MAX)
+                return false;
+
+        return true;
+}
+
+char* hostname_cleanup(char *s) {
+        char *p, *d;
+
+        for (p = s, d = s; *p; p++)
+                if ((*p >= 'a' && *p <= 'z') ||
+                    (*p >= 'A' && *p <= 'Z') ||
+                    (*p >= '0' && *p <= '9') ||
+                    *p == '-' ||
+                    *p == '_' ||
+                    *p == '.')
+                        *(d++) = *p;
+
+        *d = 0;
+
+        strshorten(s, HOST_NAME_MAX);
+        return s;
+}
+
+int terminal_vhangup_fd(int fd) {
+        if (ioctl(fd, TIOCVHANGUP) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int terminal_vhangup(const char *name) {
+        int fd, r;
+
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = terminal_vhangup_fd(fd);
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int vt_disallocate(const char *name) {
+        int fd, r;
+        unsigned u;
+
+        /* Deallocate the VT if possible. If not possible
+         * (i.e. because it is the active one), at least clear it
+         * entirely (including the scrollback buffer) */
+
+        if (!startswith(name, "/dev/"))
+                return -EINVAL;
+
+        if (!tty_is_vc(name)) {
+                /* So this is not a VT. I guess we cannot deallocate
+                 * it then. But let's at least clear the screen */
+
+                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+                if (fd < 0)
+                        return fd;
+
+                loop_write(fd, "\033[H\033[2J", 7, false); /* clear screen */
+                close_nointr_nofail(fd);
+
+                return 0;
+        }
+
+        if (!startswith(name, "/dev/tty"))
+                return -EINVAL;
+
+        r = safe_atou(name+8, &u);
+        if (r < 0)
+                return r;
+
+        if (u <= 0)
+                return -EINVAL;
+
+        /* Try to deallocate */
+        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = ioctl(fd, VT_DISALLOCATE, u);
+        close_nointr_nofail(fd);
+
+        if (r >= 0)
+                return 0;
+
+        if (errno != EBUSY)
+                return -errno;
+
+        /* Couldn't deallocate, so let's clear it fully with
+         * scrollback */
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        /* Requires Linux 2.6.40 */
+        loop_write(fd, "\033[H\033[3J", 7, false); /* clear screen including scrollback */
+        close_nointr_nofail(fd);
+
+        return 0;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
@@ -3534,7 +4530,7 @@ static const char *const sigchld_code_table[] = {
 
 DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
 
-static const char *const log_facility_table[LOG_NFACILITIES] = {
+static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = {
         [LOG_FAC(LOG_KERN)] = "kern",
         [LOG_FAC(LOG_USER)] = "user",
         [LOG_FAC(LOG_MAIL)] = "mail",
@@ -3557,7 +4553,7 @@ static const char *const log_facility_table[LOG_NFACILITIES] = {
         [LOG_FAC(LOG_LOCAL7)] = "local7"
 };
 
-DEFINE_STRING_TABLE_LOOKUP(log_facility, int);
+DEFINE_STRING_TABLE_LOOKUP(log_facility_unshifted, int);
 
 static const char *const log_level_table[] = {
         [LOG_EMERG] = "emerg",
@@ -3649,3 +4645,199 @@ static const char *const signal_table[] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP(signal, int);
+
+static int file_is_conf(const struct dirent *d, const char *suffix) {
+        assert(d);
+
+        if (ignore_file(d->d_name))
+                return 0;
+
+        if (d->d_type != DT_REG &&
+            d->d_type != DT_LNK &&
+            d->d_type != DT_UNKNOWN)
+                return 0;
+
+        return endswith(d->d_name, suffix);
+}
+
+static int files_add(Hashmap *h, const char *path, const char *suffix) {
+        DIR *dir;
+        struct dirent *de;
+        int r = 0;
+
+        dir = opendir(path);
+        if (!dir) {
+                if (errno == ENOENT)
+                        return 0;
+                return -errno;
+        }
+
+        for (de = readdir(dir); de; de = readdir(dir)) {
+                char *p, *f;
+                const char *base;
+
+                if (!file_is_conf(de, suffix))
+                        continue;
+
+                if (asprintf(&p, "%s/%s", path, de->d_name) < 0) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                f = canonicalize_file_name(p);
+                if (!f) {
+                        log_error("Failed to canonicalize file name '%s': %m", p);
+                        free(p);
+                        continue;
+                }
+                free(p);
+
+                log_debug("found: %s\n", f);
+                base = f + strlen(path) + 1;
+                if (hashmap_put(h, base, f) <= 0)
+                        free(f);
+        }
+
+finish:
+        closedir(dir);
+        return r;
+}
+
+static int base_cmp(const void *a, const void *b) {
+        const char *s1, *s2;
+
+        s1 = *(char * const *)a;
+        s2 = *(char * const *)b;
+        return strcmp(file_name_from_path(s1), file_name_from_path(s2));
+}
+
+int conf_files_list(char ***strv, const char *suffix, const char *dir, ...) {
+        Hashmap *fh = NULL;
+        char **dirs = NULL;
+        char **files = NULL;
+        char **p;
+        va_list ap;
+        int r = 0;
+
+        va_start(ap, dir);
+        dirs = strv_new_ap(dir, ap);
+        va_end(ap);
+        if (!dirs) {
+                r = -ENOMEM;
+                goto finish;
+        }
+        if (!strv_path_canonicalize(dirs)) {
+                r = -ENOMEM;
+                goto finish;
+        }
+        if (!strv_uniq(dirs)) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        fh = hashmap_new(string_hash_func, string_compare_func);
+        if (!fh) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        STRV_FOREACH(p, dirs) {
+                if (files_add(fh, *p, suffix) < 0) {
+                        log_error("Failed to search for files.");
+                        r = -EINVAL;
+                        goto finish;
+                }
+        }
+
+        files = hashmap_get_strv(fh);
+        if (files == NULL) {
+                log_error("Failed to compose list of files.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        qsort(files, hashmap_size(fh), sizeof(char *), base_cmp);
+finish:
+        strv_free(dirs);
+        hashmap_free(fh);
+        *strv = files;
+        return r;
+}
+
+bool hwclock_is_localtime(void) {
+        FILE *f;
+        char line[LINE_MAX];
+        bool local = false;
+
+        /*
+         * The third line of adjtime is "UTC" or "LOCAL" or nothing.
+         *   # /etc/adjtime
+         *   0.0 0 0.0
+         *   0
+         *   UTC
+         */
+        f = fopen("/etc/adjtime", "re");
+        if (f) {
+                if (fgets(line, sizeof(line), f) &&
+                    fgets(line, sizeof(line), f) &&
+                    fgets(line, sizeof(line), f) ) {
+                            if (!strcmp(line, "LOCAL\n"))
+                                 local = true;
+                }
+                fclose(f);
+        }
+        return local;
+}
+
+int hwclock_apply_localtime_delta(void) {
+        const struct timeval *tv_null = NULL;
+        struct timeval tv;
+        struct tm *tm;
+        int minuteswest;
+        struct timezone tz;
+
+        gettimeofday(&tv, NULL);
+        tm = localtime(&tv.tv_sec);
+        minuteswest = tm->tm_gmtoff / 60;
+
+        tz.tz_minuteswest = -minuteswest;
+        tz.tz_dsttime = 0; /* DST_NONE*/
+
+        /*
+         * If the hardware clock does not run in UTC, but in local time:
+         * The very first time we set the kernel's timezone, it will warp
+         * the clock so that it runs in UTC instead of local time.
+         */
+        if (settimeofday(tv_null, &tz) < 0)
+                return -errno;
+        else
+                return minuteswest;
+}
+
+int hwclock_get_time(struct tm *tm) {
+        int fd;
+        int err = 0;
+
+        fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+        if (ioctl(fd, RTC_RD_TIME, tm) < 0)
+                err = -errno;
+        close(fd);
+
+        return err;
+}
+
+int hwclock_set_time(const struct tm *tm) {
+        int fd;
+        int err = 0;
+
+        fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+        if (ioctl(fd, RTC_SET_TIME, tm) < 0)
+                err = -errno;
+        close(fd);
+
+        return err;
+}