]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/terminal-util.c
terminal-util: introduce openpt_allocate()
[thirdparty/systemd.git] / src / basic / terminal-util.c
index ea0ba442ff81a948fa59a3c391f5b334ed20755a..aa69bede6f5fb63e133f6c38ae22c73d46c7ce6f 100644 (file)
@@ -32,7 +32,7 @@
 #include "io-util.h"
 #include "log.h"
 #include "macro.h"
-#include "pager.h"
+#include "namespace-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "proc-cmdline.h"
@@ -97,7 +97,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                 new_termios.c_cc[VTIME] = 0;
 
                 if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
-                        int c;
+                        char c;
 
                         if (t != USEC_INFINITY) {
                                 if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
@@ -106,17 +106,12 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                                 }
                         }
 
-                        errno = 0;
-                        c = fgetc(f);
-                        if (c == EOF)
-                                r = ferror(f) && errno > 0 ? -errno : -EIO;
-                        else
-                                r = 0;
-
+                        r = safe_fgetc(f, &c);
                         (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
-
                         if (r < 0)
                                 return r;
+                        if (r == 0)
+                                return -EIO;
 
                         if (need_nl)
                                 *need_nl = c != '\n';
@@ -896,50 +891,39 @@ bool on_tty(void) {
 }
 
 int getttyname_malloc(int fd, char **ret) {
-        size_t l = 100;
+        char path[PATH_MAX], *c; /* PATH_MAX is counted *with* the trailing NUL byte */
         int r;
 
         assert(fd >= 0);
         assert(ret);
 
-        for (;;) {
-                char path[l];
-
-                r = ttyname_r(fd, path, sizeof(path));
-                if (r == 0) {
-                        char *c;
-
-                        c = strdup(skip_dev_prefix(path));
-                        if (!c)
-                                return -ENOMEM;
-
-                        *ret = c;
-                        return 0;
-                }
+        r = ttyname_r(fd, path, sizeof path); /* positive error */
+        assert(r >= 0);
+        if (r == ERANGE)
+                return -ENAMETOOLONG;
+        if (r > 0)
+                return -r;
 
-                if (r != ERANGE)
-                        return -r;
-
-                l *= 2;
-        }
+        c = strdup(skip_dev_prefix(path));
+        if (!c)
+                return -ENOMEM;
 
+        *ret = c;
         return 0;
 }
 
-int getttyname_harder(int fd, char **r) {
-        int k;
-        char *s = NULL;
+int getttyname_harder(int fd, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
 
-        k = getttyname_malloc(fd, &s);
-        if (k < 0)
-                return k;
+        r = getttyname_malloc(fd, &s);
+        if (r < 0)
+                return r;
 
-        if (streq(s, "tty")) {
-                free(s);
-                return get_ctty(0, NULL, r);
-        }
+        if (streq(s, "tty"))
+                return get_ctty(0, NULL, ret);
 
-        *r = s;
+        *ret = TAKE_PTR(s);
         return 0;
 }
 
@@ -980,56 +964,56 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
         return 0;
 }
 
-int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
-        char fn[STRLEN("/dev/char/") + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
-        _cleanup_free_ char *s = NULL;
-        const char *p;
+int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
+        _cleanup_free_ char *fn = NULL, *b = NULL;
         dev_t devnr;
-        int k;
-
-        assert(r);
-
-        k = get_ctty_devnr(pid, &devnr);
-        if (k < 0)
-                return k;
-
-        sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr));
+        int r;
 
-        k = readlink_malloc(fn, &s);
-        if (k < 0) {
+        r = get_ctty_devnr(pid, &devnr);
+        if (r < 0)
+                return r;
 
-                if (k != -ENOENT)
-                        return k;
+        r = device_path_make_canonical(S_IFCHR, devnr, &fn);
+        if (r < 0) {
+                if (r != -ENOENT) /* No symlink for this in /dev/char/? */
+                        return r;
 
-                /* This is an ugly hack */
                 if (major(devnr) == 136) {
+                        /* This is an ugly hack: PTY devices are not listed in /dev/char/, as they don't follow the
+                         * Linux device model. This means we have no nice way to match them up against their actual
+                         * device node. Let's hence do the check by the fixed, assigned major number. Normally we try
+                         * to avoid such fixed major/minor matches, but there appears to nother nice way to handle
+                         * this. */
+
                         if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
                                 return -ENOMEM;
                 } else {
-                        /* Probably something like the ptys which have no
-                         * symlink in /dev/char. Let's return something
-                         * vaguely useful. */
+                        /* Probably something similar to the ptys which have no symlink in /dev/char/. Let's return
+                         * something vaguely useful. */
 
-                        b = strdup(fn + 5);
-                        if (!b)
-                                return -ENOMEM;
+                        r = device_path_make_major_minor(S_IFCHR, devnr, &fn);
+                        if (r < 0)
+                                return r;
                 }
-        } else {
-                if (startswith(s, "/dev/"))
-                        p = s + 5;
-                else if (startswith(s, "../"))
-                        p = s + 3;
-                else
-                        p = s;
+        }
 
-                b = strdup(p);
-                if (!b)
-                        return -ENOMEM;
+        if (!b) {
+                const char *w;
+
+                w = path_startswith(fn, "/dev/");
+                if (w) {
+                        b = strdup(w);
+                        if (!b)
+                                return -ENOMEM;
+                } else
+                        b = TAKE_PTR(fn);
         }
 
-        *r = b;
-        if (_devnr)
-                *_devnr = devnr;
+        if (ret)
+                *ret = TAKE_PTR(b);
+
+        if (ret_devnr)
+                *ret_devnr = devnr;
 
         return 0;
 }
@@ -1057,11 +1041,42 @@ int ptsname_malloc(int fd, char **ret) {
                 }
 
                 free(c);
+
+                if (l > SIZE_MAX / 2)
+                        return -ENOMEM;
+
                 l *= 2;
         }
 }
 
-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
@@ -1080,8 +1095,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;
@@ -1095,26 +1110,18 @@ 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) {
-                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)
+                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);
@@ -1122,13 +1129,23 @@ 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)
                 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) {
@@ -1144,7 +1161,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) {
@@ -1152,10 +1170,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);
@@ -1168,7 +1182,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)
@@ -1200,7 +1214,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;
@@ -1267,195 +1281,124 @@ int vt_default_utf8(void) {
         return parse_boolean(b);
 }
 
-int vt_reset_keyboard(int fd) {
-        int kb;
+int vt_verify_kbmode(int fd) {
+        int curr_mode;
 
-        /* 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;
+        /*
+         * 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, KDSKBMODE, kb) < 0)
+        if (ioctl(fd, KDGKBMODE, &curr_mode) < 0)
                 return -errno;
 
-        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;
+        return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY;
 }
 
-int terminal_urlify(const char *url, const char *text, char **ret) {
-        char *n;
-
-        assert(url);
+int vt_reset_keyboard(int fd) {
+        int kb, r;
 
-        /* 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 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 (isempty(text))
-                text = url;
+        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 (urlify_enabled())
-                n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
-        else
-                n = strdup(text);
-        if (!n)
-                return -ENOMEM;
+        if (ioctl(fd, KDSKBMODE, kb) < 0)
+                return -errno;
 
-        *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;
+int vt_restore(int fd) {
+        static const struct vt_mode mode = {
+                .mode = VT_AUTO,
+        };
+        int r, q = 0;
 
-                n = strdup(text);
-                if (!n)
-                        return -ENOMEM;
+        if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
+                q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
 
-                *ret = n;
-                return 0;
+        r = vt_reset_keyboard(fd);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
+                if (q >= 0)
+                        q = r;
         }
 
-        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;
+        if (ioctl(fd, VT_SETMODE, &mode) < 0) {
+                log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
+                if (q >= 0)
+                        q = -errno;
         }
 
-        /* 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);
+        r = fchmod_and_chown(fd, TTY_MODE, 0, (gid_t) -1);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m");
+                if (q >= 0)
+                        q = r;
+        }
 
-        return terminal_urlify(url, text, ret);
+        return q;
 }
 
-int terminal_urlify_man(const char *page, const char *section, char **ret) {
-        const char *url, *text;
-
-        url = strjoina("man:", page, "(", section, ")");
-        text = strjoina(page, "(", section, ") man page");
-
-        return terminal_urlify(url, text, ret);
-}
+int vt_release(int fd, bool restore) {
+        assert(fd >= 0);
 
-static int cat_file(const char *filename, bool newline) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *urlified = NULL;
-        int r;
+        /* This function releases the VT by acknowledging the VT-switch signal
+         * sent by the kernel and optionally reset the VT in text and auto
+         * VT-switching modes. */
 
-        f = fopen(filename, "re");
-        if (!f)
+        if (ioctl(fd, VT_RELDISP, 1) < 0)
                 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);
-        }
+        if (restore)
+                return vt_restore(fd);
 
         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);
+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;
         }
-
-        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);
 }