chiark / gitweb /
Prep v239: terminal-util.[hc] - Mask new 'urlify' functions, we do not need them.
[elogind.git] / src / basic / terminal-util.c
index 51abc78ce2b98cbd2a56d8b972e8fae6652a3123..f8623f9fdac777b1e1f57034c7821566af8a8b2f 100644 (file)
@@ -1,54 +1,71 @@
-/***
-  This file is part of systemd.
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
-  Copyright 2010 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/ioctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/kd.h>
+#include <linux/tiocl.h>
+#include <linux/vt.h>
+//#include <poll.h>
+//#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+//#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysmacros.h>
+#include <sys/time.h>
 #include <sys/types.h>
-#include <sys/stat.h>
+//#include <sys/utsname.h>
 #include <termios.h>
 #include <unistd.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <time.h>
-#include <assert.h>
-#include <poll.h>
-#include <linux/vt.h>
-#include <linux/tiocl.h>
-#include <linux/kd.h>
 
+#include "alloc-util.h"
+//#include "copy.h"
+//#include "def.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "log.h"
+#include "macro.h"
+//#include "pager.h"
+#include "parse-util.h"
+//#include "path-util.h"
+//#include "proc-cmdline.h"
+#include "process-util.h"
+#include "socket-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
 #include "terminal-util.h"
 #include "time-util.h"
-#include "process-util.h"
 #include "util.h"
-#include "fileio.h"
+
+/// Additional includes needed by elogind
 #include "path-util.h"
 
 static volatile unsigned cached_columns = 0;
 static volatile unsigned cached_lines = 0;
 
+static volatile int cached_on_tty = -1;
+static volatile int cached_colors_enabled = -1;
+static volatile int cached_underline_enabled = -1;
+
 int chvt(int vt) {
         _cleanup_close_ int fd;
 
+        /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go,
+         * if that's configured. */
+
         fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
         if (fd < 0)
                 return -errno;
 
-        if (vt < 0) {
+        if (vt <= 0) {
                 int tiocl[2] = {
                         TIOCL_GETKMSGREDIRECT,
                         0
@@ -66,6 +83,7 @@ int chvt(int vt) {
         return 0;
 }
 
+#if 0 /// UNNEEDED by elogind
 int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         struct termios old_termios, new_termios;
         char c, line[LINE_MAX];
@@ -112,7 +130,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
 
         errno = 0;
         if (!fgets(line, sizeof(line), f))
-                return errno ? -errno : -EIO;
+                return errno > 0 ? -errno : -EIO;
 
         truncate_nl(line);
 
@@ -126,35 +144,40 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         return 0;
 }
 
-/// UNNEEDED by elogind
-#if 0
-int ask_char(char *ret, const char *replies, const char *text, ...) {
+#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
+
+int ask_char(char *ret, const char *replies, const char *fmt, ...) {
         int r;
 
         assert(ret);
         assert(replies);
-        assert(text);
+        assert(fmt);
 
         for (;;) {
                 va_list ap;
                 char c;
                 bool need_nl = true;
 
-                if (on_tty())
-                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+                if (colors_enabled())
+                        fputs(ANSI_HIGHLIGHT, stdout);
 
-                va_start(ap, text);
-                vprintf(text, ap);
+                putchar('\r');
+
+                va_start(ap, fmt);
+                vprintf(fmt, ap);
                 va_end(ap);
 
-                if (on_tty())
-                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+                if (colors_enabled())
+                        fputs(ANSI_NORMAL, stdout);
 
                 fflush(stdout);
 
-                r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl);
+                r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, &need_nl);
                 if (r < 0) {
 
+                        if (r == -ETIMEDOUT)
+                                continue;
+
                         if (r == -EBADMSG) {
                                 puts("Bad input, please try again.");
                                 continue;
@@ -184,21 +207,21 @@ int ask_string(char **ret, const char *text, ...) {
                 char line[LINE_MAX];
                 va_list ap;
 
-                if (on_tty())
-                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+                if (colors_enabled())
+                        fputs(ANSI_HIGHLIGHT, stdout);
 
                 va_start(ap, text);
                 vprintf(text, ap);
                 va_end(ap);
 
-                if (on_tty())
-                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+                if (colors_enabled())
+                        fputs(ANSI_NORMAL, stdout);
 
                 fflush(stdout);
 
                 errno = 0;
                 if (!fgets(line, sizeof(line), stdin))
-                        return errno ? -errno : -EIO;
+                        return errno > 0 ? -errno : -EIO;
 
                 if (!endswith(line, "\n"))
                         putchar('\n');
@@ -218,7 +241,6 @@ int ask_string(char **ret, const char *text, ...) {
                 }
         }
 }
-#endif // 0
 
 int reset_terminal_fd(int fd, bool switch_to_text) {
         struct termios termios;
@@ -239,8 +261,8 @@ int reset_terminal_fd(int fd, bool switch_to_text) {
         if (switch_to_text)
                 (void) ioctl(fd, KDSETMODE, KD_TEXT);
 
-        /* Enable console unicode mode */
-        (void) ioctl(fd, KDSKBMODE, K_UNICODE);
+        /* Set default keyboard mode */
+        (void) vt_reset_keyboard(fd);
 
         if (tcgetattr(fd, &termios) < 0) {
                 r = -errno;
@@ -297,10 +319,11 @@ int reset_terminal(const char *name) {
 
         return reset_terminal_fd(fd, true);
 }
+#endif // 0
 
 int open_terminal(const char *name, int mode) {
-        int fd, r;
         unsigned c = 0;
+        int fd;
 
         /*
          * If a TTY is in the process of being closed opening it might
@@ -330,13 +353,7 @@ int open_terminal(const char *name, int mode) {
                 c++;
         }
 
-        r = isatty(fd);
-        if (r < 0) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        if (!r) {
+        if (isatty(fd) <= 0) {
                 safe_close(fd);
                 return -ENOTTY;
         }
@@ -344,46 +361,39 @@ int open_terminal(const char *name, int mode) {
         return fd;
 }
 
+#if 0 /// UNNEEDED by elogind
 int acquire_terminal(
                 const char *name,
-                bool fail,
-                bool force,
-                bool ignore_tiocstty_eperm,
+                AcquireTerminalFlags flags,
                 usec_t timeout) {
 
-        int fd = -1, notify = -1, r = 0, wd = -1;
-        usec_t ts = 0;
+        _cleanup_close_ int notify = -1, fd = -1;
+        usec_t ts = USEC_INFINITY;
+        int r, wd = -1;
 
         assert(name);
+        assert(IN_SET(flags & ~ACQUIRE_TERMINAL_PERMISSIVE, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
 
-        /* We use inotify to be notified when the tty is closed. We
-         * create the watch before checking if we can actually acquire
-         * it, so that we don't lose any event.
+        /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually
+         * acquire it, so that we don't lose any event.
          *
-         * Note: strictly speaking this actually watches for the
-         * device being closed, it does *not* really watch whether a
-         * tty loses its controlling process. However, unless some
-         * rogue process uses TIOCNOTTY on /dev/tty *after* closing
-         * its tty otherwise this will not become a problem. As long
-         * as the administrator makes sure not configure any service
-         * on the same tty as an untrusted user this should not be a
-         * problem. (Which he probably should not do anyway.) */
-
-        if (timeout != USEC_INFINITY)
-                ts = now(CLOCK_MONOTONIC);
-
-        if (!fail && !force) {
+         * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch
+         * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty
+         * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to
+         * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they
+         * probably should not do anyway.) */
+
+        if ((flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_WAIT) {
                 notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
-                if (notify < 0) {
-                        r = -errno;
-                        goto fail;
-                }
+                if (notify < 0)
+                        return -errno;
 
                 wd = inotify_add_watch(notify, name, IN_CLOSE);
-                if (wd < 0) {
-                        r = -errno;
-                        goto fail;
-                }
+                if (wd < 0)
+                        return -errno;
+
+                if (timeout != USEC_INFINITY)
+                        ts = now(CLOCK_MONOTONIC);
         }
 
         for (;;) {
@@ -395,41 +405,43 @@ int acquire_terminal(
                 if (notify >= 0) {
                         r = flush_fd(notify);
                         if (r < 0)
-                                goto fail;
+                                return r;
                 }
 
-                /* 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 */
+                /* 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 */
                 fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
                 if (fd < 0)
                         return fd;
 
-                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
-                 * if we already own the tty. */
+                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
                 assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
 
                 /* First, try to get the tty */
-                if (ioctl(fd, TIOCSCTTY, force) < 0)
-                        r = -errno;
+                r = ioctl(fd, TIOCSCTTY,
+                          (flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_FORCE) < 0 ? -errno : 0;
 
+                /* Reset signal handler to old value */
                 assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
 
-                /* Sometimes it makes sense to ignore TIOCSCTTY
-                 * returning EPERM, i.e. when very likely we already
-                 * are have this controlling terminal. */
-                if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
-                        r = 0;
+                /* Success? Exit the loop now! */
+                if (r >= 0)
+                        break;
 
-                if (r < 0 && (force || fail || r != -EPERM))
-                        goto fail;
+                /* Any failure besides -EPERM? Fail, regardless of the mode. */
+                if (r != -EPERM)
+                        return r;
 
-                if (r >= 0)
+                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
+                                                          * into a success. Note that EPERM is also returned if we
+                                                          * already are the owner of the TTY. */
                         break;
 
-                assert(!fail);
-                assert(!force);
+                if (flags != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */
+                        return r;
+
                 assert(notify >= 0);
+                assert(wd >= 0);
 
                 for (;;) {
                         union inotify_event_buffer buffer;
@@ -439,65 +451,48 @@ int acquire_terminal(
                         if (timeout != USEC_INFINITY) {
                                 usec_t n;
 
+                                assert(ts != USEC_INFINITY);
+
                                 n = now(CLOCK_MONOTONIC);
-                                if (ts + timeout < n) {
-                                        r = -ETIMEDOUT;
-                                        goto fail;
-                                }
+                                if (ts + timeout < n)
+                                        return -ETIMEDOUT;
 
-                                r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
+                                r = fd_wait_for_event(notify, POLLIN, ts + timeout - n);
                                 if (r < 0)
-                                        goto fail;
-
-                                if (r == 0) {
-                                        r = -ETIMEDOUT;
-                                        goto fail;
-                                }
+                                        return r;
+                                if (r == 0)
+                                        return -ETIMEDOUT;
                         }
 
                         l = read(notify, &buffer, sizeof(buffer));
                         if (l < 0) {
-                                if (errno == EINTR || errno == EAGAIN)
+                                if (IN_SET(errno, EINTR, EAGAIN))
                                         continue;
 
-                                r = -errno;
-                                goto fail;
+                                return -errno;
                         }
 
                         FOREACH_INOTIFY_EVENT(e, buffer, l) {
-                                if (e->wd != wd || !(e->mask & IN_CLOSE)) {
-                                        r = -EIO;
-                                        goto fail;
-                                }
+                                if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */
+                                        break;
+
+                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
+                                        return -EIO;
                         }
 
                         break;
                 }
 
-                /* We close the tty fd here since if the old session
-                 * ended our handle will be dead. It's important that
-                 * we do this after sleeping, so that we don't enter
-                 * an endless loop. */
+                /* We close the tty fd here since if the old session ended our handle will be dead. It's important that
+                 * we do this after sleeping, so that we don't enter an endless loop. */
                 fd = safe_close(fd);
         }
 
-        safe_close(notify);
-
-        r = reset_terminal_fd(fd, true);
-        if (r < 0)
-                log_warning_errno(r, "Failed to reset terminal: %m");
-
-        return fd;
-
-fail:
-        safe_close(fd);
-        safe_close(notify);
-
-        return r;
+        return TAKE_FD(fd);
 }
+#endif // 0
 
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 int release_terminal(void) {
         static const struct sigaction sa_new = {
                 .sa_handler = SIG_IGN,
@@ -506,7 +501,7 @@ int release_terminal(void) {
 
         _cleanup_close_ int fd = -1;
         struct sigaction sa_old;
-        int r = 0;
+        int r;
 
         fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
         if (fd < 0)
@@ -516,14 +511,12 @@ int release_terminal(void) {
          * by our own TIOCNOTTY */
         assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
 
-        if (ioctl(fd, TIOCNOTTY) < 0)
-                r = -errno;
+        r = ioctl(fd, TIOCNOTTY) < 0 ? -errno : 0;
 
         assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
 
         return r;
 }
-#endif // 0
 
 int terminal_vhangup_fd(int fd) {
         assert(fd >= 0);
@@ -545,14 +538,17 @@ int terminal_vhangup(const char *name) {
 }
 
 int vt_disallocate(const char *name) {
-        int fd, r;
+        _cleanup_close_ int fd = -1;
+        const char *e, *n;
         unsigned u;
+        int r;
 
         /* 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/"))
+        e = path_startswith(name, "/dev/");
+        if (!e)
                 return -EINVAL;
 
         if (!tty_is_vc(name)) {
@@ -568,15 +564,14 @@ int vt_disallocate(const char *name) {
                            "\033[H"    /* move home */
                            "\033[2J",  /* clear screen */
                            10, false);
-                safe_close(fd);
-
                 return 0;
         }
 
-        if (!startswith(name, "/dev/tty"))
+        n = startswith(e, "tty");
+        if (!n)
                 return -EINVAL;
 
-        r = safe_atou(name+8, &u);
+        r = safe_atou(n, &u);
         if (r < 0)
                 return r;
 
@@ -589,7 +584,7 @@ int vt_disallocate(const char *name) {
                 return fd;
 
         r = ioctl(fd, VT_DISALLOCATE, u);
-        safe_close(fd);
+        fd = safe_close(fd);
 
         if (r >= 0)
                 return 0;
@@ -608,128 +603,31 @@ int vt_disallocate(const char *name) {
                    "\033[H"   /* move home */
                    "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
                    10, false);
-        safe_close(fd);
-
         return 0;
 }
 
-void warn_melody(void) {
-        _cleanup_close_ int fd = -1;
-
-        fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
-        if (fd < 0)
-                return;
-
-        /* Yeah, this is synchronous. Kinda sucks. But well... */
-
-        (void) ioctl(fd, KIOCSOUND, (int)(1193180/440));
-        usleep(125*USEC_PER_MSEC);
-
-        (void) ioctl(fd, KIOCSOUND, (int)(1193180/220));
-        usleep(125*USEC_PER_MSEC);
-
-        (void) ioctl(fd, KIOCSOUND, (int)(1193180/220));
-        usleep(125*USEC_PER_MSEC);
-
-        (void) ioctl(fd, KIOCSOUND, 0);
-}
-
-/// UNNEEDED by elogind
-#if 0
 int make_console_stdio(void) {
         int fd, r;
 
         /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
 
-        fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY);
+        fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY);
         if (fd < 0)
                 return log_error_errno(fd, "Failed to acquire terminal: %m");
 
-        r = make_stdio(fd);
+        r = reset_terminal_fd(fd, true);
         if (r < 0)
-                return log_error_errno(r, "Failed to duplicate terminal fd: %m");
-
-        return 0;
-}
-#endif // 0
-
-int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
-        static const char status_indent[] = "         "; /* "[" STATUS "] " */
-        _cleanup_free_ char *s = NULL;
-        _cleanup_close_ int fd = -1;
-        struct iovec iovec[6] = {};
-        int n = 0;
-        static bool prev_ephemeral;
-
-        assert(format);
-
-        /* This is independent of logging, as status messages are
-         * optional and go exclusively to the console. */
-
-        if (vasprintf(&s, format, ap) < 0)
-                return log_oom();
-
-        fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
-                return fd;
-
-        if (ellipse) {
-                char *e;
-                size_t emax, sl;
-                int c;
+                log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
 
-                c = fd_columns(fd);
-                if (c <= 0)
-                        c = 80;
-
-                sl = status ? sizeof(status_indent)-1 : 0;
-
-                emax = c - sl - 1;
-                if (emax < 3)
-                        emax = 3;
-
-                e = ellipsize(s, emax, 50);
-                if (e) {
-                        free(s);
-                        s = e;
-                }
-        }
-
-        if (prev_ephemeral)
-                IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE);
-        prev_ephemeral = ephemeral;
-
-        if (status) {
-                if (!isempty(status)) {
-                        IOVEC_SET_STRING(iovec[n++], "[");
-                        IOVEC_SET_STRING(iovec[n++], status);
-                        IOVEC_SET_STRING(iovec[n++], "] ");
-                } else
-                        IOVEC_SET_STRING(iovec[n++], status_indent);
-        }
-
-        IOVEC_SET_STRING(iovec[n++], s);
-        if (!ephemeral)
-                IOVEC_SET_STRING(iovec[n++], "\n");
+        r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
+        if (r < 0)
+                return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
 
-        if (writev(fd, iovec, n) < 0)
-                return -errno;
+        reset_terminal_feature_caches();
 
         return 0;
 }
-
-int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) {
-        va_list ap;
-        int r;
-
-        assert(format);
-
-        va_start(ap, format);
-        r = status_vprintf(status, ellipse, ephemeral, format, ap);
-        va_end(ap);
-
-        return r;
-}
+#endif // 0
 
 bool tty_is_vc(const char *tty) {
         assert(tty);
@@ -740,10 +638,7 @@ bool tty_is_vc(const char *tty) {
 bool tty_is_console(const char *tty) {
         assert(tty);
 
-        if (startswith(tty, "/dev/"))
-                tty += 5;
-
-        return streq(tty, "console");
+        return streq(skip_dev_prefix(tty), "console");
 }
 
 int vtnr_from_tty(const char *tty) {
@@ -751,8 +646,7 @@ int vtnr_from_tty(const char *tty) {
 
         assert(tty);
 
-        if (startswith(tty, "/dev/"))
-                tty += 5;
+        tty = skip_dev_prefix(tty);
 
         if (!startswith(tty, "tty") )
                 return -EINVAL;
@@ -770,62 +664,145 @@ int vtnr_from_tty(const char *tty) {
         return i;
 }
 
-char *resolve_dev_console(char **active) {
+#if 0 /// UNNEEDED by elogind
+ int resolve_dev_console(char **ret) {
+        _cleanup_free_ char *active = NULL;
         char *tty;
+        int r;
 
-        /* Resolve where /dev/console is pointing to, if /sys is actually ours
-         * (i.e. not read-only-mounted which is a sign for container setups) */
+        assert(ret);
+
+        /* Resolve where /dev/console is pointing to, if /sys is actually ours (i.e. not read-only-mounted which is a
+         * sign for container setups) */
 
         if (path_is_read_only_fs("/sys") > 0)
-                return NULL;
+                return -ENOMEDIUM;
 
-        if (read_one_line_file("/sys/class/tty/console/active", active) < 0)
-                return NULL;
+        r = read_one_line_file("/sys/class/tty/console/active", &active);
+        if (r < 0)
+                return r;
 
-        /* If multiple log outputs are configured the last one is what
-         * /dev/console points to */
-        tty = strrchr(*active, ' ');
+        /* If multiple log outputs are configured the last one is what /dev/console points to */
+        tty = strrchr(active, ' ');
         if (tty)
                 tty++;
         else
-                tty = *active;
+                tty = active;
 
         if (streq(tty, "tty0")) {
-                char *tmp;
+                active = mfree(active);
 
                 /* Get the active VC (e.g. tty1) */
-                if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) {
-                        free(*active);
-                        tty = *active = tmp;
+                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
+                if (r < 0)
+                        return r;
+
+                tty = active;
+        }
+
+        if (tty == active)
+                *ret = TAKE_PTR(active);
+        else {
+                char *tmp;
+
+                tmp = strdup(tty);
+                if (!tmp)
+                        return -ENOMEM;
+
+                *ret = tmp;
+        }
+
+        return 0;
+}
+
+int get_kernel_consoles(char ***ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        _cleanup_free_ char *line = NULL;
+        const char *p;
+        int r;
+
+        assert(ret);
+
+        /* If /sys is mounted read-only this means we are running in some kind of container environment. In that
+         * case /sys would reflect the host system, not us, hence ignore the data we can read from it. */
+        if (path_is_read_only_fs("/sys") > 0)
+                goto fallback;
+
+        r = read_one_line_file("/sys/class/tty/console/active", &line);
+        if (r < 0)
+                return r;
+
+        p = line;
+        for (;;) {
+                _cleanup_free_ char *tty = NULL;
+                char *path;
+
+                r = extract_first_word(&p, &tty, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (streq(tty, "tty0")) {
+                        tty = mfree(tty);
+                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
+                        if (r < 0)
+                                return r;
+                }
+
+                path = strappend("/dev/", tty);
+                if (!path)
+                        return -ENOMEM;
+
+                if (access(path, F_OK) < 0) {
+                        log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
+                        free(path);
+                        continue;
                 }
+
+                r = strv_consume(&l, path);
+                if (r < 0)
+                        return r;
+        }
+
+        if (strv_isempty(l)) {
+                log_debug("No devices found for system console");
+                goto fallback;
         }
 
-        return tty;
+        *ret = TAKE_PTR(l);
+
+        return 0;
+
+fallback:
+        r = strv_extend(&l, "/dev/console");
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(l);
+
+        return 0;
 }
 
 bool tty_is_vc_resolve(const char *tty) {
-        _cleanup_free_ char *active = NULL;
+        _cleanup_free_ char *resolved = NULL;
 
         assert(tty);
 
-        if (startswith(tty, "/dev/"))
-                tty += 5;
+        tty = skip_dev_prefix(tty);
 
         if (streq(tty, "console")) {
-                tty = resolve_dev_console(&active);
-                if (!tty)
+                if (resolve_dev_console(&resolved) < 0)
                         return false;
+
+                tty = resolved;
         }
 
         return tty_is_vc(tty);
 }
 
-/// UNNEEDED by elogind
-#if 0
 const char *default_term_for_tty(const char *tty) {
-        assert(tty);
-
-        return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
+        return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220";
 }
 #endif // 0
 
@@ -845,7 +822,7 @@ unsigned columns(void) {
         const char *e;
         int c;
 
-        if (_likely_(cached_columns > 0))
+        if (cached_columns > 0)
                 return cached_columns;
 
         c = 0;
@@ -879,7 +856,7 @@ unsigned lines(void) {
         const char *e;
         int l;
 
-        if (_likely_(cached_lines > 0))
+        if (cached_lines > 0)
                 return cached_lines;
 
         l = 0;
@@ -898,55 +875,36 @@ unsigned lines(void) {
 }
 
 /* intended to be used as a SIGWINCH sighandler */
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 void columns_lines_cache_reset(int signum) {
         cached_columns = 0;
         cached_lines = 0;
 }
 #endif // 0
 
-bool on_tty(void) {
-        static int cached_on_tty = -1;
-
-        if (_unlikely_(cached_on_tty < 0))
-                cached_on_tty = isatty(STDOUT_FILENO) > 0;
+void reset_terminal_feature_caches(void) {
+        cached_columns = 0;
+        cached_lines = 0;
 
-        return cached_on_tty;
+        cached_colors_enabled = -1;
+        cached_underline_enabled = -1;
+        cached_on_tty = -1;
 }
 
-int make_stdio(int fd) {
-        int r, s, t;
-
-        assert(fd >= 0);
-
-        r = dup2(fd, STDIN_FILENO);
-        s = dup2(fd, STDOUT_FILENO);
-        t = dup2(fd, STDERR_FILENO);
-
-        if (fd >= 3)
-                safe_close(fd);
-
-        if (r < 0 || s < 0 || t < 0)
-                return -errno;
-
-        /* Explicitly unset O_CLOEXEC, since if fd was < 3, then
-         * dup2() was a NOP and the bit hence possibly set. */
-        fd_cloexec(STDIN_FILENO, false);
-        fd_cloexec(STDOUT_FILENO, false);
-        fd_cloexec(STDERR_FILENO, false);
-
-        return 0;
-}
+bool on_tty(void) {
 
-int make_null_stdio(void) {
-        int null_fd;
+        /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably
+         * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally
+         * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive
+         * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy
+         * terminal functionality when outputting stuff, even if the input is piped to us. */
 
-        null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
-        if (null_fd < 0)
-                return -errno;
+        if (cached_on_tty < 0)
+                cached_on_tty =
+                        isatty(STDOUT_FILENO) > 0 &&
+                        isatty(STDERR_FILENO) > 0;
 
-        return make_stdio(null_fd);
+        return cached_on_tty;
 }
 
 int getttyname_malloc(int fd, char **ret) {
@@ -961,11 +919,9 @@ int getttyname_malloc(int fd, char **ret) {
 
                 r = ttyname_r(fd, path, sizeof(path));
                 if (r == 0) {
-                        const char *p;
                         char *c;
 
-                        p = startswith(path, "/dev/");
-                        c = strdup(p ?: path);
+                        c = strdup(skip_dev_prefix(path));
                         if (!c)
                                 return -ENOMEM;
 
@@ -982,8 +938,6 @@ int getttyname_malloc(int fd, char **ret) {
         return 0;
 }
 
-/// UNNEEDED by elogind
-#if 0
 int getttyname_harder(int fd, char **r) {
         int k;
         char *s = NULL;
@@ -1000,7 +954,6 @@ int getttyname_harder(int fd, char **r) {
         *r = s;
         return 0;
 }
-#endif // 0
 
 int get_ctty_devnr(pid_t pid, dev_t *d) {
         int r;
@@ -1040,7 +993,7 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
 }
 
 int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
-        char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
+        char fn[STRLEN("/dev/char/") + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
         _cleanup_free_ char *s = NULL;
         const char *p;
         dev_t devnr;
@@ -1093,6 +1046,34 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
         return 0;
 }
 
+#if 0 /// UNNEEDED by elogind
+int ptsname_malloc(int fd, char **ret) {
+        size_t l = 100;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        for (;;) {
+                char *c;
+
+                c = new(char, l);
+                if (!c)
+                        return -ENOMEM;
+
+                if (ptsname_r(fd, c, l) == 0) {
+                        *ret = c;
+                        return 0;
+                }
+                if (errno != ERANGE) {
+                        free(c);
+                        return -errno;
+                }
+
+                free(c);
+                l *= 2;
+        }
+}
+
 int ptsname_namespace(int pty, char **ret) {
         int no = -1, r;
 
@@ -1111,3 +1092,381 @@ int ptsname_namespace(int pty, char **ret) {
 
         return 0;
 }
+
+int openpt_in_namespace(pid_t pid, int flags) {
+        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1;
+        _cleanup_close_pair_ int pair[2] = { -1, -1 };
+        pid_t child;
+        int r;
+
+        assert(pid > 0);
+
+        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+        if (r < 0)
+                return r;
+
+        if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+                return -errno;
+
+        r = safe_fork("(sd-openpt)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                int master;
+
+                pair[0] = safe_close(pair[0]);
+
+                r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd);
+                if (r < 0)
+                        _exit(EXIT_FAILURE);
+
+                master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
+                if (master < 0)
+                        _exit(EXIT_FAILURE);
+
+                if (unlockpt(master) < 0)
+                        _exit(EXIT_FAILURE);
+
+                if (send_one_fd(pair[1], master, 0) < 0)
+                        _exit(EXIT_FAILURE);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        pair[1] = safe_close(pair[1]);
+
+        r = wait_for_terminate_and_check("(sd-openpt)", child, 0);
+        if (r < 0)
+                return r;
+        if (r != EXIT_SUCCESS)
+                return -EIO;
+
+        return receive_one_fd(pair[0], 0);
+}
+
+int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
+        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1;
+        _cleanup_close_pair_ int pair[2] = { -1, -1 };
+        pid_t child;
+        int r;
+
+        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+        if (r < 0)
+                return r;
+
+        if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+                return -errno;
+
+        r = safe_fork("(sd-terminal)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                int master;
+
+                pair[0] = safe_close(pair[0]);
+
+                r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd);
+                if (r < 0)
+                        _exit(EXIT_FAILURE);
+
+                master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC);
+                if (master < 0)
+                        _exit(EXIT_FAILURE);
+
+                if (send_one_fd(pair[1], master, 0) < 0)
+                        _exit(EXIT_FAILURE);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        pair[1] = safe_close(pair[1]);
+
+        r = wait_for_terminate_and_check("(sd-terminal)", child, 0);
+        if (r < 0)
+                return r;
+        if (r != EXIT_SUCCESS)
+                return -EIO;
+
+        return receive_one_fd(pair[0], 0);
+}
+#endif // 0
+
+static bool getenv_terminal_is_dumb(void) {
+        const char *e;
+
+        e = getenv("TERM");
+        if (!e)
+                return true;
+
+        return streq(e, "dumb");
+}
+
+bool terminal_is_dumb(void) {
+        if (!on_tty())
+                return true;
+
+        return getenv_terminal_is_dumb();
+}
+
+bool colors_enabled(void) {
+
+        /* Returns true if colors are considered supported on our stdout. For that we check $SYSTEMD_COLORS first
+         * (which is the explicit way to turn colors on/off). If that didn't work we turn colors off unless we are on a
+         * TTY. And if we are on a TTY we turn it off if $TERM is set to "dumb". There's one special tweak though: if
+         * we are PID 1 then we do not check whether we are connected to a TTY, because we don't keep /dev/console open
+         * continously due to fear of SAK, and hence things are a bit weird. */
+
+        if (cached_colors_enabled < 0) {
+#if 0 /// elogind does not allow such forcing, and we are never init!
+                int val;
+
+                val = getenv_bool("SYSTEMD_COLORS");
+                if (val >= 0)
+                        cached_colors_enabled = val;
+                else if (getpid_cached() == 1)
+                        /* PID1 outputs to the console without holding it open all the time */
+                        cached_colors_enabled = !getenv_terminal_is_dumb();
+                else
+#endif // 0
+                        cached_colors_enabled = !terminal_is_dumb();
+        }
+
+        return cached_colors_enabled;
+}
+
+#if 0 /// UNNEEDED by elogind
+bool dev_console_colors_enabled(void) {
+        _cleanup_free_ char *s = NULL;
+        int b;
+
+        /* Returns true if we assume that color is supported on /dev/console.
+         *
+         * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that
+         * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command
+         * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular
+         * colors_enabled() operates. */
+
+        b = getenv_bool("SYSTEMD_COLORS");
+        if (b >= 0)
+                return b;
+
+        if (getenv_for_pid(1, "TERM", &s) <= 0)
+                (void) proc_cmdline_get_key("TERM", 0, &s);
+
+        return !streq_ptr(s, "dumb");
+}
+#endif // 0
+
+bool underline_enabled(void) {
+
+        if (cached_underline_enabled < 0) {
+
+                /* The Linux console doesn't support underlining, turn it off, but only there. */
+
+                if (colors_enabled())
+                        cached_underline_enabled = !streq_ptr(getenv("TERM"), "linux");
+                else
+                        cached_underline_enabled = false;
+        }
+
+        return cached_underline_enabled;
+}
+
+int vt_default_utf8(void) {
+        _cleanup_free_ char *b = NULL;
+        int r;
+
+        /* Read the default VT UTF8 setting from the kernel */
+
+        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
+        if (r < 0)
+                return r;
+
+        return parse_boolean(b);
+}
+
+int vt_reset_keyboard(int fd) {
+        int kb;
+
+        /* If we can't read the default, then default to unicode. It's 2017 after all. */
+        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
+
+        if (ioctl(fd, KDSKBMODE, kb) < 0)
+                return -errno;
+
+        return 0;
+}
+
+#if 0 /// UNNEEDED by elogind
+static bool urlify_enabled(void) {
+        static int cached_urlify_enabled = -1;
+
+        /* Unfortunately 'less' doesn't support links like this yet ðŸ˜­, hence let's disable this as long as there's a
+         * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe
+         * to assume that a link-enabled 'less' version has hit most installations. */
+
+        if (cached_urlify_enabled < 0) {
+                int val;
+
+                val = getenv_bool("SYSTEMD_URLIFY");
+                if (val >= 0)
+                        cached_urlify_enabled = val;
+                else
+                        cached_urlify_enabled = colors_enabled() && !pager_have();
+        }
+
+        return cached_urlify_enabled;
+}
+
+int terminal_urlify(const char *url, const char *text, char **ret) {
+        char *n;
+
+        assert(url);
+
+        /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See
+         * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
+
+        if (isempty(text))
+                text = url;
+
+        if (urlify_enabled())
+                n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
+        else
+                n = strdup(text);
+        if (!n)
+                return -ENOMEM;
+
+        *ret = n;
+        return 0;
+}
+
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
+        _cleanup_free_ char *absolute = NULL;
+        struct utsname u;
+        const char *url;
+        int r;
+
+        assert(path);
+
+        /* Much like terminal_urlify() above, but takes a file system path as input
+         * and turns it into a proper file:// URL first. */
+
+        if (isempty(path))
+                return -EINVAL;
+
+        if (isempty(text))
+                text = path;
+
+        if (!urlify_enabled()) {
+                char *n;
+
+                n = strdup(text);
+                if (!n)
+                        return -ENOMEM;
+
+                *ret = n;
+                return 0;
+        }
+
+        if (uname(&u) < 0)
+                return -errno;
+
+        if (!path_is_absolute(path)) {
+                r = path_make_absolute_cwd(path, &absolute);
+                if (r < 0)
+                        return r;
+
+                path = absolute;
+        }
+
+        /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
+         * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
+         * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
+         * careful with validating the strings either. */
+
+        url = strjoina("file://", u.nodename, path);
+
+        return terminal_urlify(url, text, ret);
+}
+
+static int cat_file(const char *filename, bool newline) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *urlified = NULL;
+        int r;
+
+        f = fopen(filename, "re");
+        if (!f)
+                return -errno;
+
+        r = terminal_urlify_path(filename, NULL, &urlified);
+        if (r < 0)
+                return r;
+
+        printf("%s%s# %s%s\n",
+               newline ? "\n" : "",
+               ansi_highlight_blue(),
+               urlified,
+               ansi_normal());
+        fflush(stdout);
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read \"%s\": %m", filename);
+                if (r == 0)
+                        break;
+
+                puts(line);
+        }
+
+        return 0;
+}
+
+int cat_files(const char *file, char **dropins, CatFlags flags) {
+        char **path;
+        int r;
+
+        if (file) {
+                r = cat_file(file, false);
+                if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL))
+                        printf("%s# config file %s not found%s\n",
+                               ansi_highlight_magenta(),
+                               file,
+                               ansi_normal());
+                else if (r < 0)
+                        return log_warning_errno(r, "Failed to cat %s: %m", file);
+        }
+
+        STRV_FOREACH(path, dropins) {
+                r = cat_file(*path, file || path != dropins);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to cat %s: %m", *path);
+        }
+
+        return 0;
+}
+
+void print_separator(void) {
+
+        /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting
+         * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */
+
+        if (underline_enabled()) {
+                size_t i, c;
+
+                c = columns();
+
+                flockfile(stdout);
+                fputs_unlocked(ANSI_UNDERLINE, stdout);
+
+                for (i = 0; i < c; i++)
+                        fputc_unlocked(' ', stdout);
+
+                fputs_unlocked(ANSI_NORMAL "\n\n", stdout);
+                funlockfile(stdout);
+        } else
+                fputs("\n\n", stdout);
+}
+#endif // 0