chiark / gitweb /
ask-password: add minimal plymouth password agent
[elogind.git] / src / util.c
index 7903ca07b99fe64d493948f579eb9e34f3adbd31..f5ee29897f519d39071dd665ae650640025b6d3d 100644 (file)
@@ -49,6 +49,7 @@
 #include <netinet/ip.h>
 #include <linux/kd.h>
 #include <dlfcn.h>
+#include <sys/wait.h>
 
 #include "macro.h"
 #include "util.h"
@@ -503,7 +504,7 @@ finish:
 int read_one_line_file(const char *fn, char **line) {
         FILE *f;
         int r;
-        char t[2048], *c;
+        char t[LINE_MAX], *c;
 
         assert(fn);
         assert(line);
@@ -529,6 +530,155 @@ finish:
         return r;
 }
 
+int read_full_file(const char *fn, char **contents) {
+        FILE *f;
+        int r;
+        size_t n, l;
+        char *buf = NULL;
+        struct stat st;
+
+        if (!(f = fopen(fn, "re")))
+                return -errno;
+
+        if (fstat(fileno(f), &st) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        n = st.st_size > 0 ? st.st_size : LINE_MAX;
+        l = 0;
+
+        for (;;) {
+                char *t;
+                size_t k;
+
+                if (!(t = realloc(buf, n+1))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                buf = t;
+                k = fread(buf + l, 1, n - l, f);
+
+                if (k <= 0) {
+                        if (ferror(f)) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        break;
+                }
+
+                l += k;
+                n *= 2;
+
+                /* Safety check */
+                if (n > 4*1024*1024) {
+                        r = -E2BIG;
+                        goto finish;
+                }
+        }
+
+        if (buf)
+                buf[l] = 0;
+        else if (!(buf = calloc(1, 1))) {
+                r = -errno;
+                goto finish;
+        }
+
+        *contents = buf;
+        buf = NULL;
+
+        r = 0;
+
+finish:
+        fclose(f);
+        free(buf);
+
+        return r;
+}
+
+int parse_env_file(
+                const char *fname,
+                const char *separator, ...) {
+
+        int r = 0;
+        char *contents, *p;
+
+        assert(fname);
+        assert(separator);
+
+        if ((r = read_full_file(fname, &contents)) < 0)
+                return r;
+
+        p = contents;
+        for (;;) {
+                const char *key = NULL;
+
+                p += strspn(p, separator);
+                p += strspn(p, WHITESPACE);
+
+                if (!*p)
+                        break;
+
+                if (!strchr(COMMENTS, *p)) {
+                        va_list ap;
+                        char **value;
+
+                        va_start(ap, separator);
+                        while ((key = va_arg(ap, char *))) {
+                                size_t n;
+                                char *v;
+
+                                value = va_arg(ap, char **);
+
+                                n = strlen(key);
+                                if (strncmp(p, key, n) != 0 ||
+                                    p[n] != '=')
+                                        continue;
+
+                                p += n + 1;
+                                n = strcspn(p, separator);
+
+                                if (n >= 2 &&
+                                    strchr(QUOTES, p[0]) &&
+                                    p[n-1] == p[0])
+                                        v = strndup(p+1, n-2);
+                                else
+                                        v = strndup(p, n);
+
+                                if (!v) {
+                                        r = -ENOMEM;
+                                        va_end(ap);
+                                        goto fail;
+                                }
+
+                                if (v[0] == '\0') {
+                                        /* return empty value strings as NULL */
+                                        free(v);
+                                        v = NULL;
+                                }
+
+                                free(*value);
+                                *value = v;
+
+                                p += n;
+
+                                r ++;
+                                break;
+                        }
+                        va_end(ap);
+                }
+
+                if (!key)
+                        p += strcspn(p, separator);
+        }
+
+fail:
+        free(contents);
+        return r;
+}
+
 char *truncate_nl(char *s) {
         assert(s);
 
@@ -1531,6 +1681,8 @@ bool ignore_file(const char *filename) {
         return
                 filename[0] == '.' ||
                 streq(filename, "lost+found") ||
+                streq(filename, "aquota.user") ||
+                streq(filename, "aquota.group") ||
                 endswith(filename, "~") ||
                 endswith(filename, ".rpmnew") ||
                 endswith(filename, ".rpmsave") ||
@@ -1659,6 +1811,63 @@ char *format_timestamp(char *buf, size_t l, usec_t t) {
         return buf;
 }
 
+char *format_timestamp_pretty(char *buf, size_t l, usec_t t) {
+        usec_t n, d;
+
+        n = now(CLOCK_REALTIME);
+
+        if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
+                return NULL;
+
+        d = n - t;
+
+        if (d >= USEC_PER_YEAR)
+                snprintf(buf, l, "%llu years and %llu months ago",
+                         (unsigned long long) (d / USEC_PER_YEAR),
+                         (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
+        else if (d >= USEC_PER_MONTH)
+                snprintf(buf, l, "%llu months and %llu days ago",
+                         (unsigned long long) (d / USEC_PER_MONTH),
+                         (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
+        else if (d >= USEC_PER_WEEK)
+                snprintf(buf, l, "%llu weeks and %llu days ago",
+                         (unsigned long long) (d / USEC_PER_WEEK),
+                         (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
+        else if (d >= 2*USEC_PER_DAY)
+                snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
+        else if (d >= 25*USEC_PER_HOUR)
+                snprintf(buf, l, "1 day and %lluh ago",
+                         (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
+        else if (d >= 6*USEC_PER_HOUR)
+                snprintf(buf, l, "%lluh ago",
+                         (unsigned long long) (d / USEC_PER_HOUR));
+        else if (d >= USEC_PER_HOUR)
+                snprintf(buf, l, "%lluh %llumin ago",
+                         (unsigned long long) (d / USEC_PER_HOUR),
+                         (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
+        else if (d >= 5*USEC_PER_MINUTE)
+                snprintf(buf, l, "%llumin ago",
+                         (unsigned long long) (d / USEC_PER_MINUTE));
+        else if (d >= USEC_PER_MINUTE)
+                snprintf(buf, l, "%llumin %llus ago",
+                         (unsigned long long) (d / USEC_PER_MINUTE),
+                         (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
+        else if (d >= USEC_PER_SEC)
+                snprintf(buf, l, "%llus ago",
+                         (unsigned long long) (d / USEC_PER_SEC));
+        else if (d >= USEC_PER_MSEC)
+                snprintf(buf, l, "%llums ago",
+                         (unsigned long long) (d / USEC_PER_MSEC));
+        else if (d > 0)
+                snprintf(buf, l, "%lluus ago",
+                         (unsigned long long) d);
+        else
+                snprintf(buf, l, "now");
+
+        buf[l-1] = 0;
+        return buf;
+}
+
 char *format_timespan(char *buf, size_t l, usec_t t) {
         static const struct {
                 const char *suffix;
@@ -1808,23 +2017,29 @@ int read_one_char(FILE *f, char *ret, bool *need_nl) {
 }
 
 int ask(char *ret, const char *replies, const char *text, ...) {
+        bool on_tty;
+
         assert(ret);
         assert(replies);
         assert(text);
 
+        on_tty = isatty(STDOUT_FILENO);
+
         for (;;) {
                 va_list ap;
                 char c;
                 int r;
                 bool need_nl = true;
 
-                fputs("\x1B[1m", stdout);
+                if (on_tty)
+                        fputs("\x1B[1m", stdout);
 
                 va_start(ap, text);
                 vprintf(text, ap);
                 va_end(ap);
 
-                fputs("\x1B[0m", stdout);
+                if (on_tty)
+                        fputs("\x1B[0m", stdout);
 
                 fflush(stdout);
 
@@ -1860,9 +2075,9 @@ int reset_terminal(int fd) {
 
         assert(fd >= 0);
 
-        /* First, unlock termios */
-        zero(termios);
-        ioctl(fd, TIOCSLCKTRMIOS, &termios);
+        /* We leave locked terminal attributes untouched, so that
+         * Plymouth may set whatever it wants to set, and we don't
+         * interfere with that. */
 
         /* Disable exclusive mode, just in case */
         ioctl(fd, TIOCNXCL);
@@ -2034,26 +2249,34 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst
                 assert(notify >= 0);
 
                 for (;;) {
-                        struct inotify_event e;
+                        uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
                         ssize_t l;
+                        struct inotify_event *e;
 
-                        if ((l = read(notify, &e, sizeof(e))) != sizeof(e)) {
+                        if ((l = read(notify, &inotify_buffer, sizeof(inotify_buffer))) < 0) {
 
-                                if (l < 0) {
+                                if (errno == EINTR)
+                                        continue;
 
-                                        if (errno == EINTR)
-                                                continue;
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        e = (struct inotify_event*) inotify_buffer;
 
-                                        r = -errno;
-                                } else
+                        while (l > 0) {
+                                size_t step;
+
+                                if (e->wd != wd || !(e->mask & IN_CLOSE)) {
                                         r = -EIO;
+                                        goto fail;
+                                }
 
-                                goto fail;
-                        }
+                                step = sizeof(struct inotify_event) + e->len;
+                                assert(step <= (size_t) l);
 
-                        if (e.wd != wd || !(e.mask & IN_CLOSE)) {
-                                r = -EIO;
-                                goto fail;
+                                e = (struct inotify_event*) ((uint8_t*) e + step);
+                                l -= step;
                         }
 
                         break;
@@ -2778,6 +3001,46 @@ void status_welcome(void) {
 
         status_printf("Welcome to \x1B[0;32m%s\x1B[0m!\n", r); /* Green for SUSE */
         free(r);
+
+#elif defined(TARGET_GENTOO)
+        char *r;
+
+        if (read_one_line_file("/etc/gentoo-release", &r) < 0)
+                return;
+
+        truncate_nl(r);
+
+        status_printf("Welcome to \x1B[1;34m%s\x1B[0m!\n", r); /* Light Blue for Gentoo */
+
+        free(r);
+
+#elif defined(TARGET_DEBIAN)
+        char *r;
+
+        if (read_one_line_file("/etc/debian_version", &r) < 0)
+                return;
+
+        truncate_nl(r);
+
+        status_printf("Welcome to Debian \x1B[1;31m%s\x1B[0m!\n", r); /* Light Red for Debian */
+
+        free(r);
+#elif defined(TARGET_UBUNTU)
+        char *desc = NULL;
+        char *codename = NULL;
+
+        if (parse_env_file("/etc/lsb-release", NEWLINE,
+                "DISTRIB_DESCRIPTION", &desc,
+                "DISTRIB_CODENAME", &codename, NULL) < 0)
+                return;
+        if (desc && codename)
+                /* Light Red for Ubuntu */
+                status_printf("Welcome to \x1B[1;31m%s\x1B[0m (%s)\n",
+                        desc, codename);
+        free(desc);
+        free(codename);
+#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
@@ -2931,7 +3194,7 @@ int columns(void) {
                 struct winsize ws;
                 zero(ws);
 
-                if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
+                if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
                         parsed_columns = ws.ws_col;
         }
 
@@ -3004,6 +3267,267 @@ int touch(const char *path) {
         return 0;
 }
 
+char *unquote(const char *s, const char* quotes) {
+        size_t l;
+        assert(s);
+
+        if ((l = strlen(s)) < 2)
+                return strdup(s);
+
+        if (strchr(quotes, s[0]) && s[l-1] == s[0])
+                return strndup(s+1, l-2);
+
+        return strdup(s);
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+        assert(pid >= 1);
+        assert(status);
+
+        for (;;) {
+                zero(*status);
+
+                if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                return 0;
+        }
+}
+
+int wait_for_terminate_and_warn(const char *name, pid_t pid) {
+        int r;
+        siginfo_t status;
+
+        assert(name);
+        assert(pid > 1);
+
+        if ((r = wait_for_terminate(pid, &status)) < 0) {
+                log_warning("Failed to wait for %s: %s", name, strerror(-r));
+                return r;
+        }
+
+        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;
+                }
+
+                log_debug("%s succeeded.", name);
+                return 0;
+
+        } else if (status.si_code == CLD_KILLED ||
+                   status.si_code == CLD_DUMPED) {
+
+                log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+                return -EPROTO;
+        }
+
+        log_warning("%s failed due to unknown reason.", name);
+        return -EPROTO;
+
+}
+
+void freeze(void) {
+        for (;;)
+                pause();
+}
+
+bool null_or_empty(struct stat *st) {
+        assert(st);
+
+        if (S_ISREG(st->st_mode) && st->st_size <= 0)
+                return true;
+
+        if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+                return true;
+
+        return false;
+}
+
+DIR *xopendirat(int fd, const char *name) {
+        return fdopendir(openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+}
+
+int signal_from_string_try_harder(const char *s) {
+        int signo;
+        assert(s);
+
+        if ((signo = signal_from_string(s)) <= 0)
+                if (startswith(s, "SIG"))
+                        return signal_from_string(s+3);
+
+        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
+        };
+
+        assert(message);
+        assert(_passphrase);
+
+        if (flag_file) {
+                if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) {
+
+                if (tcgetattr(ttyfd, &old_termios) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                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);
+
+                new_termios = old_termios;
+                new_termios.c_lflag &= ~(ICANON|ECHO);
+                new_termios.c_cc[VMIN] = 1;
+                new_termios.c_cc[VTIME] = 0;
+
+                if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                reset_tty = true;
+        }
+
+        zero(pollfd);
+
+        pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
+        pollfd[POLL_TTY].events = POLLIN;
+        pollfd[POLL_INOTIFY].fd = notify;
+        pollfd[POLL_INOTIFY].events = POLLIN;
+
+        for (;;) {
+                char c;
+                int sleep_for = -1, k;
+                ssize_t n;
+
+                if (until > 0) {
+                        usec_t y;
+
+                        y = now(CLOCK_MONOTONIC);
+
+                        if (y > until) {
+                                r = -ETIMEDOUT;
+                                goto finish;
+                        }
+
+                        sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+                }
+
+                if (flag_file)
+                        if (access(flag_file, F_OK) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                } else if (k == 0) {
+                        r = -ETIMEDOUT;
+                        goto finish;
+                }
+
+                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+                        flush_fd(notify);
+
+                if (pollfd[POLL_TTY].revents == 0)
+                        continue;
+
+                if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+
+                } else if (n == 0)
+                        break;
+
+                if (c == '\n')
+                        break;
+                else if (c == 21) {
+
+                        while (p > 0) {
+                                p--;
+
+                                if (ttyfd >= 0)
+                                        loop_write(ttyfd, "\b \b", 3, false);
+                        }
+
+                } else if (c == '\b' || c == 127) {
+                        if (p > 0) {
+                                p--;
+
+                                if (ttyfd >= 0)
+                                        loop_write(ttyfd, "\b \b", 3, false);
+                        }
+                } else {
+                        passphrase[p++] = c;
+
+                        if (ttyfd >= 0)
+                                loop_write(ttyfd, "*", 1, false);
+                }
+        }
+
+        if (ttyfd >= 0)
+                loop_write(ttyfd, "\n", 1, false);
+
+        passphrase[p] = 0;
+
+        if (!(*_passphrase = strdup(passphrase))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        if (ttyfd >= 0) {
+                if (reset_tty)
+                        tcsetattr(ttyfd, TCSADRAIN, &old_termios);
+
+                close_nointr_nofail(ttyfd);
+        }
+
+        return r;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
@@ -3118,7 +3642,9 @@ static const char *const signal_table[] = {
         [SIGPIPE] = "PIPE",
         [SIGALRM] = "ALRM",
         [SIGTERM] = "TERM",
-        [SIGSTKFLT] = "STKFLT",
+#ifdef SIGSTKFLT
+        [SIGSTKFLT] = "STKFLT",  /* Linux on SPARC doesn't know SIGSTKFLT */
+#endif
         [SIGCHLD] = "CHLD",
         [SIGCONT] = "CONT",
         [SIGSTOP] = "STOP",