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_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_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::testing::*;
917 use starnix_core::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
918 use starnix_core::vfs::fs_args::MountParams;
919 use starnix_core::vfs::{MountInfo, NamespaceNode, OpenAccessCheck};
920 use starnix_uapi::auth::Credentials;
921 use starnix_uapi::file_mode::FileMode;
922 use starnix_uapi::signals::{SIGCHLD, SIGTTOU};
923
924 fn new_pts_fs(kernel: &Kernel) -> FileSystemHandle {
925 let mut options = FileSystemOptions::default();
926 options.params = MountParams::parse("ptmxmode=666".into()).expect("parse option");
927 super::new_pts_fs(&kernel, options).expect("create new_pts_fs")
928 }
929
930 fn ioctl<T: zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable + Copy>(
931 current_task: &CurrentTask,
932 file: &FileHandle,
933 command: u32,
934 value: &T,
935 ) -> Result<T, Errno> {
936 let address =
937 map_memory(current_task, UserAddress::default(), std::mem::size_of::<T>() as u64);
938 let address_ref = UserRef::<T>::new(address);
939 current_task.write_object(address_ref, value)?;
940 file.ioctl(current_task, command, address.into())?;
941 current_task.read_object(address_ref)
942 }
943
944 fn set_controlling_terminal(
945 current_task: &CurrentTask,
946 file: &FileHandle,
947 steal: bool,
948 ) -> Result<SyscallResult, Errno> {
949 #[allow(clippy::bool_to_int_with_if)]
950 file.ioctl(current_task, TIOCSCTTY, steal.into())
951 }
952
953 fn lookup_node(
954 task: &CurrentTask,
955 fs: &FileSystemHandle,
956 name: &FsStr,
957 ) -> Result<NamespaceNode, Errno> {
958 let root = NamespaceNode::new_anonymous(fs.root().clone());
959 root.lookup_child(task, &mut Default::default(), name)
960 }
961
962 fn open_file_with_flags(
963 current_task: &CurrentTask,
964 fs: &FileSystemHandle,
965 name: &FsStr,
966 flags: OpenFlags,
967 ) -> Result<FileHandle, Errno> {
968 let node = lookup_node(current_task, fs, name)?;
969 node.open(current_task, flags)
970 }
971
972 fn open_file(
973 current_task: &CurrentTask,
974 fs: &FileSystemHandle,
975 name: &FsStr,
976 ) -> Result<FileHandle, Errno> {
977 open_file_with_flags(current_task, fs, name, OpenFlags::RDWR | OpenFlags::NOCTTY)
978 }
979
980 fn open_ptmx_and_unlock(
981 current_task: &CurrentTask,
982 fs: &FileSystemHandle,
983 ) -> Result<FileHandle, Errno> {
984 let file = open_file_with_flags(current_task, fs, "ptmx".into(), OpenFlags::RDWR)?;
985
986 ioctl::<i32>(current_task, &file, TIOCSPTLCK, &0)?;
988
989 Ok(file)
990 }
991
992 #[fuchsia::test]
993 async fn opening_ptmx_creates_pts() {
994 spawn_kernel_and_run(async |task| {
995 let kernel = task.kernel();
996 tty_device_init(kernel).expect("tty_device_init");
997 let fs = new_pts_fs(kernel);
998 lookup_node(task, &fs, "0".into()).unwrap_err();
999 let _ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1000 lookup_node(task, &fs, "0".into()).expect("pty");
1001 })
1002 .await;
1003 }
1004
1005 #[fuchsia::test]
1006 async fn closing_ptmx_closes_pts() {
1007 spawn_kernel_and_run(async |task| {
1008 let kernel = task.kernel();
1009 tty_device_init(kernel).expect("tty_device_init");
1010 let fs = new_pts_fs(kernel);
1011 lookup_node(task, &fs, "0".into()).unwrap_err();
1012 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1013 let _pts = open_file(task, &fs, "0".into()).expect("open file");
1014 std::mem::drop(ptmx);
1015 task.trigger_delayed_releaser();
1016 lookup_node(task, &fs, "0".into()).unwrap_err();
1017 })
1018 .await;
1019 }
1020
1021 #[fuchsia::test]
1022 async fn pts_are_reused() {
1023 spawn_kernel_and_run(async |task| {
1024 let kernel = task.kernel();
1025 tty_device_init(kernel).expect("tty_device_init");
1026 let fs = new_pts_fs(kernel);
1027
1028 let _ptmx0 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1029 let mut _ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1030 let _ptmx2 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1031
1032 lookup_node(task, &fs, "0".into()).expect("component_lookup");
1033 lookup_node(task, &fs, "1".into()).expect("component_lookup");
1034 lookup_node(task, &fs, "2".into()).expect("component_lookup");
1035
1036 std::mem::drop(_ptmx1);
1037 task.trigger_delayed_releaser();
1038
1039 lookup_node(task, &fs, "1".into()).unwrap_err();
1040
1041 _ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1042 lookup_node(task, &fs, "1".into()).expect("component_lookup");
1043 })
1044 .await;
1045 }
1046
1047 #[fuchsia::test]
1048 async fn opening_inexistant_replica_fails() {
1049 spawn_kernel_and_run(async |task| {
1050 let kernel = task.kernel();
1051 tty_device_init(kernel).expect("tty_device_init");
1052 new_pts_fs(kernel);
1054 let fs = TmpFs::new_fs(kernel);
1055 let mount = MountInfo::detached();
1056 let pts = fs
1057 .root()
1058 .create_entry(task, &mount, "custom_pts".into(), |dir, mount, name| {
1059 dir.create_node(
1060 task,
1061 mount,
1062 name,
1063 mode!(IFCHR, 0o666),
1064 DeviceId::new(DEVPTS_FIRST_MAJOR, 0),
1065 FsCred::root(),
1066 )
1067 })
1068 .expect("custom_pts");
1069 let node = NamespaceNode::new_anonymous(pts.clone());
1070 assert!(node.open(task, OpenAccessCheck::skip(OpenFlags::RDONLY)).is_err());
1071 })
1072 .await;
1073 }
1074
1075 #[fuchsia::test]
1076 async fn test_open_tty() {
1077 spawn_kernel_and_run(async |task| {
1078 let kernel = task.kernel();
1079 tty_device_init(kernel).expect("tty_device_init");
1080 let fs = new_pts_fs(kernel);
1081 let devfs = starnix_core::fs::devtmpfs::DevTmpFs::from_kernel(kernel);
1082
1083 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1084 set_controlling_terminal(task, &ptmx, false).expect("set_controlling_terminal");
1085 let tty =
1086 open_file_with_flags(task, &devfs, "tty".into(), OpenFlags::RDWR).expect("tty");
1087 assert_eq!(
1090 ioctl::<i32>(task, &tty, TIOCGPTN, &0),
1091 ioctl::<i32>(task, &ptmx, TIOCGPTN, &0)
1092 );
1093
1094 ioctl::<i32>(task, &ptmx, TIOCNOTTY, &0).expect("detach terminal");
1096 let pts = open_file(task, &fs, "0".into()).expect("open file");
1097 set_controlling_terminal(task, &pts, false).expect("set_controlling_terminal");
1098 let tty =
1099 open_file_with_flags(task, &devfs, "tty".into(), OpenFlags::RDWR).expect("tty");
1100 assert!(ioctl::<i32>(task, &tty, TIOCGPTN, &0).is_err());
1102 })
1103 .await;
1104 }
1105
1106 #[fuchsia::test]
1107 async fn test_unknown_ioctl() {
1108 spawn_kernel_and_run(async |task| {
1109 let kernel = task.kernel();
1110 tty_device_init(kernel).expect("tty_device_init");
1111 let fs = new_pts_fs(kernel);
1112
1113 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1114 assert_eq!(ptmx.ioctl(task, 42, Default::default()), error!(ENOTTY));
1115
1116 let pts_file = open_file(task, &fs, "0".into()).expect("open file");
1117 assert_eq!(pts_file.ioctl(task, 42, Default::default()), error!(ENOTTY));
1118 })
1119 .await;
1120 }
1121
1122 #[fuchsia::test]
1123 async fn test_tiocgptn_ioctl() {
1124 spawn_kernel_and_run(async |task| {
1125 let kernel = task.kernel();
1126 tty_device_init(kernel).expect("tty_device_init");
1127 let fs = new_pts_fs(kernel);
1128 let ptmx0 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1129 let ptmx1 = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1130
1131 let pts0 = ioctl::<u32>(task, &ptmx0, TIOCGPTN, &0).expect("ioctl");
1132 assert_eq!(pts0, 0);
1133
1134 let pts1 = ioctl::<u32>(task, &ptmx1, TIOCGPTN, &0).expect("ioctl");
1135 assert_eq!(pts1, 1);
1136 })
1137 .await;
1138 }
1139
1140 #[fuchsia::test]
1141 async fn test_new_terminal_is_locked() {
1142 spawn_kernel_and_run(async |task| {
1143 let kernel = task.kernel();
1144 tty_device_init(kernel).expect("tty_device_init");
1145 let fs = new_pts_fs(kernel);
1146 let _ptmx_file = open_file(task, &fs, "ptmx".into()).expect("open file");
1147
1148 let pts = lookup_node(task, &fs, "0".into()).expect("component_lookup");
1149 assert_eq!(pts.open(task, OpenFlags::RDONLY).map(|_| ()), error!(EIO));
1150 })
1151 .await;
1152 }
1153
1154 #[fuchsia::test]
1155 async fn test_lock_ioctls() {
1156 spawn_kernel_and_run(async |task| {
1157 let kernel = task.kernel();
1158 tty_device_init(kernel).expect("tty_device_init");
1159 let fs = new_pts_fs(kernel);
1160 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1161 let pts = lookup_node(task, &fs, "0".into()).expect("component_lookup");
1162
1163 assert_eq!(ioctl::<i32>(task, &ptmx, TIOCGPTLCK, &0), Ok(0));
1165 pts.open(task, OpenFlags::RDONLY).expect("open");
1167
1168 ioctl::<i32>(task, &ptmx, TIOCSPTLCK, &42).expect("ioctl");
1170 assert_eq!(ioctl::<i32>(task, &ptmx, TIOCGPTLCK, &0), Ok(1));
1172 assert_eq!(pts.open(task, OpenFlags::RDONLY).map(|_| ()), error!(EIO));
1174 })
1175 .await;
1176 }
1177
1178 #[fuchsia::test]
1179 async fn test_ptmx_stats() {
1180 spawn_kernel_and_run(async |task| {
1181 let kernel = task.kernel();
1182 tty_device_init(kernel).expect("tty_device_init");
1183 task.set_creds(Credentials::with_ids(22, 22));
1184 let fs = new_pts_fs(kernel);
1185 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1186 let ptmx_stat = ptmx.node().stat(task).expect("stat");
1187 assert_eq!(ptmx_stat.st_blksize as usize, BLOCK_SIZE);
1188 let pts = open_file(task, &fs, "0".into()).expect("open file");
1189 let pts_stats = pts.node().stat(task).expect("stat");
1190 assert_eq!(pts_stats.st_mode & FileMode::PERMISSIONS.bits(), 0o600);
1191 assert_eq!(pts_stats.st_uid, 22);
1192 })
1194 .await;
1195 }
1196
1197 #[fuchsia::test]
1198 async fn test_attach_terminal_when_open() {
1199 spawn_kernel_and_run(async |task| {
1200 let kernel = task.kernel();
1201 tty_device_init(kernel).expect("tty_device_init");
1202 let fs = new_pts_fs(kernel);
1203 let _opened_main = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1204 assert!(task.thread_group().controlling_terminal().is_none());
1206 let _opened_replica2 =
1208 open_file_with_flags(task, &fs, "0".into(), OpenFlags::RDWR | OpenFlags::NOCTTY)
1209 .expect("open file");
1210 assert!(task.thread_group().controlling_terminal().is_none());
1211
1212 let _opened_replica2 =
1214 open_file_with_flags(task, &fs, "0".into(), OpenFlags::RDWR).expect("open file");
1215 assert!(task.thread_group().controlling_terminal().is_some());
1216 })
1217 .await;
1218 }
1219
1220 #[fuchsia::test]
1221 async fn test_attach_terminal() {
1222 spawn_kernel_and_run(async |task1| {
1223 let kernel = task1.kernel();
1224 tty_device_init(kernel).expect("tty_device_init");
1225 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1226 task2.thread_group().setsid().expect("setsid");
1227
1228 let fs = new_pts_fs(kernel);
1229 let opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1230 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1231
1232 assert_eq!(ioctl::<i32>(task1, &opened_main, TIOCGPGRP, &0), error!(ENOTTY));
1233 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1234
1235 set_controlling_terminal(task1, &opened_main, false).unwrap();
1236 assert_eq!(
1237 ioctl::<i32>(task1, &opened_main, TIOCGPGRP, &0),
1238 Ok(task1.thread_group().read().process_group.id)
1239 );
1240 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1241
1242 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1244 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1245 })
1246 .await;
1247 }
1248
1249 #[fuchsia::test]
1250 async fn test_steal_terminal() {
1251 spawn_kernel_and_run(async |task1| {
1252 let kernel = task1.kernel();
1253 tty_device_init(kernel).expect("tty_device_init");
1254 task1.set_creds(Credentials::with_ids(1, 1));
1255
1256 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1257
1258 let fs = new_pts_fs(kernel);
1259 let _opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1260 let wo_opened_replica =
1261 open_file_with_flags(task1, &fs, "0".into(), OpenFlags::WRONLY | OpenFlags::NOCTTY)
1262 .expect("open file");
1263 assert!(!wo_opened_replica.can_read());
1264
1265 assert_eq!(set_controlling_terminal(task1, &wo_opened_replica, false), error!(EPERM));
1267
1268 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1269 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1271
1272 set_controlling_terminal(task1, &opened_replica, false)
1274 .expect("Associate terminal to task1");
1275
1276 set_controlling_terminal(task1, &opened_replica, false)
1278 .expect("Redundant association should succeed");
1279
1280 task2.thread_group().setsid().expect("setsid");
1281
1282 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1284
1285 assert_eq!(set_controlling_terminal(&task2, &opened_replica, true), error!(EPERM));
1287
1288 task2.set_creds(Credentials::with_ids(0, 0));
1290 assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), error!(EPERM));
1292 set_controlling_terminal(&task2, &opened_replica, true)
1293 .expect("Associate terminal to task2");
1294
1295 assert!(task1.thread_group().controlling_terminal().is_none());
1296
1297 let _opened_main2 = open_ptmx_and_unlock(task1, &fs).expect("ptmx 1");
1300 let opened_replica2 = open_file(task1, &fs, "1".into()).expect("open file 1");
1301 set_controlling_terminal(task1, &opened_replica2, false)
1302 .expect("Associate second terminal to task1");
1303 assert!(task1.thread_group().controlling_terminal().is_some());
1304 assert_eq!(set_controlling_terminal(&task2, &opened_replica2, true), error!(EPERM));
1305 assert!(task1.thread_group().controlling_terminal().is_some());
1306 })
1307 .await;
1308 }
1309
1310 #[fuchsia::test]
1311 async fn test_tiocgsid() {
1312 spawn_kernel_and_run(async |task1| {
1313 let kernel = task1.kernel();
1314 tty_device_init(kernel).expect("tty_device_init");
1315 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1316 task2.running_state().mm.update(Some(task1.mm().unwrap().clone()));
1317 task2.thread_group().setsid().expect("setsid");
1318
1319 let fs = new_pts_fs(kernel);
1320 let opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1321 let opened_replica = open_file(&task2, &fs, "0".into()).expect("open file");
1322
1323 assert_eq!(ioctl::<pid_t>(task1, &opened_main, TIOCGSID, &0), error!(ENOTTY));
1325 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1326
1327 set_controlling_terminal(&task2, &opened_replica, false)
1329 .expect("set controlling terminal");
1330 let expected_sid = task2.thread_group().read().session.id;
1331
1332 assert_eq!(ioctl::<pid_t>(&task2, &opened_main, TIOCGSID, &0), Ok(expected_sid));
1334 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), Ok(expected_sid));
1335
1336 assert_eq!(ioctl::<pid_t>(task1, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1339 assert_eq!(ioctl::<pid_t>(task1, &opened_main, TIOCGSID, &0), Ok(expected_sid));
1340
1341 ioctl::<i32>(&task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1343 assert_eq!(ioctl::<pid_t>(&task2, &opened_main, TIOCGSID, &0), error!(ENOTTY));
1344 assert_eq!(ioctl::<pid_t>(&task2, &opened_replica, TIOCGSID, &0), error!(ENOTTY));
1345 })
1346 .await;
1347 }
1348
1349 #[fuchsia::test]
1350 async fn test_set_foreground_process() {
1351 spawn_kernel_and_run(async |init| {
1352 let kernel = init.kernel();
1353 tty_device_init(kernel).expect("tty_device_init");
1354 let task1 = init.clone_task_for_test(0, Some(SIGCHLD));
1355 task1.running_state().mm.update(Some(init.mm().unwrap().clone()));
1358 task1.thread_group().setsid().expect("setsid");
1359 let pre_existing_task = task1.clone_task_for_test(0, Some(SIGCHLD));
1360
1361 let fs = new_pts_fs(kernel);
1362 let _opened_main = open_ptmx_and_unlock(init, &fs).expect("ptmx");
1363 let opened_replica = open_file(&pre_existing_task, &fs, "0".into()).expect("open file");
1364
1365 assert_eq!(
1368 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task1.pid.id),
1369 error!(ENOTTY)
1370 );
1371
1372 set_controlling_terminal(&task1, &opened_replica, false).unwrap();
1375 assert!(pre_existing_task.thread_group().controlling_terminal().is_none());
1376
1377 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1378 task2.running_state().mm.update(Some(init.mm().unwrap().clone()));
1379 task2.thread_group().setpgid(&task2, &task2, &task2.pid).expect("setpgid");
1380 let task2_pgid = task2.thread_group().read().process_group.clone();
1381 assert_ne!(task2_pgid, task1.thread_group().read().process_group);
1382
1383 assert_eq!(
1385 ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0),
1386 Ok(task1.thread_group().read().process_group.id)
1387 );
1388
1389 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &-1), error!(EINVAL));
1391
1392 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &255), error!(ESRCH));
1394
1395 let init_pgid = init.thread_group().read().process_group.clone();
1397 assert_eq!(
1398 ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &init_pgid.id),
1399 error!(EPERM)
1400 );
1401
1402 assert_eq!(
1404 ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &task2_pgid.id),
1405 error!(EINTR)
1406 );
1407 assert!(task2.read().has_signal_pending(SIGTTOU));
1408
1409 ioctl::<i32>(&task1, &opened_replica, TIOCSPGRP, &task2_pgid.id).unwrap();
1411
1412 let terminal =
1414 Arc::clone(&task1.thread_group().controlling_terminal().as_ref().unwrap().terminal);
1415 assert_eq!(
1416 terminal.read().controlling_session.as_ref().unwrap().foreground_process_group(),
1417 &task2_pgid
1418 );
1419 })
1420 .await;
1421 }
1422
1423 #[fuchsia::test]
1424 async fn test_detach_session() {
1425 spawn_kernel_and_run(async |task1| {
1426 let kernel = task1.kernel();
1427 tty_device_init(kernel).expect("tty_device_init");
1428 let task2 = task1.clone_task_for_test(0, Some(SIGCHLD));
1429 task2.thread_group().setsid().expect("setsid");
1430
1431 let fs = new_pts_fs(kernel);
1432 let _opened_main = open_ptmx_and_unlock(task1, &fs).expect("ptmx");
1433 let opened_replica = open_file(task1, &fs, "0".into()).expect("open file");
1434
1435 assert_eq!(ioctl::<i32>(task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1437
1438 set_controlling_terminal(&task2, &opened_replica, false)
1439 .expect("set controlling terminal");
1440
1441 assert_eq!(ioctl::<i32>(task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1443
1444 let task3 = task2.clone_task_for_test(0, Some(SIGCHLD));
1447 task3.running_state().mm.update(Some(task1.mm().unwrap().clone()));
1448 task2.running_state().mm.update(Some(task1.mm().unwrap().clone()));
1449 assert!(task3.thread_group().controlling_terminal().is_some());
1450 assert_eq!(ioctl::<i32>(&task3, &opened_replica, TIOCGPGRP, &0), Ok(task2.pid.id));
1451
1452 ioctl::<i32>(&task3, &opened_replica, TIOCNOTTY, &0)
1453 .expect("non-leader detach terminal");
1454 assert!(task3.thread_group().controlling_terminal().is_none());
1455 assert!(task2.thread_group().controlling_terminal().is_some());
1456 assert_eq!(ioctl::<i32>(&task3, &opened_replica, TIOCGPGRP, &0), error!(ENOTTY));
1457 assert_eq!(
1458 ioctl::<i32>(&task3, &opened_replica, TIOCSPGRP, &task2.pid.id),
1459 error!(ENOTTY)
1460 );
1461 assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), Ok(task2.pid.id));
1462
1463 ioctl::<i32>(&task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1465 assert!(task2.thread_group().controlling_terminal().is_none());
1466 })
1467 .await;
1468 }
1469
1470 #[fuchsia::test]
1471 async fn test_send_data_back_and_forth() {
1472 spawn_kernel_and_run(async |task| {
1473 let kernel = task.kernel();
1474 tty_device_init(kernel).expect("tty_device_init");
1475 let fs = new_pts_fs(kernel);
1476 let ptmx = open_ptmx_and_unlock(task, &fs).expect("ptmx");
1477 let pts = open_file(task, &fs, "0".into()).expect("open file");
1478
1479 let has_data_ready_to_read = |fd: &FileHandle| {
1480 fd.query_events(task).expect("query_events").contains(FdEvents::POLLIN)
1481 };
1482
1483 let write_and_assert = |fd: &FileHandle, data: &[u8]| {
1484 assert_eq!(
1485 fd.write(task, &mut VecInputBuffer::new(data)).expect("write"),
1486 data.len()
1487 );
1488 };
1489
1490 let read_and_check = |fd: &FileHandle, data: &[u8]| {
1491 assert!(has_data_ready_to_read(fd));
1492 let mut buffer = VecOutputBuffer::new(data.len() + 1);
1493 assert_eq!(fd.read(task, &mut buffer).expect("read"), data.len());
1494 assert_eq!(data, buffer.data());
1495 };
1496
1497 let hello_buffer = b"hello\n";
1498 let hello_transformed_buffer = b"hello\r\n";
1499
1500 write_and_assert(&ptmx, hello_buffer);
1502 read_and_check(&pts, hello_buffer);
1503
1504 read_and_check(&ptmx, hello_transformed_buffer);
1506
1507 write_and_assert(&pts, hello_buffer);
1509 read_and_check(&ptmx, hello_transformed_buffer);
1510
1511 assert!(!has_data_ready_to_read(&pts));
1513 })
1514 .await;
1515 }
1516}