Skip to content

Commit a0e7606

Browse files
committed
fix(tty): preserve ctty across PTY user close
Keep the Linux pty_close driver callback for the logical last user descriptor, but clear the controlling-session association only on a true final tty release. Add a dunitest that closes the last slave descriptor while the master remains alive and verifies that the session can still reopen /dev/tty with the original SID and foreground process group. Signed-off-by: longjin <longjin@dragonos.org>
1 parent f895fcc commit a0e7606

2 files changed

Lines changed: 68 additions & 3 deletions

File tree

kernel/src/driver/tty/tty_core.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -830,15 +830,21 @@ impl TtyOperation for TtyCore {
830830
let subtype = tty.core().driver().tty_driver_sub_type();
831831
let is_pty_slave_last = cnt == 1 && subtype == TtyDriverSubType::PtySlave;
832832
let is_pty_master_last = cnt == 1 && subtype == TtyDriverSubType::PtyMaster;
833-
if !self.core().count_valid() || is_pty_slave_last || is_pty_master_last {
833+
let final_release = !self.core().count_valid();
834+
if final_release || is_pty_slave_last || is_pty_master_last {
834835
// log::debug!(
835836
// "TtyCore close: ref count: {}, tty: {}",
836837
// cnt,
837838
// tty.core().name()
838839
// );
839840
let r = self.core().tty_driver.driver_funcs().close(tty.clone());
840-
// 如果计数为0或者无效,表示tty已经关闭
841-
let _ = TtyJobCtrlManager::remove_session_tty(&tty);
841+
// Linux pty_close() runs when the last user fd closes even though
842+
// the peer keeps the tty structure count at one. The controlling
843+
// session remains attached in that case and may reopen /dev/tty;
844+
// only a true final structure release clears it here.
845+
if final_release {
846+
let _ = TtyJobCtrlManager::remove_session_tty(&tty);
847+
}
842848
return r;
843849
}
844850
Ok(())

user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,65 @@ TEST(TtyPtyHangup, SlaveCanReopenWhileMasterAliveAfterLastSlaveClose) {
632632
EXPECT_EQ('r', ch);
633633
}
634634

635+
TEST(TtyPtyHangup, ControllingSlaveCanReopenDevTtyAfterLastFdClose) {
636+
PtyPair pair = OpenRawPty();
637+
ASSERT_GE(pair.master.get(), 0);
638+
ASSERT_GE(pair.slave.get(), 0);
639+
640+
int gate[2] = {-1, -1};
641+
ASSERT_EQ(0, pipe(gate)) << strerror(errno);
642+
643+
pid_t child = fork();
644+
ASSERT_GE(child, 0) << strerror(errno);
645+
if (child == 0) {
646+
close(gate[1]);
647+
close(pair.master.release());
648+
if (setsid() < 0) {
649+
_exit(1);
650+
}
651+
if (ioctl(pair.slave.get(), TIOCSCTTY, 0) < 0) {
652+
_exit(2);
653+
}
654+
655+
char token = 0;
656+
if (read(gate[0], &token, sizeof(token)) != sizeof(token)) {
657+
_exit(3);
658+
}
659+
close(gate[0]);
660+
pair.slave.reset();
661+
662+
int tty_fd = open("/dev/tty", O_RDWR | O_NOCTTY);
663+
if (tty_fd < 0) {
664+
_exit(4);
665+
}
666+
pid_t foreground = tcgetpgrp(tty_fd);
667+
pid_t sid = -1;
668+
if (foreground != getpgrp() || ioctl(tty_fd, TIOCGSID, &sid) < 0 ||
669+
sid != getsid(0)) {
670+
close(tty_fd);
671+
_exit(5);
672+
}
673+
close(tty_fd);
674+
_exit(0);
675+
}
676+
677+
close(gate[0]);
678+
pair.slave.reset();
679+
char token = 'R';
680+
ssize_t gate_write = -1;
681+
do {
682+
gate_write = write(gate[1], &token, sizeof(token));
683+
} while (gate_write < 0 && errno == EINTR);
684+
int gate_errno = errno;
685+
close(gate[1]);
686+
ASSERT_EQ(static_cast<ssize_t>(sizeof(token)), gate_write) << strerror(gate_errno);
687+
688+
int status = 0;
689+
ASSERT_TRUE(WaitForChild(child, &status));
690+
ASSERT_TRUE(WIFEXITED(status)) << "child status=" << status;
691+
ASSERT_EQ(0, WEXITSTATUS(status)) << "child status=" << status;
692+
}
693+
635694
TEST(TtyPtyHangup, ReopenedSlaveKeepsIndexReservedAfterMasterClose) {
636695
char first_name[128] = {};
637696
PtyPair pair = OpenRawPty(first_name);

0 commit comments

Comments
 (0)