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.
This commit is contained in:
Lennart Poettering
2024-10-30 22:37:44 +01:00
parent fbd2679f66
commit fc9dc71a3f
3 changed files with 82 additions and 3 deletions
+57
View 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);
}
+3
View 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);
+22 -3
View 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);