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