Skip to content

Commit 71bc88e

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 3d1a1d3 commit 71bc88e

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
@@ -1095,15 +1095,21 @@ impl TtyOperation for TtyCore {
10951095
let subtype = tty.core().driver().tty_driver_sub_type();
10961096
let is_pty_slave_last = cnt == 1 && subtype == TtyDriverSubType::PtySlave;
10971097
let is_pty_master_last = cnt == 1 && subtype == TtyDriverSubType::PtyMaster;
1098-
if !self.core().count_valid() || is_pty_slave_last || is_pty_master_last {
1098+
let final_release = !self.core().count_valid();
1099+
if final_release || is_pty_slave_last || is_pty_master_last {
10991100
// log::debug!(
11001101
// "TtyCore close: ref count: {}, tty: {}",
11011102
// cnt,
11021103
// tty.core().name()
11031104
// );
11041105
let r = self.core().tty_driver.driver_funcs().close(tty.clone());
1105-
// 如果计数为0或者无效,表示tty已经关闭
1106-
let _ = TtyJobCtrlManager::remove_session_tty(&tty);
1106+
// Linux pty_close() runs when the last user fd closes even though
1107+
// the peer keeps the tty structure count at one. The controlling
1108+
// session remains attached in that case and may reopen /dev/tty;
1109+
// only a true final structure release clears it here.
1110+
if final_release {
1111+
let _ = TtyJobCtrlManager::remove_session_tty(&tty);
1112+
}
11071113
return r;
11081114
}
11091115
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
@@ -633,6 +633,65 @@ TEST(TtyPtyHangup, SlaveCanReopenWhileMasterAliveAfterLastSlaveClose) {
633633
EXPECT_EQ('r', ch);
634634
}
635635

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

0 commit comments

Comments
 (0)