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