]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/terminal-util.c
update TODO
[thirdparty/systemd.git] / src / basic / terminal-util.c
index 0b71220ec41c53a610c4401cefcb5ed46cf8ebc4..6cacde90bac6b58333368ed1cf6dfc301c338a17 100644 (file)
 #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>
@@ -83,31 +81,34 @@ int chvt(int vt) {
 int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         _cleanup_free_ char *line = NULL;
         struct termios old_termios;
-        int r;
+        int r, fd;
 
         assert(f);
         assert(ret);
 
-        /* If this is a terminal, then switch canonical mode off, so that we can read a single character */
-        if (tcgetattr(fileno(f), &old_termios) >= 0) {
+        /* If this is a terminal, then switch canonical mode off, so that we can read a single
+         * character. (Note that fmemopen() streams do not have an fd associated with them, let's handle that
+         * nicely.) */
+        fd = fileno(f);
+        if (fd >= 0 && tcgetattr(fd, &old_termios) >= 0) {
                 struct termios new_termios = old_termios;
 
                 new_termios.c_lflag &= ~ICANON;
                 new_termios.c_cc[VMIN] = 1;
                 new_termios.c_cc[VTIME] = 0;
 
-                if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+                if (tcsetattr(fd, TCSADRAIN, &new_termios) >= 0) {
                         char c;
 
                         if (t != USEC_INFINITY) {
-                                if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
-                                        (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+                                if (fd_wait_for_event(fd, POLLIN, t) <= 0) {
+                                        (void) tcsetattr(fd, TCSADRAIN, &old_termios);
                                         return -ETIMEDOUT;
                                 }
                         }
 
                         r = safe_fgetc(f, &c);
-                        (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+                        (void) tcsetattr(fd, TCSADRAIN, &old_termios);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -121,8 +122,13 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                 }
         }
 
-        if (t != USEC_INFINITY) {
-                if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
+        if (t != USEC_INFINITY && fd > 0) {
+                /* Let's wait the specified amount of time for input. When we have no fd we skip this, under
+                 * the assumption that this is an fmemopen() stream or so where waiting doesn't make sense
+                 * anyway, as the data is either already in the stream or cannot possible be placed there
+                 * while we access the stream */
+
+                if (fd_wait_for_event(fd, POLLIN, t) <= 0)
                         return -ETIMEDOUT;
         }
 
@@ -200,38 +206,33 @@ int ask_char(char *ret, const char *replies, const char *fmt, ...) {
 }
 
 int ask_string(char **ret, const char *text, ...) {
+        _cleanup_free_ char *line = NULL;
+        va_list ap;
         int r;
 
         assert(ret);
         assert(text);
 
-        for (;;) {
-                _cleanup_free_ char *line = NULL;
-                va_list ap;
+        if (colors_enabled())
+                fputs(ANSI_HIGHLIGHT, stdout);
 
-                if (colors_enabled())
-                        fputs(ANSI_HIGHLIGHT, stdout);
+        va_start(ap, text);
+        vprintf(text, ap);
+        va_end(ap);
 
-                va_start(ap, text);
-                vprintf(text, ap);
-                va_end(ap);
+        if (colors_enabled())
+                fputs(ANSI_NORMAL, stdout);
 
-                if (colors_enabled())
-                        fputs(ANSI_NORMAL, stdout);
-
-                fflush(stdout);
+        fflush(stdout);
 
-                r = read_line(stdin, LONG_LINE_MAX, &line);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return -EIO;
+        r = read_line(stdin, LONG_LINE_MAX, &line);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EIO;
 
-                if (!isempty(line)) {
-                        *ret = TAKE_PTR(line);
-                        return 0;
-                }
-        }
+        *ret = TAKE_PTR(line);
+        return 0;
 }
 
 int reset_terminal_fd(int fd, bool switch_to_text) {
@@ -526,93 +527,86 @@ int terminal_vhangup(const char *name) {
 }
 
 int vt_disallocate(const char *name) {
-        _cleanup_close_ int fd = -1;
-        const char *e, *n;
-        unsigned u;
+        const char *e;
         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) */
+         * entirely (including the scrollback buffer). */
 
         e = path_startswith(name, "/dev/");
         if (!e)
                 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 */
+        if (tty_is_vc(name)) {
+                _cleanup_close_ int fd = -1;
+                unsigned u;
+                const char *n;
 
-                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
-                if (fd < 0)
-                        return fd;
-
-                loop_write(fd,
-                           "\033[r"    /* clear scrolling region */
-                           "\033[H"    /* move home */
-                           "\033[2J",  /* clear screen */
-                           10, false);
-                return 0;
-        }
+                n = startswith(e, "tty");
+                if (!n)
+                        return -EINVAL;
 
-        n = startswith(e, "tty");
-        if (!n)
-                return -EINVAL;
-
-        r = safe_atou(n, &u);
-        if (r < 0)
-                return r;
+                r = safe_atou(n, &u);
+                if (r < 0)
+                        return r;
 
-        if (u <= 0)
-                return -EINVAL;
+                if (u <= 0)
+                        return -EINVAL;
 
-        /* Try to deallocate */
-        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
-        if (fd < 0)
-                return fd;
-
-        r = ioctl(fd, VT_DISALLOCATE, u);
-        fd = safe_close(fd);
+                /* Try to deallocate */
+                fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+                if (fd < 0)
+                        return fd;
 
-        if (r >= 0)
-                return 0;
+                r = ioctl(fd, VT_DISALLOCATE, u);
+                if (r >= 0)
+                        return 0;
+                if (errno != EBUSY)
+                        return -errno;
+        }
 
-        if (errno != EBUSY)
-                return -errno;
+        /* So this is not a VT (in which case we cannot deallocate it),
+         * or we failed to deallocate. Let's at least clear the screen. */
 
-        /* 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;
+        _cleanup_close_ int fd2 = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd2 < 0)
+                return fd2;
 
-        loop_write(fd,
-                   "\033[r"   /* clear scrolling region */
-                   "\033[H"   /* move home */
-                   "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
-                   10, false);
+        (void) loop_write(fd2,
+                          "\033[r"   /* clear scrolling region */
+                          "\033[H"   /* move home */
+                          "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
+                          10, false);
         return 0;
 }
 
 int make_console_stdio(void) {
         int fd, r;
 
-        /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
+        /* Make /dev/console the controlling terminal and stdin/stdout/stderr, if we can. If we can't use
+         * /dev/null instead. This is particularly useful if /dev/console is turned off, e.g. if console=null
+         * is specified on the kernel command line. */
 
         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");
+        if (fd < 0) {
+                log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
 
-        r = reset_terminal_fd(fd, true);
-        if (r < 0)
-                log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
+                r = make_null_stdio();
+                if (r < 0)
+                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
 
-        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");
+        } else {
+                r = reset_terminal_fd(fd, true);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
 
-        reset_terminal_feature_caches();
+                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");
+        }
 
+        reset_terminal_feature_caches();
         return 0;
 }
 
@@ -720,8 +714,7 @@ int get_kernel_consoles(char ***ret) {
 
         p = line;
         for (;;) {
-                _cleanup_free_ char *tty = NULL;
-                char *path;
+                _cleanup_free_ char *tty = NULL, *path = NULL;
 
                 r = extract_first_word(&p, &tty, NULL, 0);
                 if (r < 0)
@@ -736,17 +729,16 @@ int get_kernel_consoles(char ***ret) {
                                 return r;
                 }
 
-                path = strappend("/dev/", tty);
+                path = path_join("/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);
+                r = strv_consume(&l, TAKE_PTR(path));
                 if (r < 0)
                         return r;
         }
@@ -794,6 +786,9 @@ const char *default_term_for_tty(const char *tty) {
 int fd_columns(int fd) {
         struct winsize ws = {};
 
+        if (fd < 0)
+                return -EBADF;
+
         if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
                 return -errno;
 
@@ -828,6 +823,9 @@ unsigned columns(void) {
 int fd_lines(int fd) {
         struct winsize ws = {};
 
+        if (fd < 0)
+                return -EBADF;
+
         if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
                 return -errno;
 
@@ -1049,7 +1047,34 @@ int ptsname_malloc(int fd, char **ret) {
         }
 }
 
-int ptsname_namespace(int pty, char **ret) {
+int openpt_allocate(int flags, char **ret_slave) {
+        _cleanup_close_ int fd = -1;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (ret_slave) {
+                r = ptsname_malloc(fd, &p);
+                if (r < 0)
+                        return r;
+
+                if (!path_startswith(p, "/dev/pts/"))
+                        return -EINVAL;
+        }
+
+        if (unlockpt(fd) < 0)
+                return -errno;
+
+        if (ret_slave)
+                *ret_slave = TAKE_PTR(p);
+
+        return TAKE_FD(fd);
+}
+
+static int ptsname_namespace(int pty, char **ret) {
         int no = -1, r;
 
         /* Like ptsname(), but doesn't assume that the path is
@@ -1068,8 +1093,8 @@ 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;
+int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) {
+        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1, fd = -1;
         _cleanup_close_pair_ int pair[2] = { -1, -1 };
         pid_t child;
         int r;
@@ -1088,18 +1113,13 @@ int openpt_in_namespace(pid_t pid, int flags) {
         if (r < 0)
                 return r;
         if (r == 0) {
-                int master;
-
                 pair[0] = safe_close(pair[0]);
 
-                master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
-                if (master < 0)
-                        _exit(EXIT_FAILURE);
-
-                if (unlockpt(master) < 0)
+                fd = openpt_allocate(flags, NULL);
+                if (fd < 0)
                         _exit(EXIT_FAILURE);
 
-                if (send_one_fd(pair[1], master, 0) < 0)
+                if (send_one_fd(pair[1], fd, 0) < 0)
                         _exit(EXIT_FAILURE);
 
                 _exit(EXIT_SUCCESS);
@@ -1113,7 +1133,17 @@ int openpt_in_namespace(pid_t pid, int flags) {
         if (r != EXIT_SUCCESS)
                 return -EIO;
 
-        return receive_one_fd(pair[0], 0);
+        fd = receive_one_fd(pair[0], 0);
+        if (fd < 0)
+                return fd;
+
+        if (ret_slave) {
+                r = ptsname_namespace(fd, ret_slave);
+                if (r < 0)
+                        return r;
+        }
+
+        return TAKE_FD(fd);
 }
 
 int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
@@ -1182,7 +1212,7 @@ bool colors_enabled(void) {
          * (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. */
+         * continuously due to fear of SAK, and hence things are a bit weird. */
 
         if (cached_colors_enabled < 0) {
                 int val;
@@ -1190,6 +1220,11 @@ bool colors_enabled(void) {
                 val = getenv_bool("SYSTEMD_COLORS");
                 if (val >= 0)
                         cached_colors_enabled = val;
+
+                else if (getenv("NO_COLOR"))
+                        /* We only check for the presence of the variable; value is ignored. */
+                        cached_colors_enabled = false;
+
                 else if (getpid_cached() == 1)
                         /* PID1 outputs to the console without holding it open all the time */
                         cached_colors_enabled = !getenv_terminal_is_dumb();
@@ -1215,6 +1250,9 @@ bool dev_console_colors_enabled(void) {
         if (b >= 0)
                 return b;
 
+        if (getenv("NO_COLOR"))
+                return false;
+
         if (getenv_for_pid(1, "TERM", &s) <= 0)
                 (void) proc_cmdline_get_key("TERM", 0, &s);
 
@@ -1249,36 +1287,12 @@ int vt_default_utf8(void) {
         return parse_boolean(b);
 }
 
-int vt_verify_kbmode(int fd) {
-        int curr_mode;
-
-        /*
-         * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
-         * Otherwise we would (likely) interfere with X11's processing of the
-         * key events.
-         *
-         * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
-         */
-
-        if (ioctl(fd, KDGKBMODE, &curr_mode) < 0)
-                return -errno;
-
-        return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY;
-}
-
 int vt_reset_keyboard(int fd) {
-        int kb, r;
+        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;
 
-        r = vt_verify_kbmode(fd);
-        if (r == -EBUSY) {
-                log_debug_errno(r, "Keyboard is not in XLATE or UNICODE mode, not resetting: %m");
-                return 0;
-        } else if (r < 0)
-                return r;
-
         if (ioctl(fd, KDSKBMODE, kb) < 0)
                 return -errno;
 
@@ -1291,8 +1305,7 @@ int vt_restore(int fd) {
         };
         int r, q = 0;
 
-        r = ioctl(fd, KDSETMODE, KD_TEXT);
-        if (r < 0)
+        if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
                 q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
 
         r = vt_reset_keyboard(fd);
@@ -1302,18 +1315,17 @@ int vt_restore(int fd) {
                         q = r;
         }
 
-        r = ioctl(fd, VT_SETMODE, &mode);
-        if (r < 0) {
+        if (ioctl(fd, VT_SETMODE, &mode) < 0) {
                 log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
                 if (q >= 0)
                         q = -errno;
         }
 
-        r = fchown(fd, 0, (gid_t) -1);
+        r = fchmod_and_chown(fd, TTY_MODE, 0, (gid_t) -1);
         if (r < 0) {
-                log_debug_errno(errno, "Failed to chown VT, ignoring: %m");
+                log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m");
                 if (q >= 0)
-                        q = -errno;
+                        q = r;
         }
 
         return q;
@@ -1334,3 +1346,41 @@ int vt_release(int fd, bool restore) {
 
         return 0;
 }
+
+void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
+        /* Note that this will initialize output variables only when there's something to output.
+         * The caller must pre-initalize to "" or NULL as appropriate. */
+
+        if (priority <= LOG_ERR) {
+                if (on)
+                        *on = ANSI_HIGHLIGHT_RED;
+                if (off)
+                        *off = ANSI_NORMAL;
+                if (highlight)
+                        *highlight = ANSI_HIGHLIGHT;
+
+        } else if (priority <= LOG_WARNING) {
+                if (on)
+                        *on = ANSI_HIGHLIGHT_YELLOW;
+                if (off)
+                        *off = ANSI_NORMAL;
+                if (highlight)
+                        *highlight = ANSI_HIGHLIGHT;
+
+        } else if (priority <= LOG_NOTICE) {
+                if (on)
+                        *on = ANSI_HIGHLIGHT;
+                if (off)
+                        *off = ANSI_NORMAL;
+                if (highlight)
+                        *highlight = ANSI_HIGHLIGHT_RED;
+
+        } else if (priority >= LOG_DEBUG) {
+                if (on)
+                        *on = ANSI_GREY;
+                if (off)
+                        *off = ANSI_NORMAL;
+                if (highlight)
+                        *highlight = ANSI_HIGHLIGHT_RED;
+        }
+}