1#![recursion_limit = "512"]
6
7use starnix_core::device::DeviceMode;
8use starnix_core::device::kobject::DeviceMetadata;
9pub use starnix_core::device::terminal::DEVPTS_COUNT;
10use starnix_core::device::terminal::{Terminal, TerminalSide, TtyState, get_device_type_for_pts};
11use starnix_core::fs::sysfs::build_device_directory;
12use starnix_core::mm::MemoryAccessorExt;
13use starnix_core::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, Waiter};
14use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
15use starnix_core::vfs::pseudo::vec_directory::{VecDirectory, VecDirectoryEntry};
16use starnix_core::vfs::{
17 CacheMode, DirEntry, DirectoryEntryType, FdFlags, FileHandle, FileObject, FileObjectState,
18 FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode, FsNodeHandle,
19 FsNodeInfo, FsNodeOps, FsStr, FsString, LookupContext, MountInfo, NamespaceNode, SpecialNode,
20 SymlinkMode,
21};
22use starnix_core::{fileops_impl_nonseekable, fileops_impl_noop_sync, fs_node_impl_dir_readonly};
23use starnix_logging::track_stub;
24use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
25use starnix_types::vfs::default_statfs;
26use starnix_uapi::auth::FsCred;
27use starnix_uapi::device_id::{DeviceId, TTY_ALT_MAJOR};
28use starnix_uapi::errors::Errno;
29use starnix_uapi::file_mode::mode;
30use starnix_uapi::mount_flags::MountFlags;
31use starnix_uapi::open_flags::OpenFlags;
32use starnix_uapi::signals::SIGWINCH;
33use starnix_uapi::termios::{
34 into_termio, into_termios2, termios_from_termios2, termios2_from_termios,
35};
36use starnix_uapi::user_address::{UserAddress, UserRef};
37use starnix_uapi::vfs::FdEvents;
38use starnix_uapi::{
39 DEVPTS_SUPER_MAGIC, FIOASYNC, FIONREAD, FIOQSIZE, TCFLSH, TCGETA, TCGETS, TCGETS2, TCGETX,
40 TCSBRK, TCSBRKP, TCSETA, TCSETAF, TCSETAW, TCSETS, TCSETS2, TCSETSF, TCSETSF2, TCSETSW,
41 TCSETSW2, TCSETX, TCSETXF, TCSETXW, TCXONC, TIOCCBRK, TIOCCONS, TIOCEXCL, TIOCGETD,
42 TIOCGICOUNT, TIOCGLCKTRMIOS, TIOCGPGRP, TIOCGPTLCK, TIOCGPTN, TIOCGPTPEER, TIOCGRS485,
43 TIOCGSERIAL, TIOCGSID, TIOCGSOFTCAR, TIOCGWINSZ, TIOCLINUX, TIOCMBIC, TIOCMBIS, TIOCMGET,
44 TIOCMIWAIT, TIOCMSET, TIOCNOTTY, TIOCNXCL, TIOCOUTQ, TIOCPKT, TIOCSBRK, TIOCSCTTY,
45 TIOCSERCONFIG, TIOCSERGETLSR, TIOCSERGETMULTI, TIOCSERGSTRUCT, TIOCSERGWILD, TIOCSERSETMULTI,
46 TIOCSERSWILD, TIOCSETD, TIOCSLCKTRMIOS, TIOCSPGRP, TIOCSPTLCK, TIOCSRS485, TIOCSSERIAL,
47 TIOCSSOFTCAR, TIOCSTI, TIOCSWINSZ, TIOCVHANGUP, errno, error, gid_t, ino_t, pid_t, statfs,
48 uapi, uid_t,
49};
50use std::sync::{Arc, Weak};
51
52const DEVPTS_FIRST_MAJOR: u32 = 136;
54const DEVPTS_MAJOR_COUNT: u32 = 4;
55const BLOCK_SIZE: usize = 1024;
58
59const ROOT_NODE_ID: ino_t = 1;
61const PTMX_NODE_ID: ino_t = 2;
62const FIRST_PTS_NODE_ID: ino_t = 3;
63
64pub fn dev_pts_fs(
65 current_task: &CurrentTask,
66 options: FileSystemOptions,
67) -> Result<FileSystemHandle, Errno> {
68 new_pts_fs(¤t_task.kernel(), options)
69}
70
71pub fn new_pts_fs(kernel: &Kernel, options: FileSystemOptions) -> Result<FileSystemHandle, Errno> {
72 let state = if options.params.get(b"newinstance").is_some() {
73 Arc::new(TtyState::default())
74 } else {
75 kernel.expando.get::<TtyState>()
76 };
77
78 new_pts_fs_with_state(kernel, options, state)
79}
80
81pub fn new_pts_fs_with_state(
82 kernel: &Kernel,
83 options: FileSystemOptions,
84 state: Arc<TtyState>,
85) -> Result<FileSystemHandle, Errno> {
86 let parse_octal = |m: &str| u32::from_str_radix(m, 8);
87 let uid = options.params.get_as::<uid_t>(b"uid")?;
88 let gid = options.params.get_as::<gid_t>(b"gid")?;
89 let mode = options.params.get_with(b"mode", parse_octal)?.unwrap_or(0o600);
90 let ptmxmode = options.params.get_with(b"ptmxmode", parse_octal)?.unwrap_or(0);
91
92 let dev_pts_fs = DevPtsFs { state: state.clone(), uid, gid, mode, ptmxmode };
93
94 let fs = FileSystem::new(kernel, CacheMode::Uncached, dev_pts_fs, options)
95 .expect("devpts filesystem constructed with valid options");
96 fs.create_root(ROOT_NODE_ID, DevPtsRootDir { state });
97 Ok(fs)
98}
99
100pub fn create_main_and_replica(
106 current_task: &CurrentTask,
107 window_size: uapi::winsize,
108) -> Result<(FileHandle, FileHandle), Errno> {
109 let pty_file = current_task.open_file("/dev/ptmx".into(), OpenFlags::RDWR)?;
110 let pty = pty_file.downcast_file::<DevPtmxFile>().ok_or_else(|| errno!(ENOTTY))?;
111 {
112 let mut terminal = pty.terminal.write();
113 terminal.line_discipline.locked = false;
114 terminal.line_discipline.window_size = window_size;
115 }
116 let pts_path = FsString::from(format!("/dev/pts/{}", pty.terminal.id));
117 let pts_file = current_task.open_file(pts_path.as_ref(), OpenFlags::RDWR)?;
118 Ok((pty_file, pts_file))
119}
120
121pub fn tty_device_init(kernel: &Kernel) -> Result<(), Errno> {
122 let registry = &kernel.device_registry;
123
124 for n in 0..DEVPTS_MAJOR_COUNT {
126 registry
127 .register_major(
128 "pts".into(),
129 DeviceMode::Char,
130 DEVPTS_FIRST_MAJOR + n,
131 open_dev_pts_device,
132 )
133 .expect("can register pts{n} device");
134 }
135
136 kernel
138 .device_registry
139 .register_major("/dev/tty".into(), DeviceMode::Char, TTY_ALT_MAJOR, open_dev_pts_device)
140 .expect("can register tty device");
141
142 let tty_class = registry.objects.tty_class();
143 registry.add_major_device(
144 kernel,
145 "tty".into(),
146 DeviceMetadata::new("tty".into(), DeviceId::TTY, DeviceMode::Char),
147 None,
148 tty_class.clone(),
149 build_device_directory,
150 );
151 registry.add_major_device(
152 kernel,
153 "ptmx".into(),
154 DeviceMetadata::new("ptmx".into(), DeviceId::PTMX, DeviceMode::Char),
155 None,
156 tty_class,
157 build_device_directory,
158 );
159 Ok(())
160}
161
162struct DevPtsFs {
163 state: Arc<TtyState>,
164 uid: Option<uid_t>,
165 gid: Option<gid_t>,
166 mode: u32,
167 ptmxmode: u32,
168}
169
170impl FileSystemOps for DevPtsFs {
171 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
172 Ok(default_statfs(DEVPTS_SUPER_MAGIC))
173 }
174 fn name(&self) -> &'static FsStr {
175 "devpts".into()
176 }
177
178 fn uses_external_node_ids(&self) -> bool {
179 false
180 }
181}
182
183impl DevPtsFs {
184 fn pty_creds_for(&self, current_task: &CurrentTask) -> FsCred {
185 let creds = current_task.current_creds();
186 let uid = self.uid.unwrap_or_else(|| creds.uid);
187 let gid = self.gid.unwrap_or_else(|| creds.gid);
188 FsCred { uid, gid }
189 }
190}
191
192struct DevPtsRootDir {
193 state: Arc<TtyState>,
194}
195
196impl FsNodeOps for DevPtsRootDir {
197 fs_node_impl_dir_readonly!();
198
199 fn create_file_ops(
200 &self,
201 _node: &FsNode,
202 _current_task: &CurrentTask,
203 _flags: OpenFlags,
204 ) -> Result<Box<dyn FileOps>, Errno> {
205 let mut result = vec![];
206 result.push(VecDirectoryEntry {
207 entry_type: DirectoryEntryType::CHR,
208 name: "ptmx".into(),
209 inode: Some(PTMX_NODE_ID),
210 });
211 for (id, terminal) in self.state.terminals.read().iter() {
212 if let Some(terminal) = terminal.upgrade() {
213 if !terminal.read().is_main_closed() {
214 result.push(VecDirectoryEntry {
215 entry_type: DirectoryEntryType::CHR,
216 name: format!("{id}").into(),
217 inode: Some((*id as ino_t) + FIRST_PTS_NODE_ID),
218 });
219 }
220 }
221 }
222 Ok(VecDirectory::new_file(result))
223 }
224
225 fn lookup(
226 &self,
227 entry: &DirEntry,
228 _current_task: &CurrentTask,
229 name: &FsStr,
230 ) -> Result<FsNodeHandle, Errno> {
231 let fs = entry.node.fs();
232 let devptsfs =
233 fs.downcast_ops::<DevPtsFs>().expect("DevPts should only handle `DevPtsFs`s");
234 let name = std::str::from_utf8(name).map_err(|_| errno!(ENOENT))?;
235 if name == "ptmx" {
236 let mut info = FsNodeInfo::new(mode!(IFCHR, devptsfs.ptmxmode), FsCred::root());
237 info.rdev = DeviceId::PTMX;
238 info.blksize = BLOCK_SIZE;
239 let node = fs.create_node(PTMX_NODE_ID, SpecialNode, info);
240 return Ok(node);
241 }
242 if let Ok(id) = name.parse::<u32>() {
243 let terminal = self.state.terminals.read().get(&id).and_then(Weak::upgrade);
244 if let Some(terminal) = terminal {
245 if !terminal.read().is_main_closed() {
246 let ino = (id as ino_t) + FIRST_PTS_NODE_ID;
247 let mut info =
248 FsNodeInfo::new(mode!(IFCHR, devptsfs.mode), terminal.fscred.clone());
249 info.rdev = get_device_type_for_pts(id);
250 info.blksize = BLOCK_SIZE;
251 let node = fs.create_node(ino, SpecialNode, info);
252 return Ok(node);
253 }
254 }
255 }
256 error!(ENOENT)
257 }
258}
259
260fn open_dev_pts_device(
261 current_task: &CurrentTask,
262 id: DeviceId,
263 node: &NamespaceNode,
264 flags: OpenFlags,
265) -> Result<Box<dyn FileOps>, Errno> {
266 match id {
267 DeviceId::PTMX => {
269 let fs = node.entry.node.fs();
270 let Some(devpts_fs) = fs.downcast_ops::<DevPtsFs>() else {
271 let parent = node.parent().ok_or_else(|| errno!(EINVAL))?;
274 let mut lookup_context = LookupContext::new(SymlinkMode::Follow);
275 let ptmx_node =
276 current_task.lookup_path(&mut lookup_context, parent, "pts/ptmx".into())?;
277 return open_dev_pts_device(current_task, id, &ptmx_node, flags);
278 };
279
280 let creds = devpts_fs.pty_creds_for(current_task);
281 let terminal = devpts_fs.state.get_next_terminal(fs.root().clone(), creds)?;
282 let name = FsString::from(terminal.id.to_string());
283 let replica_dir_entry =
284 fs.root().component_lookup(current_task, &MountInfo::detached(), name.as_ref())?;
285 let replica_node =
286 NamespaceNode { mount: node.mount.clone(), entry: replica_dir_entry };
287 Ok(Box::new(DevPtmxFile::new(terminal, Some(replica_node))))
288 }
289 DeviceId::TTY => {
291 let controlling_terminal = current_task.thread_group().controlling_terminal();
292 if let Some(controlling_terminal) = controlling_terminal {
293 match controlling_terminal.side {
294 TerminalSide::Main => {
295 Ok(Box::new(DevPtmxFile::new(controlling_terminal.terminal, None)))
296 }
297 TerminalSide::Replica => {
298 Ok(Box::new(TtyFile::new(controlling_terminal.terminal)))
299 }
300 }
301 } else {
302 error!(ENXIO)
303 }
304 }
305 _ if id.major() < DEVPTS_FIRST_MAJOR
306 || id.major() >= DEVPTS_FIRST_MAJOR + DEVPTS_MAJOR_COUNT =>
307 {
308 error!(ENODEV)
309 }
310 _ => {
312 let fs = node.entry.node.fs();
313 let Some(devpts_fs) = fs.downcast_ops::<DevPtsFs>() else {
314 return error!(ENOTSUP);
315 };
316 let pts_id = (id.major() - DEVPTS_FIRST_MAJOR) * 256 + id.minor();
317 let terminal = devpts_fs
318 .state
319 .terminals
320 .read()
321 .get(&pts_id)
322 .and_then(Weak::upgrade)
323 .ok_or_else(|| errno!(EIO))?;
324 if terminal.read().line_discipline.locked {
325 return error!(EIO);
326 }
327 if !flags.contains(OpenFlags::NOCTTY) {
328 let _ = current_task.thread_group().set_controlling_terminal(
331 current_task,
332 &terminal,
333 TerminalSide::Replica,
334 false, flags.can_read(),
336 );
337 }
338 Ok(Box::new(TtyFile::new(terminal)))
339 }
340 }
341}
342
343struct DevPtmxFile {
344 terminal: Arc<Terminal>,
345
346 replica_node: Option<NamespaceNode>,
351}
352
353impl DevPtmxFile {
354 pub fn new(terminal: Arc<Terminal>, replica_node: Option<NamespaceNode>) -> Self {
355 terminal.main_open();
356 Self { terminal, replica_node }
357 }
358}
359
360impl FileOps for DevPtmxFile {
361 fileops_impl_nonseekable!();
362 fileops_impl_noop_sync!();
363
364 fn close(self: Box<Self>, _file: &FileObjectState, _current_task: &CurrentTask) {
365 self.terminal.disassociate_controlling_session();
366 self.terminal.main_close();
367 }
368
369 fn read(
370 &self,
371 file: &FileObject,
372 current_task: &CurrentTask,
373 offset: usize,
374 data: &mut dyn OutputBuffer,
375 ) -> Result<usize, Errno> {
376 debug_assert!(offset == 0);
377 file.blocking_op(current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, || {
378 self.terminal.main_read(data)
379 })
380 }
381
382 fn write(
383 &self,
384 file: &FileObject,
385 current_task: &CurrentTask,
386 offset: usize,
387 data: &mut dyn InputBuffer,
388 ) -> Result<usize, Errno> {
389 debug_assert!(offset == 0);
390 file.blocking_op(current_task, FdEvents::POLLOUT | FdEvents::POLLHUP, None, || {
391 self.terminal.main_write(data)
392 })
393 }
394
395 fn wait_async(
396 &self,
397 _file: &FileObject,
398 _current_task: &CurrentTask,
399 waiter: &Waiter,
400 events: FdEvents,
401 handler: EventHandler,
402 ) -> Option<WaitCanceler> {
403 Some(self.terminal.main_wait_async(waiter, events, handler))
404 }
405
406 fn query_events(
407 &self,
408 _file: &FileObject,
409 _current_task: &CurrentTask,
410 ) -> Result<FdEvents, Errno> {
411 Ok(self.terminal.main_query_events())
412 }
413
414 fn ioctl(
415 &self,
416 file: &FileObject,
417 current_task: &CurrentTask,
418 request: u32,
419 arg: SyscallArg,
420 ) -> Result<SyscallResult, Errno> {
421 let user_addr = UserAddress::from(arg);
422 match request {
423 TIOCGPTN => {
424 let value: u32 = self.terminal.id;
426 current_task.write_object(UserRef::<u32>::new(user_addr), &value)?;
427 Ok(SUCCESS)
428 }
429 TIOCGPTLCK => {
430 let value = i32::from(self.terminal.read().line_discipline.locked);
432 current_task.write_object(UserRef::<i32>::new(user_addr), &value)?;
433 Ok(SUCCESS)
434 }
435 TIOCSPTLCK => {
436 let value = current_task.read_object(UserRef::<i32>::new(user_addr))?;
438 self.terminal.write().line_discipline.locked = value != 0;
439 Ok(SUCCESS)
440 }
441 TIOCGPTPEER => {
442 let Some(replica_node) = &self.replica_node else {
443 return error!(ENOTTY);
444 };
445
446 if replica_node.mount.flags().contains(MountFlags::NODEV) {
447 return error!(EACCES);
448 }
449
450 let flags = OpenFlags::from_bits_truncate(u32::from(arg));
451 let replica_file = replica_node.open(current_task, flags)?;
452
453 let fd_flags = if flags.contains(OpenFlags::CLOEXEC) {
454 FdFlags::CLOEXEC
455 } else {
456 FdFlags::empty()
457 };
458 let fd = current_task.add_file(replica_file, fd_flags)?;
459 Ok(fd.into())
460 }
461 _ => shared_ioctl(&self.terminal, true, file, current_task, request, arg),
462 }
463 }
464}
465
466pub struct TtyFile {
467 terminal: Arc<Terminal>,
468}
469
470impl TtyFile {
471 pub fn new(terminal: Arc<Terminal>) -> Self {
472 terminal.replica_open();
473 Self { terminal }
474 }
475}
476
477impl FileOps for TtyFile {
478 fileops_impl_nonseekable!();
479 fileops_impl_noop_sync!();
480
481 fn close(self: Box<Self>, _file: &FileObjectState, _current_task: &CurrentTask) {
482 self.terminal.replica_close();
483 }
484
485 fn read(
486 &self,
487 file: &FileObject,
488 current_task: &CurrentTask,
489 offset: usize,
490 data: &mut dyn OutputBuffer,
491 ) -> Result<usize, Errno> {
492 debug_assert!(offset == 0);
493 file.blocking_op(current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, || {
494 self.terminal.replica_read(data)
495 })
496 }
497
498 fn write(
499 &self,
500 file: &FileObject,
501 current_task: &CurrentTask,
502 offset: usize,
503 data: &mut dyn InputBuffer,
504 ) -> Result<usize, Errno> {
505 debug_assert!(offset == 0);
506 file.blocking_op(current_task, FdEvents::POLLOUT | FdEvents::POLLHUP, None, || {
507 self.terminal.replica_write(data)
508 })
509 }
510
511 fn wait_async(
512 &self,
513 _file: &FileObject,
514 _current_task: &CurrentTask,
515 waiter: &Waiter,
516 events: FdEvents,
517 handler: EventHandler,
518 ) -> Option<WaitCanceler> {
519 Some(self.terminal.replica_wait_async(waiter, events, handler))
520 }
521
522 fn query_events(
523 &self,
524 _file: &FileObject,
525 _current_task: &CurrentTask,
526 ) -> Result<FdEvents, Errno> {
527 Ok(self.terminal.replica_query_events())
528 }
529
530 fn ioctl(
531 &self,
532 file: &FileObject,
533 current_task: &CurrentTask,
534 request: u32,
535 arg: SyscallArg,
536 ) -> Result<SyscallResult, Errno> {
537 shared_ioctl(&self.terminal, false, file, current_task, request, arg)
538 }
539}
540
541fn shared_ioctl(
543 terminal: &Terminal,
544 is_main: bool,
545 file: &FileObject,
546 current_task: &CurrentTask,
547 request: u32,
548 arg: SyscallArg,
549) -> Result<SyscallResult, Errno> {
550 let user_addr = UserAddress::from(arg);
551 match request {
552 FIONREAD => {
553 let value = terminal.read().get_available_read_size(is_main) as u32;
555 current_task.write_object(UserRef::<u32>::new(user_addr), &value)?;
556 Ok(SUCCESS)
557 }
558 TIOCSCTTY => {
559 let steal = bool::from(arg);
561 current_task.thread_group().set_controlling_terminal(
562 current_task,
563 terminal,
564 TerminalSide::from(is_main),
565 steal,
566 file.can_read(),
567 )?;
568 Ok(SUCCESS)
569 }
570 TIOCNOTTY => {
571 current_task.thread_group().release_controlling_terminal(
573 current_task,
574 terminal,
575 TerminalSide::from(is_main),
576 )?;
577 Ok(SUCCESS)
578 }
579 TIOCGPGRP => {
580 let pgid = current_task.thread_group().get_foreground_process_group(terminal)?;
582 current_task.write_object(UserRef::<pid_t>::new(user_addr), &pgid)?;
583 Ok(SUCCESS)
584 }
585 TIOCSPGRP => {
586 let pgid = current_task.read_object(UserRef::<pid_t>::new(user_addr))?;
588 if pgid < 0 {
589 return error!(EINVAL);
590 }
591 let pgid = current_task.kernel().pids.get(pgid)?;
592 current_task.thread_group().set_foreground_process_group(
593 current_task,
594 terminal,
595 &pgid,
596 )?;
597 Ok(SUCCESS)
598 }
599 TIOCGWINSZ => {
600 current_task.write_object(
602 UserRef::<uapi::winsize>::new(user_addr),
603 &terminal.read().line_discipline.window_size,
604 )?;
605 Ok(SUCCESS)
606 }
607 TIOCSWINSZ => {
608 let new_winsize = current_task.read_object(UserRef::<uapi::winsize>::new(user_addr))?;
610 if terminal.read().line_discipline.window_size == new_winsize {
611 return Ok(SUCCESS);
612 }
613 terminal.write().line_discipline.window_size = new_winsize;
615
616 terminal.send_signals(&[SIGWINCH]);
618 Ok(SUCCESS)
619 }
620 TCGETA => {
621 let termio = into_termio(terminal.read().termios());
622 current_task.write_object(UserRef::<uapi::termio>::new(user_addr), &termio)?;
623 Ok(SUCCESS)
624 }
625 TCGETS => {
626 let termios = termios_from_termios2(terminal.read().termios());
629 current_task.write_object(UserRef::<uapi::termios>::new(user_addr), &termios)?;
630 Ok(SUCCESS)
631 }
632 TCGETS2 => {
633 current_task.write_object(
634 UserRef::<uapi::termios2>::new(user_addr),
635 terminal.read().termios(),
636 )?;
637 Ok(SUCCESS)
638 }
639 TCSETA => {
640 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
641 terminal.set_termios(into_termios2(termio));
642 Ok(SUCCESS)
643 }
644 TCSETS => {
645 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
648 terminal.set_termios(termios2_from_termios(&termios));
649 Ok(SUCCESS)
650 }
651 TCSETS2 => {
652 let termios2 = current_task.read_object(UserRef::<uapi::termios2>::new(user_addr))?;
653 terminal.set_termios(termios2);
654 Ok(SUCCESS)
655 }
656 TCSETAF => {
657 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
659 terminal.set_termios(into_termios2(termio));
660 Ok(SUCCESS)
661 }
662 TCSETSF => {
663 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
665 terminal.set_termios(termios2_from_termios(&termios));
666 Ok(SUCCESS)
667 }
668 TCSETSF2 => {
669 let termios2 = current_task.read_object(UserRef::<uapi::termios2>::new(user_addr))?;
671 terminal.set_termios(termios2);
672 Ok(SUCCESS)
673 }
674 TCSETAW => {
675 track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETAW drain output queue first");
676 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
677 terminal.set_termios(into_termios2(termio));
678 Ok(SUCCESS)
679 }
680 TCSETSW => {
681 track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETSW drain output queue first");
682 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
683 terminal.set_termios(termios2_from_termios(&termios));
684 Ok(SUCCESS)
685 }
686 TCSETSW2 => {
687 track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETSW2 drain output queue first");
688 let termios2 = current_task.read_object(UserRef::<uapi::termios2>::new(user_addr))?;
689 terminal.set_termios(termios2);
690 Ok(SUCCESS)
691 }
692 TIOCSETD => {
693 track_stub!(
694 TODO("https://fxbug.dev/322874060"),
695 "devpts setting line discipline",
696 is_main
697 );
698 error!(EINVAL)
699 }
700 TCSBRK => Ok(SUCCESS),
701 TCXONC => {
702 track_stub!(TODO("https://fxbug.dev/322892912"), "devpts ioctl TCXONC", is_main);
703 error!(ENOSYS)
704 }
705 TCFLSH => {
706 terminal.flush(is_main, u32::from(arg))?;
707 Ok(SUCCESS)
708 }
709 TIOCEXCL => {
710 track_stub!(TODO("https://fxbug.dev/322893449"), "devpts ioctl TIOCEXCL", is_main);
711 error!(ENOSYS)
712 }
713 TIOCNXCL => {
714 track_stub!(TODO("https://fxbug.dev/322893393"), "devpts ioctl TIOCNXCL", is_main);
715 error!(ENOSYS)
716 }
717 TIOCOUTQ => {
718 track_stub!(TODO("https://fxbug.dev/322893723"), "devpts ioctl TIOCOUTQ", is_main);
719 error!(ENOSYS)
720 }
721 TIOCSTI => {
722 track_stub!(TODO("https://fxbug.dev/322893780"), "devpts ioctl TIOCSTI", is_main);
723 error!(ENOSYS)
724 }
725 TIOCMGET => {
726 track_stub!(TODO("https://fxbug.dev/322893681"), "devpts ioctl TIOCMGET", is_main);
727 error!(ENOSYS)
728 }
729 TIOCMBIS => {
730 track_stub!(TODO("https://fxbug.dev/322893709"), "devpts ioctl TIOCMBIS", is_main);
731 error!(ENOSYS)
732 }
733 TIOCMBIC => {
734 track_stub!(TODO("https://fxbug.dev/322893610"), "devpts ioctl TIOCMBIC", is_main);
735 error!(ENOSYS)
736 }
737 TIOCMSET => {
738 track_stub!(TODO("https://fxbug.dev/322893211"), "devpts ioctl TIOCMSET", is_main);
739 error!(ENOSYS)
740 }
741 TIOCGSOFTCAR => {
742 track_stub!(TODO("https://fxbug.dev/322893365"), "devpts ioctl TIOCGSOFTCAR", is_main);
743 error!(ENOSYS)
744 }
745 TIOCSSOFTCAR => {
746 track_stub!(TODO("https://fxbug.dev/322894074"), "devpts ioctl TIOCSSOFTCAR", is_main);
747 error!(ENOSYS)
748 }
749 TIOCLINUX => {
750 track_stub!(TODO("https://fxbug.dev/322893147"), "devpts ioctl TIOCLINUX", is_main);
751 error!(ENOSYS)
752 }
753 TIOCCONS => {
754 track_stub!(TODO("https://fxbug.dev/322893267"), "devpts ioctl TIOCCONS", is_main);
755 error!(ENOSYS)
756 }
757 TIOCGSERIAL => {
758 track_stub!(TODO("https://fxbug.dev/322893503"), "devpts ioctl TIOCGSERIAL", is_main);
759 error!(ENOSYS)
760 }
761 TIOCSSERIAL => {
762 track_stub!(TODO("https://fxbug.dev/322893663"), "devpts ioctl TIOCSSERIAL", is_main);
763 error!(ENOSYS)
764 }
765 TIOCPKT => {
766 if !is_main {
767 return error!(ENOTTY);
768 }
769 let value = current_task.read_object(UserRef::<i32>::new(user_addr))?;
770 terminal.write().set_packet_mode(value != 0);
771 Ok(SUCCESS)
772 }
773 TIOCGETD => {
774 track_stub!(TODO("https://fxbug.dev/322893974"), "devpts ioctl TIOCGETD", is_main);
775 error!(ENOSYS)
776 }
777 TCSBRKP => Ok(SUCCESS),
778 TIOCSBRK => {
779 track_stub!(TODO("https://fxbug.dev/322893936"), "devpts ioctl TIOCSBRK", is_main);
780 error!(ENOSYS)
781 }
782 TIOCCBRK => {
783 track_stub!(TODO("https://fxbug.dev/322893213"), "devpts ioctl TIOCCBRK", is_main);
784 error!(ENOSYS)
785 }
786 TIOCGSID => {
787 if !is_main
788 && !current_task
789 .thread_group()
790 .controlling_terminal()
791 .as_ref()
792 .is_some_and(|ct| std::ptr::eq(terminal, Arc::as_ptr(&ct.terminal)))
793 {
794 return error!(ENOTTY);
795 }
796 let sid = terminal
797 .read()
798 .controlling_session()
799 .map(|sid| sid.id)
800 .ok_or_else(|| errno!(ENOTTY))?;
801 current_task.write_object(UserRef::<pid_t>::new(user_addr), &sid)?;
802 Ok(SUCCESS)
803 }
804 TIOCGRS485 => {
805 track_stub!(TODO("https://fxbug.dev/322893728"), "devpts ioctl TIOCGRS485", is_main);
806 error!(ENOSYS)
807 }
808 TIOCSRS485 => {
809 track_stub!(TODO("https://fxbug.dev/322893783"), "devpts ioctl TIOCSRS485", is_main);
810 error!(ENOSYS)
811 }
812 TCGETX => {
813 track_stub!(TODO("https://fxbug.dev/322893327"), "devpts ioctl TCGETX", is_main);
814 error!(ENOSYS)
815 }
816 TCSETX => {
817 track_stub!(TODO("https://fxbug.dev/322893741"), "devpts ioctl TCSETX", is_main);
818 error!(ENOSYS)
819 }
820 TCSETXF => {
821 track_stub!(TODO("https://fxbug.dev/322893937"), "devpts ioctl TCSETXF", is_main);
822 error!(ENOSYS)
823 }
824 TCSETXW => {
825 track_stub!(TODO("https://fxbug.dev/322893899"), "devpts ioctl TCSETXW", is_main);
826 error!(ENOSYS)
827 }
828 TIOCVHANGUP => {
829 track_stub!(TODO("https://fxbug.dev/322893742"), "devpts ioctl TIOCVHANGUP", is_main);
830 error!(ENOSYS)
831 }
832 FIOASYNC => {
833 track_stub!(TODO("https://fxbug.dev/322893269"), "devpts ioctl FIOASYNC", is_main);
834 error!(ENOSYS)
835 }
836 TIOCSERCONFIG => {
837 track_stub!(TODO("https://fxbug.dev/322893881"), "devpts ioctl TIOCSERCONFIG", is_main);
838 error!(ENOSYS)
839 }
840 TIOCSERGWILD => {
841 track_stub!(TODO("https://fxbug.dev/322893686"), "devpts ioctl TIOCSERGWILD", is_main);
842 error!(ENOSYS)
843 }
844 TIOCSERSWILD => {
845 track_stub!(TODO("https://fxbug.dev/322893837"), "devpts ioctl TIOCSERSWILD", is_main);
846 error!(ENOSYS)
847 }
848 TIOCGLCKTRMIOS => {
849 track_stub!(
850 TODO("https://fxbug.dev/322894114"),
851 "devpts ioctl TIOCGLCKTRMIOS",
852 is_main
853 );
854 error!(ENOSYS)
855 }
856 TIOCSLCKTRMIOS => {
857 track_stub!(
858 TODO("https://fxbug.dev/322893711"),
859 "devpts ioctl TIOCSLCKTRMIOS",
860 is_main
861 );
862 error!(ENOSYS)
863 }
864 TIOCSERGSTRUCT => {
865 track_stub!(
866 TODO("https://fxbug.dev/322893828"),
867 "devpts ioctl TIOCSERGSTRUCT",
868 is_main
869 );
870 error!(ENOSYS)
871 }
872 TIOCSERGETLSR => {
873 track_stub!(TODO("https://fxbug.dev/322894083"), "devpts ioctl TIOCSERGETLSR", is_main);
874 error!(ENOSYS)
875 }
876 TIOCSERGETMULTI => {
877 track_stub!(
878 TODO("https://fxbug.dev/322893962"),
879 "devpts ioctl TIOCSERGETMULTI",
880 is_main
881 );
882 error!(ENOSYS)
883 }
884 TIOCSERSETMULTI => {
885 track_stub!(
886 TODO("https://fxbug.dev/322893273"),
887 "devpts ioctl TIOCSERSETMULTI",
888 is_main
889 );
890 error!(ENOSYS)
891 }
892 TIOCMIWAIT => {
893 track_stub!(TODO("https://fxbug.dev/322894005"), "devpts ioctl TIOCMIWAIT", is_main);
894 error!(ENOSYS)
895 }
896 TIOCGICOUNT => {
897 track_stub!(TODO("https://fxbug.dev/322893862"), "devpts ioctl TIOCGICOUNT", is_main);
898 error!(ENOSYS)
899 }
900 FIOQSIZE => {
901 track_stub!(TODO("https://fxbug.dev/322893770"), "devpts ioctl FIOQSIZE", is_main);
902 error!(ENOSYS)
903 }
904 other => {
905 track_stub!(TODO("https://fxbug.dev/322893712"), "devpts unknown ioctl", other);
906 error!(ENOTTY)
907 }
908 }
909}
910
911#[cfg(test)]
912mod tests {
913 use super::*;
914 use crate::tty_device_init;
915 use starnix_core::fs::tmpfs::TmpFs;
916 use starnix_core::task::{ExitStatus, KernelFeatures};
917 use starnix_core::testing::*;
918 use starnix_core::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
919 use starnix_core::vfs::fs_args::MountParams;
920 use starnix_core::vfs::{MountInfo, NamespaceNode, OpenAccessCheck};
921 use starnix_types::ownership::Releasable;
922 use starnix_uapi::auth::Credentials;
923 use starnix_uapi::file_mode::FileMode;
924 use starnix_uapi::signals::{
925 SIGCHLD, SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTSTP, SIGTTOU, SigSet,
926 };
927 use std::sync::atomic::{AtomicBool, Ordering};
928
929 fn new_pts_fs(kernel: &Kernel) -> FileSystemHandle {
930 let mut options = FileSystemOptions::default();
931 options.params = MountParams::parse("ptmxmode=666".into()).expect("parse option");
932 super::new_pts_fs(&kernel, options).expect("create new_pts_fs")
933 }
934
935 fn ioctl<T: zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable + Copy>(
936 current_task: &CurrentTask,
937 file: &FileHandle,
938 command: u32,
939 value: &T,
940 ) -> Result<T, Errno> {
941 let address =
942 map_memory(current_task, UserAddress::default(), std::mem::size_of::<T>() as u64);
943 let address_ref = UserRef::<T>::new(address);
944 current_task.write_object(address_ref, value)?;
945 file.ioctl(current_task, command, address.into())?;
946 current_task.read_object(address_ref)
947 }
948
949 fn set_controlling_terminal(
950 current_task: &CurrentTask,
951 file: &FileHandle,
952 steal: bool,
953 ) -> Result<SyscallResult, Errno> {
954 #[allow(clippy::bool_to_int_with_if)]
955 file.ioctl(current_task, TIOCSCTTY, steal.into())
956 }
957
958 fn lookup_node(
959 task: &CurrentTask,
960 fs: &FileSystemHandle,
961 name: &FsStr,
962 ) -> Result<NamespaceNode, Errno> {
963 let root = NamespaceNode::new_anonymous(fs.root().clone());
964 root.lookup_child(task, &mut Default::default(), name)
965 }
966
967 fn open_file_with_flags(
968 current_task: &CurrentTask,
969 fs: &FileSystemHandle,
970 name: &FsStr,
971 flags: OpenFlags,
972 ) -> Result<FileHandle, Errno> {
973 let node = lookup_node(current_task, fs, name)?;
974 node.open(current_task, flags)
975 }
976
977 fn open_file(
978 current_task: &CurrentTask,
979 fs: &FileSystemHandle,
980 name: &FsStr,
981 ) -> Result<FileHandle, Errno> {
982 open_file_with_flags(current_task, fs, name, OpenFlags::RDWR | OpenFlags::NOCTTY)
983 }
984
985 fn open_ptmx_and_unlock(
986 current_task: &CurrentTask,
987 fs: &FileSystemHandle,
988 ) -> Result<FileHandle, Errno> {
989 let file = open_file_with_flags(current_task, fs, "ptmx".into(), OpenFlags::RDWR)?;
990
991 ioctl::<i32>(current_task, &file, TIOCSPTLCK, &0)?;
993
994 Ok(file)
995 }
996
997 #[fuchsia::test]
998 async fn opening_ptmx_creates_pts() {
999 spawn_kernel_and_run(async |task| {
1000 let kernel = task.kernel();
1001 tty_device_init(kernel).expect("tty_device_init");
1002 let fs = new_pts_fs(kernel);
1003 lookup_node(task, &fs, "0".into()).unwrap_err();
1004 let _ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1005 lookup_node(task, &fs, "0".into()).expect("pty");
1006 })
1007 .await;
1008 }
1009
1010 #[fuchsia::test]
1011 async fn closing_ptmx_closes_pts() {
1012 spawn_kernel_and_run(async |task| {
1013 let kernel = task.kernel();
1014 tty_device_init(kernel).expect("tty_device_init");
1015 let fs = new_pts_fs(kernel);
1016 lookup_node(task, &fs, "0".into()).unwrap_err();
1017 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1018 let _pts = open_file(task, &fs, "0".into()).expect("open file");
1019 std::mem::drop(ptmx);
1020 task.trigger_delayed_releaser();
1021 lookup_node(task, &fs, "0".into()).unwrap_err();
1022 })
1023 .await;
1024 }
1025
1026 #[fuchsia::test]
1027 async fn pts_are_reused() {
1028 spawn_kernel_and_run(async |task| {
1029 let kernel = task.kernel();
1030 tty_device_init(kernel).expect("tty_device_init");
1031 let fs = new_pts_fs(kernel);
1032
1033 let _ptmx0 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1034 let mut _ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1035 let _ptmx2 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1036
1037 lookup_node(task, &fs, "0".into()).expect("component_lookup");
1038 lookup_node(task, &fs, "1".into()).expect("component_lookup");
1039 lookup_node(task, &fs, "2".into()).expect("component_lookup");
1040
1041 std::mem::drop(_ptmx1);
1042 task.trigger_delayed_releaser();
1043
1044 lookup_node(task, &fs, "1".into()).unwrap_err();
1045
1046 _ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1047 lookup_node(task, &fs, "1".into()).expect("component_lookup");
1048 })
1049 .await;
1050 }
1051
1052 #[fuchsia::test]
1053 async fn opening_inexistant_replica_fails() {
1054 spawn_kernel_and_run(async |task| {
1055 let kernel = task.kernel();
1056 tty_device_init(kernel).expect("tty_device_init");
1057 new_pts_fs(kernel);
1059 let fs = TmpFs::new_fs(kernel);
1060 let mount = MountInfo::detached();
1061 let pts = fs
1062 .root()
1063 .create_entry(task, &mount, "custom_pts".into(), |dir, mount, name| {
1064 dir.create_node(
1065 task,
1066 mount,
1067 name,
1068 mode!(IFCHR, 0o666),
1069 DeviceId::new(DEVPTS_FIRST_MAJOR, 0),
1070 FsCred::root(),
1071 )
1072 })
1073 .expect("custom_pts");
1074 let node = NamespaceNode::new_anonymous(pts.clone());
1075 assert!(node.open(task, OpenAccessCheck::skip(OpenFlags::RDONLY)).is_err());
1076 })
1077 .await;
1078 }
1079
1080 #[fuchsia::test]
1081 async fn test_open_tty() {
1082 spawn_kernel_with_features_and_run(
1083 async |task| {
1084 let kernel = task.kernel();
1085 tty_device_init(kernel).expect("tty_device_init");
1086 let fs = new_pts_fs(kernel);
1087 let devfs = starnix_core::fs::devtmpfs::DevTmpFs::from_kernel(kernel);
1088
1089 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1090 set_controlling_terminal(task, &ptmx, false).expect("set_controlling_terminal");
1091 let tty =
1092 open_file_with_flags(task, &devfs, "tty".into(), OpenFlags::RDWR).expect("tty");
1093 assert_eq!(
1096 ioctl::<i32>(task, &tty, TIOCGPTN, &0),
1097 ioctl::<i32>(task, &ptmx, TIOCGPTN, &0)
1098 );
1099
1100 ioctl::<i32>(task, &ptmx, TIOCNOTTY, &0).expect("detach terminal");
1102 let pts = open_file(task, &fs, "0".into()).expect("open file");
1103 set_controlling_terminal(task, &pts, false).expect("set_controlling_terminal");
1104 let tty =
1105 open_file_with_flags(task, &devfs, "tty".into(), OpenFlags::RDWR).expect("tty");
1106 assert!(ioctl::<i32>(task, &tty, TIOCGPTN, &0).is_err());
1108 },
1109 KernelFeatures { devtmpfs: true, ..Default::default() },
1110 )
1111 .await;
1112 }
1113
1114 #[fuchsia::test]
1115 async fn test_unknown_ioctl() {
1116 spawn_kernel_and_run(async |task| {
1117 let kernel = task.kernel();
1118 tty_device_init(kernel).expect("tty_device_init");
1119 let fs = new_pts_fs(kernel);
1120
1121 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1122 assert_eq!(ptmx.ioctl(task, 42, Default::default()), error!(ENOTTY));
1123
1124 let pts_file = open_file(task, &fs, "0".into()).expect("open file");
1125 assert_eq!(pts_file.ioctl(task, 42, Default::default()), error!(ENOTTY));
1126 })
1127 .await;
1128 }
1129
1130 #[fuchsia::test]
1131 async fn test_tiocgptn_ioctl() {
1132 spawn_kernel_and_run(async |task| {
1133 let kernel = task.kernel();
1134 tty_device_init(kernel).expect("tty_device_init");
1135 let fs = new_pts_fs(kernel);
1136 let ptmx0 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1137 let ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1138
1139 let pts0 = ioctl::<u32>(task, &ptmx0, TIOCGPTN, &0).expect("ioctl");
1140 assert_eq!(pts0, 0);
1141
1142 let pts1 = ioctl::<u32>(task, &ptmx1, TIOCGPTN, &0).expect("ioctl");
1143 assert_eq!(pts1, 1);
1144 })
1145 .await;
1146 }
1147
1148 #[fuchsia::test]
1149 async fn test_new_terminal_is_locked() {
1150 spawn_kernel_and_run(async |task| {
1151 let kernel = task.kernel();
1152 tty_device_init(kernel).expect("tty_device_init");
1153 let fs = new_pts_fs(kernel);
1154 let _ptmx_file = open_file(task, &fs, "ptmx".into()).expect("open file");
1155
1156 let pts = lookup_node(task, &fs, "0".into()).expect("component_lookup");
1157 assert_eq!(pts.open(task, OpenFlags::RDONLY).map(|_| ()), error!(EIO));
1158 })
1159 .await;
1160 }
1161
1162 #[fuchsia::test]
1163 async fn test_lock_ioctls() {
1164 spawn_kernel_and_run(async |task| {
1165 let kernel = task.kernel();
1166 tty_device_init(kernel).expect("tty_device_init");
1167 let fs = new_pts_fs(kernel);
1168 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1169 let pts = lookup_node(task, &fs, "0".into()).expect("component_lookup");
1170
1171 assert_eq!(ioctl::<i32>(task, &ptmx, TIOCGPTLCK, &0), Ok(0));
1173 pts.open(task, OpenFlags::RDONLY).expect("open");
1175
1176 ioctl::<i32>(task, &ptmx, TIOCSPTLCK, &42).expect("ioctl");
1178 assert_eq!(ioctl::<i32>(task, &ptmx, TIOCGPTLCK, &0), Ok(1));
1180 assert_eq!(pts.open(task, OpenFlags::RDONLY).map(|_| ()), error!(EIO));
1182 })
1183 .await;
1184 }
1185
1186 #[fuchsia::test]
1187 async fn test_ptmx_stats() {
1188 spawn_kernel_and_run(async |task| {
1189 let kernel = task.kernel();
1190 tty_device_init(kernel).expect("tty_device_init");
1191 task.set_creds(Credentials::with_ids(22, 22));
1192 let fs = new_pts_fs(kernel);
1193 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1194 let ptmx_stat = ptmx.node().stat(task).expect("stat");
1195 assert_eq!(ptmx_stat.st_blksize as usize, BLOCK_SIZE);
1196 let pts = open_file(task, &fs, "0".into()).expect("open file");
1197 let pts_stats = pts.node().stat(task).expect("stat");
1198 assert_eq!(pts_stats.st_mode & FileMode::PERMISSIONS.bits(), 0o600);
1199 assert_eq!(pts_stats.st_uid, 22);
1200 })
1202 .await;
1203 }
1204
1205 #[fuchsia::test]
1206 async fn test_attach_terminal_when_open() {
1207 spawn_kernel_and_run(async |task| {
1208 let kernel = task.kernel();
1209 tty_device_init(kernel).expect("tty_device_init");
1210 let fs = new_pts_fs(kernel);
1211 let _opened_main = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1212 assert!(task.thread_group().controlling_terminal().is_none());
1214 let _opened_replica2 =
1216 open_file_with_flags(task, &fs, "0".into(), OpenFlags::RDWR | OpenFlags::NOCTTY)
1217 .expect("open file");
1218 assert!(task.thread_group().controlling_terminal().is_none());
1219
1220 let _opened_replica2 =
1222 open_file_with_flags(task, &fs, "0".into(), OpenFlags::RDWR).expect("open file");
1223 assert!(task.thread_group().controlling_terminal().is_some());
1224 })
1225 .await;
1226 }
1227
1228 #[fuchsia::test]
1229 async fn test_attach_terminal() {
1230 spawn_kernel_and_run(async |task1| {
1231 let kernel = task1.kernel();
1232 tty_device_init(kernel).expect("tty_device_init");
1233 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1234 task2.thread_group().setsid().expect("setsid");
1235
1236 let fs = new_pts_fs(kernel);
1237 let opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1238 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1239
1240 assert_eq!(ioctl::<i32>(task1, &opened_main, TIOCGPGRP, &0), error!(ENOTTY));
1241 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1242
1243 set_controlling_terminal(task1, &opened_main, false).unwrap();
1244 assert_eq!(
1245 ioctl::<i32>(task1, &opened_main, TIOCGPGRP, &0),
1246 Ok(task1.thread_group().pgid().id)
1247 );
1248 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1249
1250 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1252 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1253 })
1254 .await;
1255 }
1256
1257 #[fuchsia::test]
1258 async fn test_steal_terminal() {
1259 spawn_kernel_and_run(async |task1| {
1260 let kernel = task1.kernel();
1261 tty_device_init(kernel).expect("tty_device_init");
1262 task1.set_creds(Credentials::with_ids(1, 1));
1263
1264 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1265
1266 let fs = new_pts_fs(kernel);
1267 let _opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1268 let wo_opened_replica =
1269 open_file_with_flags(task1, &fs, "0".into(), OpenFlags::WRONLY | OpenFlags::NOCTTY)
1270 .expect("open file");
1271 assert!(!wo_opened_replica.can_read());
1272
1273 assert_eq!(set_controlling_terminal(task1, &wo_opened_replica, false), error!(EPERM));
1275
1276 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1277 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1279
1280 set_controlling_terminal(task1, &opened_replica, false)
1282 .expect("Associate terminal to task1");
1283
1284 set_controlling_terminal(task1, &opened_replica, false)
1286 .expect("Redundant association should succeed");
1287
1288 task2.thread_group().setsid().expect("setsid");
1289
1290 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1292
1293 assert_eq!(set_controlling_terminal(&task2, &opened_replica, true), error!(EPERM));
1295
1296 task2.set_creds(Credentials::with_ids(0, 0));
1298 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1300 set_controlling_terminal(&task2, &opened_replica, true)
1301 .expect("Associate terminal to task2");
1302
1303 assert!(task1.thread_group().controlling_terminal().is_none());
1304
1305 let _opened_main2 = open_ptmx_and_unlock(task1, &fs).expect("ptmx 1");
1308 let opened_replica2 = open_file(task1, &fs, "1".into()).expect("open file 1");
1309 set_controlling_terminal(task1, &opened_replica2, false)
1310 .expect("Associate second terminal to task1");
1311 assert!(task1.thread_group().controlling_terminal().is_some());
1312 assert_eq!(set_controlling_terminal(&task2, &opened_replica2, true), error!(EPERM));
1313 assert!(task1.thread_group().controlling_terminal().is_some());
1314 })
1315 .await;
1316 }
1317
1318 #[fuchsia::test]
1319 async fn test_tiocgsid() {
1320 spawn_kernel_and_run(async |task1| {
1321 let kernel = task1.kernel();
1322 tty_device_init(kernel).expect("tty_device_init");
1323 let mut task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1324 task2.set_mm_for_test(task1.mm().unwrap().clone());
1325 task2.thread_group().setsid().expect("setsid");
1326
1327 let fs = new_pts_fs(kernel);
1328 let opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1329 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1330
1331 assert_eq!(ioctl::<pid_t>(task1, &opened_main, TIOCGSID, &0), error!(ENOTTY));
1333 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1334
1335 set_controlling_terminal(&task2, &opened_replica, false)
1337 .expect("set controlling terminal");
1338 let expected_sid = task2.thread_group().read().session.id;
1339
1340 assert_eq!(ioctl::<pid_t>(&task2, &opened_main, TIOCGSID, &0), Ok(expected_sid));
1342 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), Ok(expected_sid));
1343
1344 assert_eq!(ioctl::<pid_t>(task1, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1347 assert_eq!(ioctl::<pid_t>(task1, &opened_main, TIOCGSID, &0), Ok(expected_sid));
1348
1349 ioctl::<i32>(&task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1351 assert_eq!(ioctl::<pid_t>(&task2, &opened_main, TIOCGSID, &0), error!(ENOTTY));
1352 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1353 })
1354 .await;
1355 }
1356
1357 #[fuchsia::test]
1358 async fn test_set_foreground_process() {
1359 spawn_kernel_and_run(async |init| {
1360 let kernel = init.kernel();
1361 tty_device_init(kernel).expect("tty_device_init");
1362 let mut task1 = init.clone_task_for_test(0, Some(SIGCHLD));
1363 task1.set_mm_for_test(init.mm().unwrap().clone());
1366 task1.thread_group().setsid().expect("setsid");
1367 let pre_existing_task = task1.clone_task_for_test(0, Some(SIGCHLD));
1368
1369 let fs = new_pts_fs(kernel);
1370 let _opened_main = open_ptmx_and_unlock(init, &fs).expect("ptmx");
1371 let opened_replica = open_file(&pre_existing_task, &fs, "0".into()).expect("open file");
1372
1373 assert_eq!(
1376 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task1.pid.id),
1377 error!(ENOTTY)
1378 );
1379
1380 set_controlling_terminal(&task1, &opened_replica, false).unwrap();
1383 assert!(pre_existing_task.thread_group().controlling_terminal().is_none());
1384
1385 let mut task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1386 task2.set_mm_for_test(init.mm().unwrap().clone());
1387 task2.thread_group().setpgid(&task2, &task2, &task2.pid).expect("setpgid");
1388 let task2_pgid = task2.thread_group().pgid();
1389 assert_ne!(task2_pgid, task1.thread_group().pgid());
1390
1391 assert_eq!(
1393 ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0),
1394 Ok(task1.thread_group().pgid().id)
1395 );
1396
1397 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &-1), error!(EINVAL));
1399
1400 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &255), error!(ESRCH));
1402
1403 let init_pgid = init.thread_group().pgid();
1405 assert_eq!(
1406 ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &init_pgid.id),
1407 error!(EPERM)
1408 );
1409
1410 assert_eq!(
1412 ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &task2_pgid.id),
1413 error!(EINTR)
1414 );
1415 assert!(task2.read().has_signal_pending(SIGTTOU));
1416
1417 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task2_pgid.id).unwrap();
1419
1420 let terminal =
1422 Arc::clone(&task1.thread_group().controlling_terminal().as_ref().unwrap().terminal);
1423 assert_eq!(
1424 terminal.read().controlling_session.as_ref().unwrap().foreground_process_group(),
1425 &task2_pgid
1426 );
1427 })
1428 .await;
1429 }
1430
1431 #[fuchsia::test]
1432 async fn test_detach_session() {
1433 spawn_kernel_and_run(async |task1| {
1434 let kernel = task1.kernel();
1435 tty_device_init(kernel).expect("tty_device_init");
1436 let mut task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1437 task2.thread_group().setsid().expect("setsid");
1438
1439 let fs = new_pts_fs(kernel);
1440 let _opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1441 let opened_replica = open_file(task1, &fs, "0".into()).expect("open file");
1442
1443 assert_eq!(ioctl::<i32>(task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1445
1446 set_controlling_terminal(&task2, &opened_replica, false)
1447 .expect("set controlling terminal");
1448
1449 assert_eq!(ioctl::<i32>(task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1451
1452 let mut task3 = task2.clone_task_for_test(0, Some(SIGCHLD));
1455 task3.set_mm_for_test(task1.mm().unwrap().clone());
1456 task2.set_mm_for_test(task1.mm().unwrap().clone());
1457 assert!(task3.thread_group().controlling_terminal().is_some());
1458 assert_eq!(ioctl::<i32>(&task3, &opened_replica, TIOCGPGRP, &0), Ok(task2.pid.id));
1459
1460 ioctl::<i32>(&task3, &opened_replica, TIOCNOTTY, &0)
1461 .expect("non-leader detach terminal");
1462 assert!(task3.thread_group().controlling_terminal().is_none());
1463 assert!(task2.thread_group().controlling_terminal().is_some());
1464 assert_eq!(ioctl::<i32>(&task3, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1465 assert_eq!(
1466 ioctl::<i32>(&task3, &opened_replica, TIOCSPGRP, &task2.pid.id),
1467 error!(ENOTTY)
1468 );
1469 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), Ok(task2.pid.id));
1470
1471 ioctl::<i32>(&task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1473 assert!(task2.thread_group().controlling_terminal().is_none());
1474 })
1475 .await;
1476 }
1477
1478 #[fuchsia::test]
1479 async fn test_send_data_back_and_forth() {
1480 spawn_kernel_and_run(async |task| {
1481 let kernel = task.kernel();
1482 tty_device_init(kernel).expect("tty_device_init");
1483 let fs = new_pts_fs(kernel);
1484 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1485 let pts = open_file(task, &fs, "0".into()).expect("open file");
1486
1487 let has_data_ready_to_read = |fd: &FileHandle| {
1488 fd.query_events(task).expect("query_events").contains(FdEvents::POLLIN)
1489 };
1490
1491 let write_and_assert = |fd: &FileHandle, data: &[u8]| {
1492 assert_eq!(
1493 fd.write(task, &mut VecInputBuffer::new(data)).expect("write"),
1494 data.len()
1495 );
1496 };
1497
1498 let read_and_check = |fd: &FileHandle, data: &[u8]| {
1499 assert!(has_data_ready_to_read(fd));
1500 let mut buffer = VecOutputBuffer::new(data.len() + 1);
1501 assert_eq!(fd.read(task, &mut buffer).expect("read"), data.len());
1502 assert_eq!(data, buffer.data());
1503 };
1504
1505 let hello_buffer = b"hello\n";
1506 let hello_transformed_buffer = b"hello\r\n";
1507
1508 write_and_assert(&ptmx, hello_buffer);
1510 read_and_check(&pts, hello_buffer);
1511
1512 read_and_check(&ptmx, hello_transformed_buffer);
1514
1515 write_and_assert(&pts, hello_buffer);
1517 read_and_check(&ptmx, hello_transformed_buffer);
1518
1519 assert!(!has_data_ready_to_read(&pts));
1521 })
1522 .await;
1523 }
1524
1525 #[fuchsia::test]
1526 async fn test_foreground_pgrp_persistence_and_signal_noop_on_exit() {
1527 spawn_kernel_and_run(async |init| {
1528 let kernel = init.kernel();
1529 tty_device_init(kernel).expect("tty_device_init");
1530
1531 let mut task1 = init.clone_task_for_test(0, Some(SIGCHLD));
1532 task1.set_mm_for_test(init.mm().unwrap().clone());
1533 task1.thread_group().setsid().expect("setsid");
1534 let task1_pgid = task1.thread_group().pgid();
1535
1536 let fs = new_pts_fs(kernel);
1537 let _opened_main = open_ptmx_and_unlock(init, &fs).expect("ptmx");
1538 let opened_replica = open_file(&task1, &fs, "0".into()).expect("open replica");
1539 set_controlling_terminal(&task1, &opened_replica, false)
1540 .expect("set controlling terminal");
1541
1542 let terminal =
1543 Arc::clone(&task1.thread_group().controlling_terminal().as_ref().unwrap().terminal);
1544
1545 let mut task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1547 task2.set_mm_for_test(init.mm().unwrap().clone());
1548 task2.thread_group().setpgid(&task2, &task2, &task2.pid).expect("setpgid task2");
1549 let task2_pgid = task2.thread_group().pgid();
1550 let task2_pgid_id = task2_pgid.id;
1551
1552 let mut task3 = task1.clone_task_for_test(0, Some(SIGCHLD));
1553 task3.set_mm_for_test(init.mm().unwrap().clone());
1554 task1.thread_group().setpgid(&task1, &task3, &task2_pgid).expect("setpgid task3");
1555
1556 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task2_pgid_id)
1558 .expect("set foreground process group");
1559 assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0), Ok(task2_pgid_id));
1560
1561 task1.write().set_signal_mask(SigSet::from(SIGWINCH));
1563 task2.write().set_signal_mask(SigSet::from(SIGWINCH));
1564 task3.write().set_signal_mask(SigSet::from(SIGWINCH));
1565
1566 terminal.send_signals(&[SIGWINCH]);
1568 assert!(task2.thread_group().has_signal_queued(SIGWINCH));
1569 assert!(task3.thread_group().has_signal_queued(SIGWINCH));
1570 assert!(!task1.thread_group().has_signal_queued(SIGWINCH));
1571
1572 task2.thread_group().kill(ExitStatus::Exit(0), None);
1574 std::mem::drop(task2);
1575
1576 assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0), Ok(task2_pgid_id));
1578 terminal.send_signals(&[SIGINT]);
1579 assert!(task3.thread_group().has_signal_queued(SIGINT));
1580 assert!(!task1.thread_group().has_signal_queued(SIGINT));
1581
1582 task3.thread_group().kill(ExitStatus::Exit(0), None);
1585 std::mem::drop(task3);
1586 {
1587 let mut pids = kernel.pids.lock();
1588 for zombie in task1.thread_group().write().zombie_children.drain(..) {
1589 zombie.release(&mut pids);
1590 }
1591 }
1592 assert_eq!(kernel.pids.get(task2_pgid_id), error!(ESRCH));
1593
1594 assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0), Ok(task2_pgid_id));
1596
1597 terminal.send_signals(&[SIGINT, SIGQUIT, SIGTSTP, SIGWINCH, SIGHUP]);
1599 assert!(!task1.thread_group().has_signal_queued(SIGINT));
1600 assert!(!task1.thread_group().has_signal_queued(SIGQUIT));
1601 assert!(!task1.thread_group().has_signal_queued(SIGTSTP));
1602 assert!(!task1.thread_group().has_signal_queued(SIGWINCH));
1603 assert!(!task1.thread_group().has_signal_queued(SIGHUP));
1604
1605 task1.write().set_signal_mask(SigSet::from(SIGTTOU) | SigSet::from(SIGWINCH));
1607 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task1_pgid.id)
1608 .expect("reclaim foreground process group");
1609 assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0), Ok(task1_pgid.id));
1610
1611 let mut task4 = task1.clone_task_for_test(0, Some(SIGCHLD));
1613 task4.set_mm_for_test(init.mm().unwrap().clone());
1614 task4.thread_group().setpgid(&task4, &task4, &task4.pid).expect("setpgid task4");
1615 let task4_pgid_id = task4.pid.id;
1616 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task4_pgid_id)
1617 .expect("set foreground to task4");
1618 task4.thread_group().kill(ExitStatus::Exit(0), None);
1619 std::mem::drop(task4);
1620
1621 terminal.disassociate_controlling_session();
1622 assert!(task1.thread_group().controlling_terminal().is_none());
1623 assert!(!task1.thread_group().has_signal_queued(SIGHUP));
1624 assert!(!task1.thread_group().has_signal_queued(SIGCONT));
1625 })
1626 .await;
1627 }
1628
1629 #[fuchsia::test]
1630 async fn test_concurrent_terminal_job_control_stress() {
1631 spawn_kernel_and_run(async |init| {
1632 let kernel = init.kernel();
1633 tty_device_init(kernel).expect("tty_device_init");
1634
1635 let mut task1 = init.clone_task_for_test(0, Some(SIGCHLD));
1636 task1.set_mm_for_test(init.mm().unwrap().clone());
1637 task1.thread_group().setsid().expect("setsid task1");
1638 task1.write().set_signal_mask(SigSet::from(SIGTTOU));
1639 let task1_pgid = task1.thread_group().pgid();
1640
1641 let mut child_a = task1.clone_task_for_test(0, Some(SIGCHLD));
1642 child_a.set_mm_for_test(init.mm().unwrap().clone());
1643 child_a
1644 .thread_group()
1645 .setpgid(&child_a, &child_a, &child_a.pid)
1646 .expect("setpgid child_a");
1647 let child_a_pgid = child_a.pid.clone();
1648
1649 let mut child_b = task1.clone_task_for_test(0, Some(SIGCHLD));
1650 child_b.set_mm_for_test(init.mm().unwrap().clone());
1651 child_b
1652 .thread_group()
1653 .setpgid(&child_b, &child_b, &child_b.pid)
1654 .expect("setpgid child_b");
1655 let child_b_pgid = child_b.pid.clone();
1656
1657 let mut child_m = task1.clone_task_for_test(0, Some(SIGCHLD));
1658 child_m.set_mm_for_test(init.mm().unwrap().clone());
1659
1660 let fs = new_pts_fs(kernel);
1661 let _opened_main = open_ptmx_and_unlock(init, &fs).expect("ptmx 0");
1662 let opened_replica = open_file(&task1, &fs, "0".into()).expect("open replica 0");
1663 set_controlling_terminal(&task1, &opened_replica, false)
1664 .expect("set controlling terminal 0");
1665 let terminal =
1666 Arc::clone(&task1.thread_group().controlling_terminal().as_ref().unwrap().terminal);
1667
1668 let mut task_s2 = init.clone_task_for_test(0, Some(SIGCHLD));
1670 task_s2.set_mm_for_test(init.mm().unwrap().clone());
1671 task_s2.thread_group().setsid().expect("setsid task_s2");
1672 task_s2.write().set_signal_mask(SigSet::from(SIGTTOU));
1673 let task_s2_pgid = task_s2.pid.clone();
1674
1675 let _opened_main2 = open_ptmx_and_unlock(init, &fs).expect("ptmx 1");
1676 let opened_replica2 = open_file(&task_s2, &fs, "1".into()).expect("open replica 1");
1677 set_controlling_terminal(&task_s2, &opened_replica2, false)
1678 .expect("set controlling terminal 1");
1679 let terminal2 = Arc::clone(
1680 &task_s2.thread_group().controlling_terminal().as_ref().unwrap().terminal,
1681 );
1682
1683 let running = Arc::new(AtomicBool::new(true));
1684
1685 let sig_term1 = Arc::clone(&terminal);
1687 let sig_term2 = Arc::clone(&terminal2);
1688 let sig_running = Arc::clone(&running);
1689 let signal_thread = std::thread::spawn(move || {
1690 while sig_running.load(Ordering::Relaxed) {
1691 sig_term1.send_signals(&[SIGWINCH, SIGCONT]);
1692 sig_term2.send_signals(&[SIGWINCH, SIGCONT]);
1693 }
1694 });
1695
1696 let get_tg1 = Arc::clone(task1.thread_group());
1698 let get_term1 = Arc::clone(&terminal);
1699 let get_running = Arc::clone(&running);
1700 let valid_pgids = [task1_pgid.id, child_a_pgid.id, child_b_pgid.id];
1701 let getter_thread = std::thread::spawn(move || {
1702 while get_running.load(Ordering::Relaxed) {
1703 if let Ok(pgid) = get_tg1.get_foreground_process_group(&get_term1) {
1704 assert!(valid_pgids.contains(&pgid));
1705 }
1706 }
1707 });
1708
1709 let setsid_tg1 = Arc::clone(task1.thread_group());
1711 let setsid_tga = Arc::clone(child_a.thread_group());
1712 let setsid_running = Arc::clone(&running);
1713 let setsid_thread = std::thread::spawn(move || {
1714 while setsid_running.load(Ordering::Relaxed) {
1715 assert_eq!(setsid_tg1.setsid(), error!(EPERM));
1716 assert_eq!(setsid_tga.setsid(), error!(EPERM));
1717 }
1718 });
1719
1720 let disassoc_term2 = Arc::clone(&terminal2);
1722 let disassoc_running = Arc::clone(&running);
1723 let disassoc_thread = std::thread::spawn(move || {
1724 while disassoc_running.load(Ordering::Relaxed) {
1725 disassoc_term2.disassociate_controlling_session();
1726 }
1727 });
1728
1729 for _ in 0..75 {
1731 let _ = ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &child_a_pgid.id);
1732 task1
1733 .thread_group()
1734 .setpgid(&task1, &child_m, &child_a_pgid)
1735 .expect("migrate child_m to child_a");
1736
1737 let _ = ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &child_b_pgid.id);
1738 task1
1739 .thread_group()
1740 .setpgid(&task1, &child_m, &child_b_pgid)
1741 .expect("migrate child_m to child_b");
1742
1743 let _ = ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task1_pgid.id);
1744
1745 let _ = set_controlling_terminal(&task_s2, &opened_replica2, false);
1746 let _ = task_s2.thread_group().set_foreground_process_group(
1747 &task_s2,
1748 &terminal2,
1749 &task_s2_pgid,
1750 );
1751 let _ = ioctl::<i32>(&task_s2, &opened_replica2, TIOCNOTTY, &0);
1752 }
1753
1754 terminal.disassociate_controlling_session();
1756
1757 running.store(false, Ordering::Relaxed);
1758 signal_thread.join().expect("signal_thread join");
1759 getter_thread.join().expect("getter_thread join");
1760 setsid_thread.join().expect("setsid_thread join");
1761 disassoc_thread.join().expect("disassoc_thread join");
1762
1763 assert!(task1.thread_group().controlling_terminal().is_none());
1764 assert!(task_s2.thread_group().controlling_terminal().is_none());
1765 })
1766 .await;
1767 }
1768
1769 #[fuchsia::test]
1770 async fn test_tiocnotty_non_leader_only_detaches_caller() {
1771 spawn_kernel_and_run(async |init| {
1772 let kernel = init.kernel();
1773 tty_device_init(kernel).expect("tty_device_init");
1774
1775 let mut leader = init.clone_task_for_test(0, Some(SIGCHLD));
1776 leader.set_mm_for_test(init.mm().unwrap().clone());
1777 leader.thread_group().setsid().expect("setsid");
1778
1779 let fs = new_pts_fs(kernel);
1780 let _opened_main = open_ptmx_and_unlock(init, &fs).expect("ptmx");
1781 let opened_replica = open_file(&leader, &fs, "0".into()).expect("open replica");
1782 set_controlling_terminal(&leader, &opened_replica, false)
1783 .expect("set controlling terminal");
1784
1785 let mut child = leader.clone_task_for_test(0, Some(SIGCHLD));
1787 child.set_mm_for_test(init.mm().unwrap().clone());
1788 assert!(leader.thread_group().controlling_terminal().is_some());
1789 assert!(child.thread_group().controlling_terminal().is_some());
1790
1791 let terminal = Arc::clone(
1792 &leader.thread_group().controlling_terminal().as_ref().unwrap().terminal,
1793 );
1794
1795 ioctl::<i32>(&child, &opened_replica, TIOCNOTTY, &0)
1798 .expect("non-leader TIOCNOTTY should succeed");
1799 assert!(child.thread_group().controlling_terminal().is_none());
1800 assert!(leader.thread_group().controlling_terminal().is_some());
1801 assert_eq!(terminal.read().controlling_session(), Some(&leader.pid));
1802 assert!(!leader.thread_group().has_signal_queued(SIGHUP));
1803 assert!(!child.thread_group().has_signal_queued(SIGHUP));
1804
1805 assert_eq!(ioctl::<i32>(&child, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1807
1808 ioctl::<i32>(&leader, &opened_replica, TIOCNOTTY, &0)
1810 .expect("leader TIOCNOTTY should succeed");
1811 assert!(leader.thread_group().controlling_terminal().is_none());
1812 assert!(terminal.read().controlling_session().is_none());
1813 })
1814 .await;
1815 }
1816}