mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
When SIGWINCH causes the window size to change, signal the replica's foreground process group. This is similar to what Linux does. Also implement TIOCSCTTY/TIOCNOTTY and SIGWINCH delivery on TIOCSWINSZ for host-imported TTYs, which previously did not support controlling terminals or window size change signals. Add syscall tests in pty.cc and host_pty.cc to verify the behavior. Co-authored-by: Skywalkr-dev <[email protected]> Fixes #13317 PiperOrigin-RevId: 927482382
212 lines
5.9 KiB
C++
212 lines
5.9 KiB
C++
// Copyright 2026 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include <ctime>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "absl/strings/numbers.h"
|
|
#include "test/util/logging.h"
|
|
#include "test/util/pty_util.h"
|
|
#include "test/util/test_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
TEST(HostPtyTest, Termios2) {
|
|
// We expect a host PTY FD to be passed.
|
|
char* fd_str = getenv("TEST_HOST_PTY_FD");
|
|
ASSERT_NE(fd_str, nullptr) << "TEST_HOST_PTY_FD environment variable not set";
|
|
int fd;
|
|
ASSERT_TRUE(absl::SimpleAtoi(fd_str, &fd))
|
|
<< "Invalid TEST_HOST_PTY_FD: " << fd_str;
|
|
|
|
struct termios t;
|
|
ASSERT_THAT(ioctl(fd, TCGETS, &t), SyscallSucceeds());
|
|
|
|
struct kernel_termios2 t2 = {};
|
|
ASSERT_THAT(ioctl(fd, TCGETS2, &t2), SyscallSucceeds());
|
|
|
|
EXPECT_EQ(t.c_iflag, t2.c_iflag);
|
|
EXPECT_EQ(t.c_oflag, t2.c_oflag);
|
|
EXPECT_EQ(t.c_cflag, t2.c_cflag);
|
|
EXPECT_EQ(t.c_lflag, t2.c_lflag);
|
|
for (int i = 0; i < NCCS && i < KERNEL_NCCS; ++i) {
|
|
EXPECT_EQ(t.c_cc[i], t2.c_cc[i]);
|
|
}
|
|
|
|
// Test TCSETS2.
|
|
auto original_lflag = t2.c_lflag;
|
|
t2.c_lflag ^= ECHO;
|
|
ASSERT_THAT(ioctl(fd, TCSETS2, &t2), SyscallSucceeds());
|
|
|
|
struct kernel_termios2 t3 = {};
|
|
ASSERT_THAT(ioctl(fd, TCGETS2, &t3), SyscallSucceeds());
|
|
EXPECT_EQ(t2.c_lflag, t3.c_lflag);
|
|
|
|
// Restore original flags.
|
|
t2.c_lflag = original_lflag;
|
|
ASSERT_THAT(ioctl(fd, TCSETS2, &t2), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(HostPtyTest, SigwinchOnWindowSizeChange) {
|
|
char* fd_str = getenv("TEST_HOST_PTY_FD");
|
|
ASSERT_NE(fd_str, nullptr) << "TEST_HOST_PTY_FD environment variable not set";
|
|
int fd;
|
|
ASSERT_TRUE(absl::SimpleAtoi(fd_str, &fd))
|
|
<< "Invalid TEST_HOST_PTY_FD: " << fd_str;
|
|
|
|
int sync_pipe[2];
|
|
ASSERT_THAT(pipe(sync_pipe), SyscallSucceeds());
|
|
|
|
pid_t child = fork();
|
|
if (child == 0) {
|
|
close(sync_pipe[0]);
|
|
|
|
// Create new session and set the imported TTY as controlling terminal.
|
|
TEST_PCHECK(setsid() >= 0);
|
|
// Try TIOCSCTTY with steal=0, if it fails, try with steal=1. This is
|
|
// necessary because the host TTY might already be the controlling
|
|
// terminal of a process on the host.
|
|
if (ioctl(fd, TIOCSCTTY, 0) < 0) {
|
|
TEST_PCHECK(ioctl(fd, TIOCSCTTY, 1) >= 0);
|
|
}
|
|
|
|
// Block SIGWINCH.
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGWINCH);
|
|
sigset_t old_set;
|
|
TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, &old_set) == 0);
|
|
|
|
// Notify parent we are ready.
|
|
char c = 'r';
|
|
TEST_PCHECK(WriteFd(sync_pipe[1], &c, 1) == 1);
|
|
|
|
// Wait for SIGWINCH.
|
|
struct timespec timeout = {};
|
|
timeout.tv_sec = 10;
|
|
int sig = RetryEINTR(sigtimedwait)(&set, nullptr, &timeout);
|
|
if (sig != SIGWINCH) {
|
|
_exit(1); // Failed to receive SIGWINCH.
|
|
}
|
|
|
|
// Notify parent we got it.
|
|
c = '1';
|
|
TEST_PCHECK(WriteFd(sync_pipe[1], &c, 1) == 1);
|
|
|
|
// Now wait for another SIGWINCH.
|
|
sig = RetryEINTR(sigtimedwait)(&set, nullptr, &timeout);
|
|
if (sig != SIGWINCH) {
|
|
_exit(2); // Failed to receive second SIGWINCH.
|
|
}
|
|
|
|
// Notify parent we got it.
|
|
c = '2';
|
|
TEST_PCHECK(WriteFd(sync_pipe[1], &c, 1) == 1);
|
|
|
|
// Now expect NO SIGWINCH if we set the same size.
|
|
timeout.tv_sec = 2;
|
|
sig = RetryEINTR(sigtimedwait)(&set, nullptr, &timeout);
|
|
if (sig == SIGWINCH) {
|
|
_exit(3); // Unexpected SIGWINCH.
|
|
}
|
|
|
|
// Notify parent.
|
|
c = '3';
|
|
TEST_PCHECK(WriteFd(sync_pipe[1], &c, 1) == 1);
|
|
|
|
// Test TIOCSWINSZ from replica (self-signaling).
|
|
struct winsize ws = {};
|
|
TEST_PCHECK(ioctl(fd, TIOCGWINSZ, &ws) == 0);
|
|
ws.ws_row++;
|
|
ws.ws_col++;
|
|
TEST_PCHECK(ioctl(fd, TIOCSWINSZ, &ws) == 0);
|
|
timeout.tv_sec = 10;
|
|
sig = RetryEINTR(sigtimedwait)(&set, nullptr, &timeout);
|
|
if (sig != SIGWINCH) {
|
|
_exit(4); // Failed to receive SIGWINCH after replica TIOCSWINSZ.
|
|
}
|
|
|
|
// Notify parent.
|
|
c = '4';
|
|
TEST_PCHECK(WriteFd(sync_pipe[1], &c, 1) == 1);
|
|
|
|
close(sync_pipe[1]);
|
|
_exit(42); // Success.
|
|
}
|
|
ASSERT_GT(child, 0);
|
|
close(sync_pipe[1]);
|
|
|
|
// Wait for child to be ready.
|
|
char c;
|
|
ASSERT_THAT(ReadFd(sync_pipe[0], &c, 1), SyscallSucceedsWithValue(1));
|
|
EXPECT_EQ(c, 'r');
|
|
|
|
// Get current size.
|
|
struct winsize ws = {};
|
|
ASSERT_THAT(ioctl(fd, TIOCGWINSZ, &ws), SyscallSucceeds());
|
|
|
|
// 1. Change size.
|
|
ws.ws_row++;
|
|
ws.ws_col++;
|
|
ASSERT_THAT(ioctl(fd, TIOCSWINSZ, &ws), SyscallSucceeds());
|
|
|
|
// Wait for child.
|
|
ASSERT_THAT(ReadFd(sync_pipe[0], &c, 1), SyscallSucceedsWithValue(1));
|
|
EXPECT_EQ(c, '1');
|
|
|
|
// 2. Change size again.
|
|
ws.ws_row++;
|
|
ws.ws_col++;
|
|
ASSERT_THAT(ioctl(fd, TIOCSWINSZ, &ws), SyscallSucceeds());
|
|
|
|
// Wait for child.
|
|
ASSERT_THAT(ReadFd(sync_pipe[0], &c, 1), SyscallSucceedsWithValue(1));
|
|
EXPECT_EQ(c, '2');
|
|
|
|
// 3. Set same size.
|
|
ASSERT_THAT(ioctl(fd, TIOCSWINSZ, &ws), SyscallSucceeds());
|
|
|
|
// Wait for child.
|
|
ASSERT_THAT(ReadFd(sync_pipe[0], &c, 1), SyscallSucceedsWithValue(1));
|
|
EXPECT_EQ(c, '3');
|
|
|
|
// Wait for child to perform self-signaling test.
|
|
ASSERT_THAT(ReadFd(sync_pipe[0], &c, 1), SyscallSucceedsWithValue(1));
|
|
EXPECT_EQ(c, '4');
|
|
|
|
close(sync_pipe[0]);
|
|
|
|
// Wait for child.
|
|
int wstatus;
|
|
ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child));
|
|
ASSERT_TRUE(WIFEXITED(wstatus));
|
|
EXPECT_EQ(WEXITSTATUS(wstatus), 42);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace testing
|
|
} // namespace gvisor
|