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