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_type::{DeviceType, 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(), DeviceType::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(), DeviceType::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) -> DeviceType {
218 DeviceType::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 = DeviceType::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: DeviceType,
295 node: &NamespaceNode,
296 flags: OpenFlags,
297) -> Result<Box<dyn FileOps>, Errno> {
298 match id {
299 DeviceType::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 DeviceType::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 terminal.write().line_discipline.window_size =
684 current_task.read_object(UserRef::<uapi::winsize>::new(user_addr))?;
685
686 let foreground_process_group =
688 terminal.read().controller.as_ref().and_then(|terminal_controller| {
689 terminal_controller.get_foreground_process_group()
690 });
691 if let Some(process_group) = foreground_process_group {
692 process_group.send_signals(locked, &[SIGWINCH]);
693 }
694 Ok(SUCCESS)
695 }
696 TCGETA => {
697 let termio = into_termio(*terminal.read().termios());
698 current_task.write_object(UserRef::<uapi::termio>::new(user_addr), &termio)?;
699 Ok(SUCCESS)
700 }
701 TCGETS => {
702 current_task.write_object(
705 UserRef::<uapi::termios>::new(user_addr),
706 terminal.read().termios(),
707 )?;
708 Ok(SUCCESS)
709 }
710 TCSETA => {
711 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
712 terminal.set_termios(locked, into_termios(termio));
713 Ok(SUCCESS)
714 }
715 TCSETS => {
716 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
719 terminal.set_termios(locked, termios);
720 Ok(SUCCESS)
721 }
722 TCSETAF => {
723 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
725 terminal.set_termios(locked, into_termios(termio));
726 Ok(SUCCESS)
727 }
728 TCSETSF => {
729 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
731 terminal.set_termios(locked, termios);
732 Ok(SUCCESS)
733 }
734 TCSETAW => {
735 track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETAW drain output queue first");
736 let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
737 terminal.set_termios(locked, into_termios(termio));
738 Ok(SUCCESS)
739 }
740 TCSETSW => {
741 track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETSW drain output queue first");
742 let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
743 terminal.set_termios(locked, termios);
744 Ok(SUCCESS)
745 }
746 TIOCSETD => {
747 track_stub!(
748 TODO("https://fxbug.dev/322874060"),
749 "devpts setting line discipline",
750 is_main
751 );
752 error!(EINVAL)
753 }
754 TCSBRK => Ok(SUCCESS),
755 TCXONC => {
756 track_stub!(TODO("https://fxbug.dev/322892912"), "devpts ioctl TCXONC", is_main);
757 error!(ENOSYS)
758 }
759 TCFLSH => {
760 track_stub!(TODO("https://fxbug.dev/322893703"), "devpts ioctl TCFLSH", is_main);
761 error!(ENOSYS)
762 }
763 TIOCEXCL => {
764 track_stub!(TODO("https://fxbug.dev/322893449"), "devpts ioctl TIOCEXCL", is_main);
765 error!(ENOSYS)
766 }
767 TIOCNXCL => {
768 track_stub!(TODO("https://fxbug.dev/322893393"), "devpts ioctl TIOCNXCL", is_main);
769 error!(ENOSYS)
770 }
771 TIOCOUTQ => {
772 track_stub!(TODO("https://fxbug.dev/322893723"), "devpts ioctl TIOCOUTQ", is_main);
773 error!(ENOSYS)
774 }
775 TIOCSTI => {
776 track_stub!(TODO("https://fxbug.dev/322893780"), "devpts ioctl TIOCSTI", is_main);
777 error!(ENOSYS)
778 }
779 TIOCMGET => {
780 track_stub!(TODO("https://fxbug.dev/322893681"), "devpts ioctl TIOCMGET", is_main);
781 error!(ENOSYS)
782 }
783 TIOCMBIS => {
784 track_stub!(TODO("https://fxbug.dev/322893709"), "devpts ioctl TIOCMBIS", is_main);
785 error!(ENOSYS)
786 }
787 TIOCMBIC => {
788 track_stub!(TODO("https://fxbug.dev/322893610"), "devpts ioctl TIOCMBIC", is_main);
789 error!(ENOSYS)
790 }
791 TIOCMSET => {
792 track_stub!(TODO("https://fxbug.dev/322893211"), "devpts ioctl TIOCMSET", is_main);
793 error!(ENOSYS)
794 }
795 TIOCGSOFTCAR => {
796 track_stub!(TODO("https://fxbug.dev/322893365"), "devpts ioctl TIOCGSOFTCAR", is_main);
797 error!(ENOSYS)
798 }
799 TIOCSSOFTCAR => {
800 track_stub!(TODO("https://fxbug.dev/322894074"), "devpts ioctl TIOCSSOFTCAR", is_main);
801 error!(ENOSYS)
802 }
803 TIOCLINUX => {
804 track_stub!(TODO("https://fxbug.dev/322893147"), "devpts ioctl TIOCLINUX", is_main);
805 error!(ENOSYS)
806 }
807 TIOCCONS => {
808 track_stub!(TODO("https://fxbug.dev/322893267"), "devpts ioctl TIOCCONS", is_main);
809 error!(ENOSYS)
810 }
811 TIOCGSERIAL => {
812 track_stub!(TODO("https://fxbug.dev/322893503"), "devpts ioctl TIOCGSERIAL", is_main);
813 error!(ENOSYS)
814 }
815 TIOCSSERIAL => {
816 track_stub!(TODO("https://fxbug.dev/322893663"), "devpts ioctl TIOCSSERIAL", is_main);
817 error!(ENOSYS)
818 }
819 TIOCPKT => {
820 track_stub!(TODO("https://fxbug.dev/322893148"), "devpts ioctl TIOCPKT", is_main);
821 error!(ENOSYS)
822 }
823 FIONBIO => {
824 track_stub!(TODO("https://fxbug.dev/322893957"), "devpts ioctl FIONBIO", is_main);
825 error!(ENOSYS)
826 }
827 TIOCGETD => {
828 track_stub!(TODO("https://fxbug.dev/322893974"), "devpts ioctl TIOCGETD", is_main);
829 error!(ENOSYS)
830 }
831 TCSBRKP => Ok(SUCCESS),
832 TIOCSBRK => {
833 track_stub!(TODO("https://fxbug.dev/322893936"), "devpts ioctl TIOCSBRK", is_main);
834 error!(ENOSYS)
835 }
836 TIOCCBRK => {
837 track_stub!(TODO("https://fxbug.dev/322893213"), "devpts ioctl TIOCCBRK", is_main);
838 error!(ENOSYS)
839 }
840 TIOCGSID => {
841 track_stub!(TODO("https://fxbug.dev/322894076"), "devpts ioctl TIOCGSID", is_main);
842 error!(ENOSYS)
843 }
844 TIOCGRS485 => {
845 track_stub!(TODO("https://fxbug.dev/322893728"), "devpts ioctl TIOCGRS485", is_main);
846 error!(ENOSYS)
847 }
848 TIOCSRS485 => {
849 track_stub!(TODO("https://fxbug.dev/322893783"), "devpts ioctl TIOCSRS485", is_main);
850 error!(ENOSYS)
851 }
852 TCGETX => {
853 track_stub!(TODO("https://fxbug.dev/322893327"), "devpts ioctl TCGETX", is_main);
854 error!(ENOSYS)
855 }
856 TCSETX => {
857 track_stub!(TODO("https://fxbug.dev/322893741"), "devpts ioctl TCSETX", is_main);
858 error!(ENOSYS)
859 }
860 TCSETXF => {
861 track_stub!(TODO("https://fxbug.dev/322893937"), "devpts ioctl TCSETXF", is_main);
862 error!(ENOSYS)
863 }
864 TCSETXW => {
865 track_stub!(TODO("https://fxbug.dev/322893899"), "devpts ioctl TCSETXW", is_main);
866 error!(ENOSYS)
867 }
868 TIOCVHANGUP => {
869 track_stub!(TODO("https://fxbug.dev/322893742"), "devpts ioctl TIOCVHANGUP", is_main);
870 error!(ENOSYS)
871 }
872 FIONCLEX => {
873 track_stub!(TODO("https://fxbug.dev/322893938"), "devpts ioctl FIONCLEX", is_main);
874 error!(ENOSYS)
875 }
876 FIOCLEX => {
877 track_stub!(TODO("https://fxbug.dev/322894214"), "devpts ioctl FIOCLEX", is_main);
878 error!(ENOSYS)
879 }
880 FIOASYNC => {
881 track_stub!(TODO("https://fxbug.dev/322893269"), "devpts ioctl FIOASYNC", is_main);
882 error!(ENOSYS)
883 }
884 TIOCSERCONFIG => {
885 track_stub!(TODO("https://fxbug.dev/322893881"), "devpts ioctl TIOCSERCONFIG", is_main);
886 error!(ENOSYS)
887 }
888 TIOCSERGWILD => {
889 track_stub!(TODO("https://fxbug.dev/322893686"), "devpts ioctl TIOCSERGWILD", is_main);
890 error!(ENOSYS)
891 }
892 TIOCSERSWILD => {
893 track_stub!(TODO("https://fxbug.dev/322893837"), "devpts ioctl TIOCSERSWILD", is_main);
894 error!(ENOSYS)
895 }
896 TIOCGLCKTRMIOS => {
897 track_stub!(
898 TODO("https://fxbug.dev/322894114"),
899 "devpts ioctl TIOCGLCKTRMIOS",
900 is_main
901 );
902 error!(ENOSYS)
903 }
904 TIOCSLCKTRMIOS => {
905 track_stub!(
906 TODO("https://fxbug.dev/322893711"),
907 "devpts ioctl TIOCSLCKTRMIOS",
908 is_main
909 );
910 error!(ENOSYS)
911 }
912 TIOCSERGSTRUCT => {
913 track_stub!(
914 TODO("https://fxbug.dev/322893828"),
915 "devpts ioctl TIOCSERGSTRUCT",
916 is_main
917 );
918 error!(ENOSYS)
919 }
920 TIOCSERGETLSR => {
921 track_stub!(TODO("https://fxbug.dev/322894083"), "devpts ioctl TIOCSERGETLSR", is_main);
922 error!(ENOSYS)
923 }
924 TIOCSERGETMULTI => {
925 track_stub!(
926 TODO("https://fxbug.dev/322893962"),
927 "devpts ioctl TIOCSERGETMULTI",
928 is_main
929 );
930 error!(ENOSYS)
931 }
932 TIOCSERSETMULTI => {
933 track_stub!(
934 TODO("https://fxbug.dev/322893273"),
935 "devpts ioctl TIOCSERSETMULTI",
936 is_main
937 );
938 error!(ENOSYS)
939 }
940 TIOCMIWAIT => {
941 track_stub!(TODO("https://fxbug.dev/322894005"), "devpts ioctl TIOCMIWAIT", is_main);
942 error!(ENOSYS)
943 }
944 TIOCGICOUNT => {
945 track_stub!(TODO("https://fxbug.dev/322893862"), "devpts ioctl TIOCGICOUNT", is_main);
946 error!(ENOSYS)
947 }
948 FIOQSIZE => {
949 track_stub!(TODO("https://fxbug.dev/322893770"), "devpts ioctl FIOQSIZE", is_main);
950 error!(ENOSYS)
951 }
952 other => {
953 track_stub!(TODO("https://fxbug.dev/322893712"), "devpts unknown ioctl", other);
954 error!(ENOTTY)
955 }
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use super::*;
962 use crate::fs::devpts::tty_device_init;
963 use crate::fs::tmpfs::TmpFs;
964 use crate::testing::*;
965 use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
966 use crate::vfs::fs_args::MountParams;
967 use crate::vfs::{MountInfo, NamespaceNode};
968 use starnix_uapi::auth::Credentials;
969 use starnix_uapi::file_mode::{AccessCheck, FileMode};
970 use starnix_uapi::signals::{SIGCHLD, SIGTTOU};
971
972 fn new_pts_fs(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> FileSystemHandle {
973 let mut options = FileSystemOptions::default();
974 options.params = MountParams::parse("ptmxmode=666".into()).expect("parse option");
975 super::new_pts_fs(locked, &kernel, options).expect("create new_pts_fs")
976 }
977
978 fn ioctl<T: zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable + Copy>(
979 locked: &mut Locked<Unlocked>,
980 current_task: &CurrentTask,
981 file: &FileHandle,
982 command: u32,
983 value: &T,
984 ) -> Result<T, Errno> {
985 let address = map_memory(
986 locked,
987 current_task,
988 UserAddress::default(),
989 std::mem::size_of::<T>() as u64,
990 );
991 let address_ref = UserRef::<T>::new(address);
992 current_task.write_object(address_ref, value)?;
993 file.ioctl(locked, current_task, command, address.into())?;
994 current_task.read_object(address_ref)
995 }
996
997 fn set_controlling_terminal(
998 locked: &mut Locked<Unlocked>,
999 current_task: &CurrentTask,
1000 file: &FileHandle,
1001 steal: bool,
1002 ) -> Result<SyscallResult, Errno> {
1003 #[allow(clippy::bool_to_int_with_if)]
1004 file.ioctl(locked, current_task, TIOCSCTTY, steal.into())
1005 }
1006
1007 fn lookup_node<L>(
1008 locked: &mut Locked<L>,
1009 task: &CurrentTask,
1010 fs: &FileSystemHandle,
1011 name: &FsStr,
1012 ) -> Result<NamespaceNode, Errno>
1013 where
1014 L: LockEqualOrBefore<FileOpsCore>,
1015 {
1016 let root = NamespaceNode::new_anonymous(fs.root().clone());
1017 root.lookup_child(locked, task, &mut Default::default(), name)
1018 }
1019
1020 fn open_file_with_flags(
1021 locked: &mut Locked<Unlocked>,
1022 current_task: &CurrentTask,
1023 fs: &FileSystemHandle,
1024 name: &FsStr,
1025 flags: OpenFlags,
1026 ) -> Result<FileHandle, Errno> {
1027 let node = lookup_node(locked, current_task, fs, name)?;
1028 node.open(locked, current_task, flags, AccessCheck::default())
1029 }
1030
1031 fn open_file(
1032 locked: &mut Locked<Unlocked>,
1033 current_task: &CurrentTask,
1034 fs: &FileSystemHandle,
1035 name: &FsStr,
1036 ) -> Result<FileHandle, Errno> {
1037 open_file_with_flags(locked, current_task, fs, name, OpenFlags::RDWR | OpenFlags::NOCTTY)
1038 }
1039
1040 fn open_ptmx_and_unlock(
1041 locked: &mut Locked<Unlocked>,
1042 current_task: &CurrentTask,
1043 fs: &FileSystemHandle,
1044 ) -> Result<FileHandle, Errno> {
1045 let file = open_file_with_flags(locked, current_task, fs, "ptmx".into(), OpenFlags::RDWR)?;
1046
1047 ioctl::<i32>(locked, current_task, &file, TIOCSPTLCK, &0)?;
1049
1050 Ok(file)
1051 }
1052
1053 #[fuchsia::test]
1054 async fn opening_ptmx_creates_pts() {
1055 spawn_kernel_and_run(async |locked, task| {
1056 let kernel = task.kernel();
1057 tty_device_init(locked, &*task).expect("tty_device_init");
1058 let fs = new_pts_fs(locked, kernel);
1059 lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1060 let _ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1061 lookup_node(locked, task, &fs, "0".into()).expect("pty");
1062 })
1063 .await;
1064 }
1065
1066 #[fuchsia::test]
1067 async fn closing_ptmx_closes_pts() {
1068 spawn_kernel_and_run(async |locked, task| {
1069 let kernel = task.kernel();
1070 tty_device_init(locked, &*task).expect("tty_device_init");
1071 let fs = new_pts_fs(locked, kernel);
1072 lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1073 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1074 let _pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1075 std::mem::drop(ptmx);
1076 task.trigger_delayed_releaser(locked);
1077 lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1078 })
1079 .await;
1080 }
1081
1082 #[fuchsia::test]
1083 async fn pts_are_reused() {
1084 spawn_kernel_and_run(async |locked, task| {
1085 let kernel = task.kernel();
1086 tty_device_init(locked, &*task).expect("tty_device_init");
1087 let fs = new_pts_fs(locked, kernel);
1088
1089 let _ptmx0 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1090 let mut _ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1091 let _ptmx2 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1092
1093 lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1094 lookup_node(locked, task, &fs, "1".into()).expect("component_lookup");
1095 lookup_node(locked, task, &fs, "2".into()).expect("component_lookup");
1096
1097 std::mem::drop(_ptmx1);
1098 task.trigger_delayed_releaser(locked);
1099
1100 lookup_node(locked, task, &fs, "1".into()).unwrap_err();
1101
1102 _ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1103 lookup_node(locked, task, &fs, "1".into()).expect("component_lookup");
1104 })
1105 .await;
1106 }
1107
1108 #[fuchsia::test]
1109 async fn opening_inexistant_replica_fails() {
1110 spawn_kernel_and_run(async |locked, task| {
1111 let kernel = task.kernel();
1112 tty_device_init(locked, &*task).expect("tty_device_init");
1113 new_pts_fs(locked, kernel);
1115 let fs = TmpFs::new_fs(locked, kernel);
1116 let mount = MountInfo::detached();
1117 let pts = fs
1118 .root()
1119 .create_entry(
1120 locked,
1121 task,
1122 &mount,
1123 "custom_pts".into(),
1124 |locked, dir, mount, name| {
1125 dir.create_node(
1126 locked,
1127 task,
1128 mount,
1129 name,
1130 mode!(IFCHR, 0o666),
1131 DeviceType::new(DEVPTS_FIRST_MAJOR, 0),
1132 FsCred::root(),
1133 )
1134 },
1135 )
1136 .expect("custom_pts");
1137 let node = NamespaceNode::new_anonymous(pts.clone());
1138 assert!(node.open(locked, task, OpenFlags::RDONLY, AccessCheck::skip()).is_err());
1139 })
1140 .await;
1141 }
1142
1143 #[fuchsia::test]
1144 async fn test_open_tty() {
1145 spawn_kernel_and_run(async |locked, task| {
1146 let kernel = task.kernel();
1147 tty_device_init(locked, &*task).expect("tty_device_init");
1148 let fs = new_pts_fs(locked, kernel);
1149 let devfs = crate::fs::devtmpfs::DevTmpFs::from_kernel(locked, kernel);
1150
1151 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1152 set_controlling_terminal(locked, task, &ptmx, false).expect("set_controlling_terminal");
1153 let tty = open_file_with_flags(locked, task, &devfs, "tty".into(), OpenFlags::RDWR)
1154 .expect("tty");
1155 assert_eq!(
1158 ioctl::<i32>(locked, task, &tty, TIOCGPTN, &0),
1159 ioctl::<i32>(locked, task, &ptmx, TIOCGPTN, &0)
1160 );
1161
1162 ioctl::<i32>(locked, task, &ptmx, TIOCNOTTY, &0).expect("detach terminal");
1164 let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1165 set_controlling_terminal(locked, task, &pts, false).expect("set_controlling_terminal");
1166 let tty = open_file_with_flags(locked, task, &devfs, "tty".into(), OpenFlags::RDWR)
1167 .expect("tty");
1168 assert!(ioctl::<i32>(locked, task, &tty, TIOCGPTN, &0).is_err());
1170 })
1171 .await;
1172 }
1173
1174 #[fuchsia::test]
1175 async fn test_unknown_ioctl() {
1176 spawn_kernel_and_run(async |locked, task| {
1177 let kernel = task.kernel();
1178 tty_device_init(locked, &*task).expect("tty_device_init");
1179 let fs = new_pts_fs(locked, kernel);
1180
1181 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1182 assert_eq!(ptmx.ioctl(locked, task, 42, Default::default()), error!(ENOTTY));
1183
1184 let pts_file = open_file(locked, task, &fs, "0".into()).expect("open file");
1185 assert_eq!(pts_file.ioctl(locked, task, 42, Default::default()), error!(ENOTTY));
1186 })
1187 .await;
1188 }
1189
1190 #[fuchsia::test]
1191 async fn test_tiocgptn_ioctl() {
1192 spawn_kernel_and_run(async |locked, task| {
1193 let kernel = task.kernel();
1194 tty_device_init(locked, &*task).expect("tty_device_init");
1195 let fs = new_pts_fs(locked, kernel);
1196 let ptmx0 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1197 let ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1198
1199 let pts0 = ioctl::<u32>(locked, task, &ptmx0, TIOCGPTN, &0).expect("ioctl");
1200 assert_eq!(pts0, 0);
1201
1202 let pts1 = ioctl::<u32>(locked, task, &ptmx1, TIOCGPTN, &0).expect("ioctl");
1203 assert_eq!(pts1, 1);
1204 })
1205 .await;
1206 }
1207
1208 #[fuchsia::test]
1209 async fn test_new_terminal_is_locked() {
1210 spawn_kernel_and_run(async |locked, task| {
1211 let kernel = task.kernel();
1212 tty_device_init(locked, &*task).expect("tty_device_init");
1213 let fs = new_pts_fs(locked, kernel);
1214 let _ptmx_file = open_file(locked, task, &fs, "ptmx".into()).expect("open file");
1215
1216 let pts = lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1217 assert_eq!(
1218 pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).map(|_| ()),
1219 error!(EIO)
1220 );
1221 })
1222 .await;
1223 }
1224
1225 #[fuchsia::test]
1226 async fn test_lock_ioctls() {
1227 spawn_kernel_and_run(async |locked, task| {
1228 let kernel = task.kernel();
1229 tty_device_init(locked, &*task).expect("tty_device_init");
1230 let fs = new_pts_fs(locked, kernel);
1231 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1232 let pts = lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1233
1234 assert_eq!(ioctl::<i32>(locked, task, &ptmx, TIOCGPTLCK, &0), Ok(0));
1236 pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).expect("open");
1238
1239 ioctl::<i32>(locked, task, &ptmx, TIOCSPTLCK, &42).expect("ioctl");
1241 assert_eq!(ioctl::<i32>(locked, task, &ptmx, TIOCGPTLCK, &0), Ok(1));
1243 assert_eq!(
1245 pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).map(|_| ()),
1246 error!(EIO)
1247 );
1248 })
1249 .await;
1250 }
1251
1252 #[fuchsia::test]
1253 async fn test_ptmx_stats() {
1254 spawn_kernel_and_run(async |locked, task| {
1255 let kernel = task.kernel();
1256 tty_device_init(locked, &*task).expect("tty_device_init");
1257 task.set_creds(Credentials::with_ids(22, 22));
1258 let fs = new_pts_fs(locked, kernel);
1259 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1260 let ptmx_stat = ptmx.node().stat(locked, task).expect("stat");
1261 assert_eq!(ptmx_stat.st_blksize as usize, BLOCK_SIZE);
1262 let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1263 let pts_stats = pts.node().stat(locked, task).expect("stat");
1264 assert_eq!(pts_stats.st_mode & FileMode::PERMISSIONS.bits(), 0o600);
1265 assert_eq!(pts_stats.st_uid, 22);
1266 })
1268 .await;
1269 }
1270
1271 #[fuchsia::test]
1272 async fn test_attach_terminal_when_open() {
1273 spawn_kernel_and_run(async |locked, task| {
1274 let kernel = task.kernel();
1275 tty_device_init(locked, &*task).expect("tty_device_init");
1276 let fs = new_pts_fs(locked, kernel);
1277 let _opened_main = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1278 assert!(
1280 task.thread_group()
1281 .read()
1282 .process_group
1283 .session
1284 .read()
1285 .controlling_terminal
1286 .is_none()
1287 );
1288 let _opened_replica2 = open_file_with_flags(
1290 locked,
1291 task,
1292 &fs,
1293 "0".into(),
1294 OpenFlags::RDWR | OpenFlags::NOCTTY,
1295 )
1296 .expect("open file");
1297 assert!(
1298 task.thread_group()
1299 .read()
1300 .process_group
1301 .session
1302 .read()
1303 .controlling_terminal
1304 .is_none()
1305 );
1306
1307 let _opened_replica2 =
1309 open_file_with_flags(locked, task, &fs, "0".into(), OpenFlags::RDWR)
1310 .expect("open file");
1311 assert!(
1312 task.thread_group()
1313 .read()
1314 .process_group
1315 .session
1316 .read()
1317 .controlling_terminal
1318 .is_some()
1319 );
1320 })
1321 .await;
1322 }
1323
1324 #[fuchsia::test]
1325 async fn test_attach_terminal() {
1326 spawn_kernel_and_run(async |locked, task1| {
1327 let kernel = task1.kernel();
1328 tty_device_init(locked, &*task1).expect("tty_device_init");
1329 let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1330 task2.thread_group().setsid(locked).expect("setsid");
1331
1332 let fs = new_pts_fs(locked, kernel);
1333 let opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1334 let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1335
1336 assert_eq!(ioctl::<i32>(locked, task1, &opened_main, TIOCGPGRP, &0), error!(ENOTTY));
1337 assert_eq!(
1338 ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1339 error!(ENOTTY)
1340 );
1341
1342 set_controlling_terminal(locked, task1, &opened_main, false).unwrap();
1343 assert_eq!(
1344 ioctl::<i32>(locked, task1, &opened_main, TIOCGPGRP, &0),
1345 Ok(task1.thread_group().read().process_group.leader)
1346 );
1347 assert_eq!(
1348 ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1349 error!(ENOTTY)
1350 );
1351
1352 assert_eq!(
1354 set_controlling_terminal(locked, &task2, &opened_replica, false),
1355 error!(EPERM)
1356 );
1357 assert_eq!(
1358 ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1359 error!(ENOTTY)
1360 );
1361 })
1362 .await;
1363 }
1364
1365 #[fuchsia::test]
1366 async fn test_steal_terminal() {
1367 spawn_kernel_and_run(async |locked, task1| {
1368 let kernel = task1.kernel();
1369 tty_device_init(locked, &*task1).expect("tty_device_init");
1370 task1.set_creds(Credentials::with_ids(1, 1));
1371
1372 let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1373
1374 let fs = new_pts_fs(locked, kernel);
1375 let _opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1376 let wo_opened_replica = open_file_with_flags(
1377 locked,
1378 task1,
1379 &fs,
1380 "0".into(),
1381 OpenFlags::WRONLY | OpenFlags::NOCTTY,
1382 )
1383 .expect("open file");
1384 assert!(!wo_opened_replica.can_read());
1385
1386 assert_eq!(
1388 set_controlling_terminal(locked, task1, &wo_opened_replica, false),
1389 error!(EPERM)
1390 );
1391
1392 let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1393 assert_eq!(
1395 set_controlling_terminal(locked, &task2, &opened_replica, false),
1396 error!(EINVAL)
1397 );
1398
1399 set_controlling_terminal(locked, task1, &opened_replica, false)
1401 .expect("Associate terminal to task1");
1402
1403 assert_eq!(
1405 set_controlling_terminal(locked, task1, &opened_replica, false),
1406 error!(EINVAL)
1407 );
1408
1409 task2.thread_group().setsid(locked).expect("setsid");
1410
1411 assert_eq!(
1413 set_controlling_terminal(locked, &task2, &opened_replica, false),
1414 error!(EPERM)
1415 );
1416
1417 assert_eq!(
1419 set_controlling_terminal(locked, &task2, &opened_replica, true),
1420 error!(EPERM)
1421 );
1422
1423 task2.set_creds(Credentials::with_ids(0, 0));
1425 assert_eq!(
1427 set_controlling_terminal(locked, &task2, &opened_replica, false),
1428 error!(EPERM)
1429 );
1430 set_controlling_terminal(locked, &task2, &opened_replica, true)
1431 .expect("Associate terminal to task2");
1432
1433 assert!(
1434 task1
1435 .thread_group()
1436 .read()
1437 .process_group
1438 .session
1439 .read()
1440 .controlling_terminal
1441 .is_none()
1442 );
1443 })
1444 .await;
1445 }
1446
1447 #[fuchsia::test]
1448 async fn test_set_foreground_process() {
1449 spawn_kernel_and_run(async |locked, init| {
1450 let kernel = init.kernel();
1451 tty_device_init(locked, &*init).expect("tty_device_init");
1452 let task1 = init.clone_task_for_test(locked, 0, Some(SIGCHLD));
1453 task1.thread_group().setsid(locked).expect("setsid");
1454 let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1455 task2.thread_group().setpgid(locked, &task2, &task2, 0).expect("setpgid");
1456 let task2_pgid = task2.thread_group().read().process_group.leader;
1457
1458 assert_ne!(task2_pgid, task1.thread_group().read().process_group.leader);
1459
1460 let fs = new_pts_fs(locked, kernel);
1461 let _opened_main = open_ptmx_and_unlock(locked, init, &fs).expect("ptmx");
1462 let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1463
1464 assert_eq!(
1467 ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &task2_pgid),
1468 error!(ENOTTY)
1469 );
1470
1471 set_controlling_terminal(locked, &task1, &opened_replica, false).unwrap();
1473 assert_eq!(
1475 ioctl::<i32>(locked, &task1, &opened_replica, TIOCGPGRP, &0),
1476 Ok(task1.thread_group().read().process_group.leader)
1477 );
1478
1479 assert_eq!(
1481 ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &-1),
1482 error!(EINVAL)
1483 );
1484
1485 assert_eq!(
1487 ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &255),
1488 error!(ESRCH)
1489 );
1490
1491 let init_pgid = init.thread_group().read().process_group.leader;
1493 assert_eq!(
1494 ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &init_pgid),
1495 error!(EPERM)
1496 );
1497
1498 assert_eq!(
1500 ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &task2_pgid),
1501 error!(EINTR)
1502 );
1503 assert!(task2.read().has_signal_pending(SIGTTOU));
1504
1505 ioctl::<i32>(locked, &task1, &opened_replica, TIOCSPGRP, &task2_pgid).unwrap();
1507
1508 let terminal = Arc::clone(
1510 &task1
1511 .thread_group()
1512 .read()
1513 .process_group
1514 .session
1515 .read()
1516 .controlling_terminal
1517 .as_ref()
1518 .unwrap()
1519 .terminal,
1520 );
1521 assert_eq!(
1522 terminal
1523 .read()
1524 .controller
1525 .as_ref()
1526 .unwrap()
1527 .session
1528 .upgrade()
1529 .unwrap()
1530 .read()
1531 .get_foreground_process_group_leader(),
1532 task2_pgid
1533 );
1534 })
1535 .await;
1536 }
1537
1538 #[fuchsia::test]
1539 async fn test_detach_session() {
1540 spawn_kernel_and_run(async |locked, task1| {
1541 let kernel = task1.kernel();
1542 tty_device_init(locked, &*task1).expect("tty_device_init");
1543 let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1544 task2.thread_group().setsid(locked).expect("setsid");
1545
1546 let fs = new_pts_fs(locked, kernel);
1547 let _opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1548 let opened_replica = open_file(locked, task1, &fs, "0".into()).expect("open file");
1549
1550 assert_eq!(ioctl::<i32>(locked, task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1552
1553 set_controlling_terminal(locked, &task2, &opened_replica, false)
1554 .expect("set controlling terminal");
1555
1556 assert_eq!(ioctl::<i32>(locked, task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1558
1559 ioctl::<i32>(locked, &task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1561 assert!(
1562 task2
1563 .thread_group()
1564 .read()
1565 .process_group
1566 .session
1567 .read()
1568 .controlling_terminal
1569 .is_none()
1570 );
1571 })
1572 .await;
1573 }
1574
1575 #[fuchsia::test]
1576 async fn test_send_data_back_and_forth() {
1577 spawn_kernel_and_run(async |locked, task| {
1578 let kernel = task.kernel();
1579 tty_device_init(locked, &*task).expect("tty_device_init");
1580 let fs = new_pts_fs(locked, kernel);
1581 let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1582 let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1583
1584 let has_data_ready_to_read = |locked: &mut Locked<Unlocked>, fd: &FileHandle| {
1585 fd.query_events(locked, task).expect("query_events").contains(FdEvents::POLLIN)
1586 };
1587
1588 let write_and_assert = |locked: &mut Locked<Unlocked>, fd: &FileHandle, data: &[u8]| {
1589 assert_eq!(
1590 fd.write(locked, task, &mut VecInputBuffer::new(data)).expect("write"),
1591 data.len()
1592 );
1593 };
1594
1595 let read_and_check = |locked: &mut Locked<Unlocked>, fd: &FileHandle, data: &[u8]| {
1596 assert!(has_data_ready_to_read(locked, fd));
1597 let mut buffer = VecOutputBuffer::new(data.len() + 1);
1598 assert_eq!(fd.read(locked, task, &mut buffer).expect("read"), data.len());
1599 assert_eq!(data, buffer.data());
1600 };
1601
1602 let hello_buffer = b"hello\n";
1603 let hello_transformed_buffer = b"hello\r\n";
1604
1605 write_and_assert(locked, &ptmx, hello_buffer);
1607 read_and_check(locked, &pts, hello_buffer);
1608
1609 read_and_check(locked, &ptmx, hello_transformed_buffer);
1611
1612 write_and_assert(locked, &pts, hello_buffer);
1614 read_and_check(locked, &ptmx, hello_transformed_buffer);
1615
1616 assert!(!has_data_ready_to_read(locked, &pts));
1618 })
1619 .await;
1620 }
1621}