Skip to main content

starnix_core/fs/
devpts.rs

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