]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
terminal-util: add pty_open_peer() helper
authorLennart Poettering <lennart@poettering.net>
Wed, 30 Oct 2024 15:45:15 +0000 (16:45 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 30 Oct 2024 21:37:44 +0000 (22:37 +0100)
This opens a pty peer in one go, and uses the new race-free TIOCGPTPEER
ioctl() to do so – if it is available.

src/basic/terminal-util.c
src/basic/terminal-util.h
src/test/test-terminal-util.c

index 65f20594203ac9c2c6d84d030c368f6431e6ed06..4086888e5d64cd375d4bc1b7cc9d150e21e93ac1 100644 (file)
@@ -2284,3 +2284,60 @@ int terminal_is_pty_fd(int fd) {
 
         return true;
 }
+
+int pty_open_peer_racefree(int fd, int mode) {
+        assert(fd >= 0);
+
+        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
+         *
+         * This is safe to be called on TTYs from other namespaces. */
+
+        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
+
+        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
+        for (unsigned c = 0;; c++) {
+                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
+                if (peer_fd >= 0)
+                        return peer_fd;
+
+                if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) /* new ioctl() is not supported, return a clear error */
+                        return -EOPNOTSUPP;
+
+                if (errno != EIO)
+                        return -errno;
+
+                /* Max 1s in total */
+                if (c >= 20)
+                        return -EIO;
+
+                (void) usleep_safe(50 * USEC_PER_MSEC);
+        }
+}
+
+int pty_open_peer(int fd, int mode) {
+        int r;
+
+        assert(fd >= 0);
+
+        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13) if it is
+         * available. Otherwise falls back to the POSIX ptsname() + open() logic.
+         *
+         * Because of the fallback path this is not safe to be called on PTYs from other namespaces. (Because
+         * we open the peer PTY name there via a path in the file system.) */
+
+        // TODO: Remove fallback path once baseline is updated to >= 4.13, i.e. systemd v258
+
+        int peer_fd = pty_open_peer_racefree(fd, mode);
+        if (peer_fd >= 0)
+                return peer_fd;
+        if (!ERRNO_IS_NEG_NOT_SUPPORTED(peer_fd))
+                return peer_fd;
+
+        /* The racy fallback path */
+        _cleanup_free_ char *peer_path = NULL;
+        r = ptsname_malloc(fd, &peer_path);
+        if (r < 0)
+                return r;
+
+        return open_terminal(peer_path, mode);
+}
index b446e547d6078e904e3a15efc6969c36aa909648..bdc0f30afafede275000edd3a0d3cc5747f0792a 100644 (file)
@@ -144,3 +144,6 @@ int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, un
 int terminal_fix_size(int input_fd, int output_fd);
 
 int terminal_is_pty_fd(int fd);
+
+int pty_open_peer_racefree(int fd, int mode);
+int pty_open_peer(int fd, int mode);
index 9586e4e2456ab72e3d77629a046385bd76e83639..38c9f7e34ac3e6d23cb27d85db3d4c3dfe9e2d66 100644 (file)
@@ -216,14 +216,13 @@ TEST(terminal_fix_size) {
 
 TEST(terminal_is_pty_fd) {
         _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF;
-        _cleanup_free_ char *peer = NULL;
         int r;
 
-        fd1 = openpt_allocate(O_RDWR, &peer);
+        fd1 = openpt_allocate(O_RDWR, /* ret_peer_path= */ NULL);
         assert_se(fd1 >= 0);
         assert_se(terminal_is_pty_fd(fd1) > 0);
 
-        fd2 = open_terminal(peer, O_RDWR|O_CLOEXEC|O_NOCTTY);
+        fd2 = pty_open_peer(fd1, O_RDWR|O_CLOEXEC|O_NOCTTY);
         assert_se(fd2 >= 0);
         assert_se(terminal_is_pty_fd(fd2) > 0);
 
@@ -295,4 +294,24 @@ TEST(terminal_reset_defensive) {
                 log_notice_errno(r, "Failed to reset terminal: %m");
 }
 
+TEST(pty_open_peer) {
+        _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF;
+        _cleanup_free_ char *pty_path = NULL;
+
+        pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, &pty_path);
+        assert(pty_fd >= 0);
+        assert(pty_path);
+
+        peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        assert(peer_fd >= 0);
+
+        static const char x[] = { 'x', '\n' };
+        assert(write(pty_fd, x, sizeof(x)) == 2);
+
+        char buf[3];
+        assert(read(peer_fd, &buf, sizeof(buf)) == sizeof(x));
+        assert(buf[0] == x[0]);
+        assert(buf[1] == x[1]);
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);