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