From: Lennart Poettering Date: Wed, 30 Oct 2024 15:45:15 +0000 (+0100) Subject: terminal-util: add pty_open_peer() helper X-Git-Tag: v257-rc1~60^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc9dc71a3f1aeafaada1fd327d3a228e53ed94b6;p=thirdparty%2Fsystemd.git terminal-util: add pty_open_peer() helper This opens a pty peer in one go, and uses the new race-free TIOCGPTPEER ioctl() to do so – if it is available. --- diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 65f20594203..4086888e5d6 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -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); +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index b446e547d60..bdc0f30afaf 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -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); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 9586e4e2456..38c9f7e34ac 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -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);