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