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