]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/terminal-util.c
terminal-util: introduce vt_restore() helper
[thirdparty/systemd.git] / src / basic / terminal-util.c
index fa90a466d219ca0afab42175f8b7a940fa0099d6..aefa7cd4ef96e4c3db84f0f356833d39668751a8 100644 (file)
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include <errno.h>
 #include <fcntl.h>
@@ -37,7 +32,6 @@
 #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"
@@ -86,35 +80,42 @@ int chvt(int vt) {
 }
 
 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];
+        _cleanup_free_ char *line = NULL;
+        struct termios old_termios;
+        int r;
 
         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) {
-                new_termios = old_termios;
+                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) {
-                        size_t k;
+                        int c;
 
                         if (t != USEC_INFINITY) {
                                 if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
-                                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+                                        (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
                                         return -ETIMEDOUT;
                                 }
                         }
 
-                        k = fread(&c, 1, 1, f);
+                        errno = 0;
+                        c = fgetc(f);
+                        if (c == EOF)
+                                r = ferror(f) && errno > 0 ? -errno : -EIO;
+                        else
+                                r = 0;
 
-                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+                        (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
 
-                        if (k <= 0)
-                                return -EIO;
+                        if (r < 0)
+                                return r;
 
                         if (need_nl)
                                 *need_nl = c != '\n';
@@ -129,11 +130,13 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                         return -ETIMEDOUT;
         }
 
-        errno = 0;
-        if (!fgets(line, sizeof(line), f))
-                return errno > 0 ? -errno : -EIO;
+        /* If this is not a terminal, then read a full line instead */
 
-        truncate_nl(line);
+        r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EIO;
 
         if (strlen(line) != 1)
                 return -EBADMSG;
@@ -201,11 +204,13 @@ int ask_char(char *ret, const char *replies, const char *fmt, ...) {
 }
 
 int ask_string(char **ret, const char *text, ...) {
+        int r;
+
         assert(ret);
         assert(text);
 
         for (;;) {
-                char line[LINE_MAX];
+                _cleanup_free_ char *line = NULL;
                 va_list ap;
 
                 if (colors_enabled())
@@ -220,24 +225,14 @@ int ask_string(char **ret, const char *text, ...) {
 
                 fflush(stdout);
 
-                errno = 0;
-                if (!fgets(line, sizeof(line), stdin))
-                        return errno > 0 ? -errno : -EIO;
-
-                if (!endswith(line, "\n"))
-                        putchar('\n');
-                else {
-                        char *s;
-
-                        if (isempty(line))
-                                continue;
-
-                        truncate_nl(line);
-                        s = strdup(line);
-                        if (!s)
-                                return -ENOMEM;
+                r = read_line(stdin, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EIO;
 
-                        *ret = s;
+                if (!isempty(line)) {
+                        *ret = TAKE_PTR(line);
                         return 0;
                 }
         }
@@ -824,11 +819,11 @@ unsigned columns(void) {
         if (e)
                 (void) safe_atoi(e, &c);
 
-        if (c <= 0)
+        if (c <= 0 || c > USHRT_MAX) {
                 c = fd_columns(STDOUT_FILENO);
-
-        if (c <= 0)
-                c = 80;
+                if (c <= 0)
+                        c = 80;
+        }
 
         cached_columns = c;
         return cached_columns;
@@ -858,11 +853,11 @@ unsigned lines(void) {
         if (e)
                 (void) safe_atoi(e, &l);
 
-        if (l <= 0)
+        if (l <= 0 || l > USHRT_MAX) {
                 l = fd_lines(STDOUT_FILENO);
-
-        if (l <= 0)
-                l = 24;
+                if (l <= 0)
+                        l = 24;
+        }
 
         cached_lines = l;
         return cached_lines;
@@ -884,8 +879,17 @@ void reset_terminal_feature_caches(void) {
 }
 
 bool on_tty(void) {
+
+        /* 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. */
+
         if (cached_on_tty < 0)
-                cached_on_tty = isatty(STDOUT_FILENO) > 0;
+                cached_on_tty =
+                        isatty(STDOUT_FILENO) > 0 &&
+                        isatty(STDERR_FILENO) > 0;
 
         return cached_on_tty;
 }
@@ -1090,7 +1094,8 @@ int openpt_in_namespace(pid_t pid, int flags) {
         if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
                 return -errno;
 
-        r = safe_fork("(sd-openpt)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child);
+        r = namespace_fork("(sd-openptns)", "(sd-openpt)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
+                           pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1098,10 +1103,6 @@ int openpt_in_namespace(pid_t pid, int flags) {
 
                 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);
@@ -1117,7 +1118,7 @@ int openpt_in_namespace(pid_t pid, int flags) {
 
         pair[1] = safe_close(pair[1]);
 
-        r = wait_for_terminate_and_check("(sd-openpt)", child, 0);
+        r = wait_for_terminate_and_check("(sd-openptns)", child, 0);
         if (r < 0)
                 return r;
         if (r != EXIT_SUCCESS)
@@ -1139,7 +1140,8 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
         if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
                 return -errno;
 
-        r = safe_fork("(sd-terminal)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child);
+        r = namespace_fork("(sd-terminalns)", "(sd-terminal)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
+                           pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1147,10 +1149,6 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
 
                 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);
@@ -1163,7 +1161,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
 
         pair[1] = safe_close(pair[1]);
 
-        r = wait_for_terminate_and_check("(sd-terminal)", child, 0);
+        r = wait_for_terminate_and_check("(sd-terminalns)", child, 0);
         if (r < 0)
                 return r;
         if (r != EXIT_SUCCESS)
@@ -1274,169 +1272,36 @@ int vt_reset_keyboard(int fd) {
         return 0;
 }
 
-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;
-        int r;
-
-        f = fopen(filename, "re");
-        if (!f)
-                return -errno;
-
-        printf("%s%s# %s%s\n",
-               newline ? "\n" : "",
-               ansi_highlight_blue(),
-               filename,
-               ansi_normal());
-        fflush(stdout);
-
-        for (;;) {
-                _cleanup_free_ char *line = NULL;
+int vt_restore(int fd) {
+        static const struct vt_mode mode = {
+                .mode = VT_AUTO,
+        };
+        int r, q = 0;
 
-                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;
+        r = ioctl(fd, KDSETMODE, KD_TEXT);
+        if (r < 0)
+                q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
 
-                puts(line);
+        r = vt_reset_keyboard(fd);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
+                if (q >= 0)
+                        q = r;
         }
 
-        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);
+        r = ioctl(fd, VT_SETMODE, &mode);
+        if (r < 0) {
+                log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
+                if (q >= 0)
+                        q = -errno;
         }
 
-        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);
+        r = fchown(fd, 0, (gid_t) -1);
+        if (r < 0) {
+                log_debug_errno(errno, "Failed to chown VT, ignoring: %m");
+                if (q >= 0)
+                        q = -errno;
         }
 
-        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);
+        return q;
 }