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_type::{DeviceType, TTY_ALT_MAJOR};
27use starnix_uapi::errors::Errno;
28use starnix_uapi::file_mode::mode;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::signals::SIGWINCH;
31use starnix_uapi::user_address::{UserAddress, UserRef};
32use starnix_uapi::vfs::FdEvents;
33use starnix_uapi::{
34    DEVPTS_SUPER_MAGIC, FIOASYNC, FIOCLEX, FIONBIO, FIONCLEX, FIONREAD, FIOQSIZE, TCFLSH, TCGETA,
35    TCGETS, TCGETX, TCSBRK, TCSBRKP, TCSETA, TCSETAF, TCSETAW, TCSETS, TCSETSF, TCSETSW, TCSETX,
36    TCSETXF, TCSETXW, TCXONC, TIOCCBRK, TIOCCONS, TIOCEXCL, TIOCGETD, TIOCGICOUNT, TIOCGLCKTRMIOS,
37    TIOCGPGRP, TIOCGPTLCK, TIOCGPTN, TIOCGRS485, TIOCGSERIAL, TIOCGSID, TIOCGSOFTCAR, TIOCGWINSZ,
38    TIOCLINUX, TIOCMBIC, TIOCMBIS, TIOCMGET, TIOCMIWAIT, TIOCMSET, TIOCNOTTY, TIOCNXCL, TIOCOUTQ,
39    TIOCPKT, TIOCSBRK, TIOCSCTTY, TIOCSERCONFIG, TIOCSERGETLSR, TIOCSERGETMULTI, TIOCSERGSTRUCT,
40    TIOCSERGWILD, TIOCSERSETMULTI, TIOCSERSWILD, TIOCSETD, TIOCSLCKTRMIOS, TIOCSPGRP, TIOCSPTLCK,
41    TIOCSRS485, TIOCSSERIAL, TIOCSSOFTCAR, TIOCSTI, TIOCSWINSZ, TIOCVHANGUP, errno, error, gid_t,
42    ino_t, pid_t, statfs, uapi, uid_t,
43};
44use std::sync::{Arc, Weak};
45
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(), DeviceType::TTY, DeviceMode::Char),
167        tty_class.clone(),
168        build_device_directory,
169    )?;
170    registry.add_device(
171        locked,
172        kernel_or_task,
173        "ptmx".into(),
174        DeviceMetadata::new("ptmx".into(), DeviceType::PTMX, DeviceMode::Char),
175        tty_class,
176        build_device_directory,
177    )?;
178    Ok(())
179}
180
181struct DevPtsFs {
182    state: Arc<TtyState>,
183    uid: Option<uid_t>,
184    gid: Option<gid_t>,
185    mode: u32,
186    ptmxmode: u32,
187}
188
189impl FileSystemOps for DevPtsFs {
190    fn statfs(
191        &self,
192        _locked: &mut Locked<FileOpsCore>,
193        _fs: &FileSystem,
194        _current_task: &CurrentTask,
195    ) -> Result<statfs, Errno> {
196        Ok(default_statfs(DEVPTS_SUPER_MAGIC))
197    }
198    fn name(&self) -> &'static FsStr {
199        "devpts".into()
200    }
201
202    fn uses_external_node_ids(&self) -> bool {
203        false
204    }
205}
206
207impl DevPtsFs {
208    fn pty_creds_for(&self, current_task: &CurrentTask) -> FsCred {
209        let 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 DeviceType associated with the given pts replicas.
217pub fn get_device_type_for_pts(id: u32) -> DeviceType {
218    DeviceType::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 = DeviceType::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: DeviceType,
295    node: &NamespaceNode,
296    flags: OpenFlags,
297) -> Result<Box<dyn FileOps>, Errno> {
298    match id {
299        // /dev/ptmx and /dev/pts/ptmx
300        DeviceType::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        DeviceType::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            // Set the window size
683            terminal.write().line_discipline.window_size =
684                current_task.read_object(UserRef::<uapi::winsize>::new(user_addr))?;
685
686            // Send a SIGWINCH signal to the foreground process group.
687            let foreground_process_group =
688                terminal.read().controller.as_ref().and_then(|terminal_controller| {
689                    terminal_controller.get_foreground_process_group()
690                });
691            if let Some(process_group) = foreground_process_group {
692                process_group.send_signals(locked, &[SIGWINCH]);
693            }
694            Ok(SUCCESS)
695        }
696        TCGETA => {
697            let termio = into_termio(*terminal.read().termios());
698            current_task.write_object(UserRef::<uapi::termio>::new(user_addr), &termio)?;
699            Ok(SUCCESS)
700        }
701        TCGETS => {
702            // N.B. TCGETS on the main terminal actually returns the configuration of the replica
703            // end.
704            current_task.write_object(
705                UserRef::<uapi::termios>::new(user_addr),
706                terminal.read().termios(),
707            )?;
708            Ok(SUCCESS)
709        }
710        TCSETA => {
711            let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
712            terminal.set_termios(locked, into_termios(termio));
713            Ok(SUCCESS)
714        }
715        TCSETS => {
716            // N.B. TCSETS on the main terminal actually affects the configuration of the replica
717            // end.
718            let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
719            terminal.set_termios(locked, termios);
720            Ok(SUCCESS)
721        }
722        TCSETAF => {
723            // This should drain the output queue and discard the pending input first.
724            let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
725            terminal.set_termios(locked, into_termios(termio));
726            Ok(SUCCESS)
727        }
728        TCSETSF => {
729            // This should drain the output queue and discard the pending input first.
730            let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
731            terminal.set_termios(locked, termios);
732            Ok(SUCCESS)
733        }
734        TCSETAW => {
735            track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETAW drain output queue first");
736            let termio = current_task.read_object(UserRef::<uapi::termio>::new(user_addr))?;
737            terminal.set_termios(locked, into_termios(termio));
738            Ok(SUCCESS)
739        }
740        TCSETSW => {
741            track_stub!(TODO("https://fxbug.dev/322873281"), "TCSETSW drain output queue first");
742            let termios = current_task.read_object(UserRef::<uapi::termios>::new(user_addr))?;
743            terminal.set_termios(locked, termios);
744            Ok(SUCCESS)
745        }
746        TIOCSETD => {
747            track_stub!(
748                TODO("https://fxbug.dev/322874060"),
749                "devpts setting line discipline",
750                is_main
751            );
752            error!(EINVAL)
753        }
754        TCSBRK => Ok(SUCCESS),
755        TCXONC => {
756            track_stub!(TODO("https://fxbug.dev/322892912"), "devpts ioctl TCXONC", is_main);
757            error!(ENOSYS)
758        }
759        TCFLSH => {
760            track_stub!(TODO("https://fxbug.dev/322893703"), "devpts ioctl TCFLSH", is_main);
761            error!(ENOSYS)
762        }
763        TIOCEXCL => {
764            track_stub!(TODO("https://fxbug.dev/322893449"), "devpts ioctl TIOCEXCL", is_main);
765            error!(ENOSYS)
766        }
767        TIOCNXCL => {
768            track_stub!(TODO("https://fxbug.dev/322893393"), "devpts ioctl TIOCNXCL", is_main);
769            error!(ENOSYS)
770        }
771        TIOCOUTQ => {
772            track_stub!(TODO("https://fxbug.dev/322893723"), "devpts ioctl TIOCOUTQ", is_main);
773            error!(ENOSYS)
774        }
775        TIOCSTI => {
776            track_stub!(TODO("https://fxbug.dev/322893780"), "devpts ioctl TIOCSTI", is_main);
777            error!(ENOSYS)
778        }
779        TIOCMGET => {
780            track_stub!(TODO("https://fxbug.dev/322893681"), "devpts ioctl TIOCMGET", is_main);
781            error!(ENOSYS)
782        }
783        TIOCMBIS => {
784            track_stub!(TODO("https://fxbug.dev/322893709"), "devpts ioctl TIOCMBIS", is_main);
785            error!(ENOSYS)
786        }
787        TIOCMBIC => {
788            track_stub!(TODO("https://fxbug.dev/322893610"), "devpts ioctl TIOCMBIC", is_main);
789            error!(ENOSYS)
790        }
791        TIOCMSET => {
792            track_stub!(TODO("https://fxbug.dev/322893211"), "devpts ioctl TIOCMSET", is_main);
793            error!(ENOSYS)
794        }
795        TIOCGSOFTCAR => {
796            track_stub!(TODO("https://fxbug.dev/322893365"), "devpts ioctl TIOCGSOFTCAR", is_main);
797            error!(ENOSYS)
798        }
799        TIOCSSOFTCAR => {
800            track_stub!(TODO("https://fxbug.dev/322894074"), "devpts ioctl TIOCSSOFTCAR", is_main);
801            error!(ENOSYS)
802        }
803        TIOCLINUX => {
804            track_stub!(TODO("https://fxbug.dev/322893147"), "devpts ioctl TIOCLINUX", is_main);
805            error!(ENOSYS)
806        }
807        TIOCCONS => {
808            track_stub!(TODO("https://fxbug.dev/322893267"), "devpts ioctl TIOCCONS", is_main);
809            error!(ENOSYS)
810        }
811        TIOCGSERIAL => {
812            track_stub!(TODO("https://fxbug.dev/322893503"), "devpts ioctl TIOCGSERIAL", is_main);
813            error!(ENOSYS)
814        }
815        TIOCSSERIAL => {
816            track_stub!(TODO("https://fxbug.dev/322893663"), "devpts ioctl TIOCSSERIAL", is_main);
817            error!(ENOSYS)
818        }
819        TIOCPKT => {
820            track_stub!(TODO("https://fxbug.dev/322893148"), "devpts ioctl TIOCPKT", is_main);
821            error!(ENOSYS)
822        }
823        FIONBIO => {
824            track_stub!(TODO("https://fxbug.dev/322893957"), "devpts ioctl FIONBIO", is_main);
825            error!(ENOSYS)
826        }
827        TIOCGETD => {
828            track_stub!(TODO("https://fxbug.dev/322893974"), "devpts ioctl TIOCGETD", is_main);
829            error!(ENOSYS)
830        }
831        TCSBRKP => Ok(SUCCESS),
832        TIOCSBRK => {
833            track_stub!(TODO("https://fxbug.dev/322893936"), "devpts ioctl TIOCSBRK", is_main);
834            error!(ENOSYS)
835        }
836        TIOCCBRK => {
837            track_stub!(TODO("https://fxbug.dev/322893213"), "devpts ioctl TIOCCBRK", is_main);
838            error!(ENOSYS)
839        }
840        TIOCGSID => {
841            track_stub!(TODO("https://fxbug.dev/322894076"), "devpts ioctl TIOCGSID", is_main);
842            error!(ENOSYS)
843        }
844        TIOCGRS485 => {
845            track_stub!(TODO("https://fxbug.dev/322893728"), "devpts ioctl TIOCGRS485", is_main);
846            error!(ENOSYS)
847        }
848        TIOCSRS485 => {
849            track_stub!(TODO("https://fxbug.dev/322893783"), "devpts ioctl TIOCSRS485", is_main);
850            error!(ENOSYS)
851        }
852        TCGETX => {
853            track_stub!(TODO("https://fxbug.dev/322893327"), "devpts ioctl TCGETX", is_main);
854            error!(ENOSYS)
855        }
856        TCSETX => {
857            track_stub!(TODO("https://fxbug.dev/322893741"), "devpts ioctl TCSETX", is_main);
858            error!(ENOSYS)
859        }
860        TCSETXF => {
861            track_stub!(TODO("https://fxbug.dev/322893937"), "devpts ioctl TCSETXF", is_main);
862            error!(ENOSYS)
863        }
864        TCSETXW => {
865            track_stub!(TODO("https://fxbug.dev/322893899"), "devpts ioctl TCSETXW", is_main);
866            error!(ENOSYS)
867        }
868        TIOCVHANGUP => {
869            track_stub!(TODO("https://fxbug.dev/322893742"), "devpts ioctl TIOCVHANGUP", is_main);
870            error!(ENOSYS)
871        }
872        FIONCLEX => {
873            track_stub!(TODO("https://fxbug.dev/322893938"), "devpts ioctl FIONCLEX", is_main);
874            error!(ENOSYS)
875        }
876        FIOCLEX => {
877            track_stub!(TODO("https://fxbug.dev/322894214"), "devpts ioctl FIOCLEX", is_main);
878            error!(ENOSYS)
879        }
880        FIOASYNC => {
881            track_stub!(TODO("https://fxbug.dev/322893269"), "devpts ioctl FIOASYNC", is_main);
882            error!(ENOSYS)
883        }
884        TIOCSERCONFIG => {
885            track_stub!(TODO("https://fxbug.dev/322893881"), "devpts ioctl TIOCSERCONFIG", is_main);
886            error!(ENOSYS)
887        }
888        TIOCSERGWILD => {
889            track_stub!(TODO("https://fxbug.dev/322893686"), "devpts ioctl TIOCSERGWILD", is_main);
890            error!(ENOSYS)
891        }
892        TIOCSERSWILD => {
893            track_stub!(TODO("https://fxbug.dev/322893837"), "devpts ioctl TIOCSERSWILD", is_main);
894            error!(ENOSYS)
895        }
896        TIOCGLCKTRMIOS => {
897            track_stub!(
898                TODO("https://fxbug.dev/322894114"),
899                "devpts ioctl TIOCGLCKTRMIOS",
900                is_main
901            );
902            error!(ENOSYS)
903        }
904        TIOCSLCKTRMIOS => {
905            track_stub!(
906                TODO("https://fxbug.dev/322893711"),
907                "devpts ioctl TIOCSLCKTRMIOS",
908                is_main
909            );
910            error!(ENOSYS)
911        }
912        TIOCSERGSTRUCT => {
913            track_stub!(
914                TODO("https://fxbug.dev/322893828"),
915                "devpts ioctl TIOCSERGSTRUCT",
916                is_main
917            );
918            error!(ENOSYS)
919        }
920        TIOCSERGETLSR => {
921            track_stub!(TODO("https://fxbug.dev/322894083"), "devpts ioctl TIOCSERGETLSR", is_main);
922            error!(ENOSYS)
923        }
924        TIOCSERGETMULTI => {
925            track_stub!(
926                TODO("https://fxbug.dev/322893962"),
927                "devpts ioctl TIOCSERGETMULTI",
928                is_main
929            );
930            error!(ENOSYS)
931        }
932        TIOCSERSETMULTI => {
933            track_stub!(
934                TODO("https://fxbug.dev/322893273"),
935                "devpts ioctl TIOCSERSETMULTI",
936                is_main
937            );
938            error!(ENOSYS)
939        }
940        TIOCMIWAIT => {
941            track_stub!(TODO("https://fxbug.dev/322894005"), "devpts ioctl TIOCMIWAIT", is_main);
942            error!(ENOSYS)
943        }
944        TIOCGICOUNT => {
945            track_stub!(TODO("https://fxbug.dev/322893862"), "devpts ioctl TIOCGICOUNT", is_main);
946            error!(ENOSYS)
947        }
948        FIOQSIZE => {
949            track_stub!(TODO("https://fxbug.dev/322893770"), "devpts ioctl FIOQSIZE", is_main);
950            error!(ENOSYS)
951        }
952        other => {
953            track_stub!(TODO("https://fxbug.dev/322893712"), "devpts unknown ioctl", other);
954            error!(ENOTTY)
955        }
956    }
957}
958
959#[cfg(test)]
960mod tests {
961    use super::*;
962    use crate::fs::devpts::tty_device_init;
963    use crate::fs::tmpfs::TmpFs;
964    use crate::testing::*;
965    use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
966    use crate::vfs::fs_args::MountParams;
967    use crate::vfs::{MountInfo, NamespaceNode};
968    use starnix_uapi::auth::Credentials;
969    use starnix_uapi::file_mode::{AccessCheck, FileMode};
970    use starnix_uapi::signals::{SIGCHLD, SIGTTOU};
971
972    fn new_pts_fs(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> FileSystemHandle {
973        let mut options = FileSystemOptions::default();
974        options.params = MountParams::parse("ptmxmode=666".into()).expect("parse option");
975        super::new_pts_fs(locked, &kernel, options).expect("create new_pts_fs")
976    }
977
978    fn ioctl<T: zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable + Copy>(
979        locked: &mut Locked<Unlocked>,
980        current_task: &CurrentTask,
981        file: &FileHandle,
982        command: u32,
983        value: &T,
984    ) -> Result<T, Errno> {
985        let address = map_memory(
986            locked,
987            current_task,
988            UserAddress::default(),
989            std::mem::size_of::<T>() as u64,
990        );
991        let address_ref = UserRef::<T>::new(address);
992        current_task.write_object(address_ref, value)?;
993        file.ioctl(locked, current_task, command, address.into())?;
994        current_task.read_object(address_ref)
995    }
996
997    fn set_controlling_terminal(
998        locked: &mut Locked<Unlocked>,
999        current_task: &CurrentTask,
1000        file: &FileHandle,
1001        steal: bool,
1002    ) -> Result<SyscallResult, Errno> {
1003        #[allow(clippy::bool_to_int_with_if)]
1004        file.ioctl(locked, current_task, TIOCSCTTY, steal.into())
1005    }
1006
1007    fn lookup_node<L>(
1008        locked: &mut Locked<L>,
1009        task: &CurrentTask,
1010        fs: &FileSystemHandle,
1011        name: &FsStr,
1012    ) -> Result<NamespaceNode, Errno>
1013    where
1014        L: LockEqualOrBefore<FileOpsCore>,
1015    {
1016        let root = NamespaceNode::new_anonymous(fs.root().clone());
1017        root.lookup_child(locked, task, &mut Default::default(), name)
1018    }
1019
1020    fn open_file_with_flags(
1021        locked: &mut Locked<Unlocked>,
1022        current_task: &CurrentTask,
1023        fs: &FileSystemHandle,
1024        name: &FsStr,
1025        flags: OpenFlags,
1026    ) -> Result<FileHandle, Errno> {
1027        let node = lookup_node(locked, current_task, fs, name)?;
1028        node.open(locked, current_task, flags, AccessCheck::default())
1029    }
1030
1031    fn open_file(
1032        locked: &mut Locked<Unlocked>,
1033        current_task: &CurrentTask,
1034        fs: &FileSystemHandle,
1035        name: &FsStr,
1036    ) -> Result<FileHandle, Errno> {
1037        open_file_with_flags(locked, current_task, fs, name, OpenFlags::RDWR | OpenFlags::NOCTTY)
1038    }
1039
1040    fn open_ptmx_and_unlock(
1041        locked: &mut Locked<Unlocked>,
1042        current_task: &CurrentTask,
1043        fs: &FileSystemHandle,
1044    ) -> Result<FileHandle, Errno> {
1045        let file = open_file_with_flags(locked, current_task, fs, "ptmx".into(), OpenFlags::RDWR)?;
1046
1047        // Unlock terminal
1048        ioctl::<i32>(locked, current_task, &file, TIOCSPTLCK, &0)?;
1049
1050        Ok(file)
1051    }
1052
1053    #[fuchsia::test]
1054    async fn opening_ptmx_creates_pts() {
1055        spawn_kernel_and_run(async |locked, task| {
1056            let kernel = task.kernel();
1057            tty_device_init(locked, &*task).expect("tty_device_init");
1058            let fs = new_pts_fs(locked, kernel);
1059            lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1060            let _ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1061            lookup_node(locked, task, &fs, "0".into()).expect("pty");
1062        })
1063        .await;
1064    }
1065
1066    #[fuchsia::test]
1067    async fn closing_ptmx_closes_pts() {
1068        spawn_kernel_and_run(async |locked, task| {
1069            let kernel = task.kernel();
1070            tty_device_init(locked, &*task).expect("tty_device_init");
1071            let fs = new_pts_fs(locked, kernel);
1072            lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1073            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1074            let _pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1075            std::mem::drop(ptmx);
1076            task.trigger_delayed_releaser(locked);
1077            lookup_node(locked, task, &fs, "0".into()).unwrap_err();
1078        })
1079        .await;
1080    }
1081
1082    #[fuchsia::test]
1083    async fn pts_are_reused() {
1084        spawn_kernel_and_run(async |locked, task| {
1085            let kernel = task.kernel();
1086            tty_device_init(locked, &*task).expect("tty_device_init");
1087            let fs = new_pts_fs(locked, kernel);
1088
1089            let _ptmx0 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1090            let mut _ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1091            let _ptmx2 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1092
1093            lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1094            lookup_node(locked, task, &fs, "1".into()).expect("component_lookup");
1095            lookup_node(locked, task, &fs, "2".into()).expect("component_lookup");
1096
1097            std::mem::drop(_ptmx1);
1098            task.trigger_delayed_releaser(locked);
1099
1100            lookup_node(locked, task, &fs, "1".into()).unwrap_err();
1101
1102            _ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1103            lookup_node(locked, task, &fs, "1".into()).expect("component_lookup");
1104        })
1105        .await;
1106    }
1107
1108    #[fuchsia::test]
1109    async fn opening_inexistant_replica_fails() {
1110        spawn_kernel_and_run(async |locked, task| {
1111            let kernel = task.kernel();
1112            tty_device_init(locked, &*task).expect("tty_device_init");
1113            // Initialize pts devices
1114            new_pts_fs(locked, kernel);
1115            let fs = TmpFs::new_fs(locked, kernel);
1116            let mount = MountInfo::detached();
1117            let pts = fs
1118                .root()
1119                .create_entry(
1120                    locked,
1121                    task,
1122                    &mount,
1123                    "custom_pts".into(),
1124                    |locked, dir, mount, name| {
1125                        dir.create_node(
1126                            locked,
1127                            task,
1128                            mount,
1129                            name,
1130                            mode!(IFCHR, 0o666),
1131                            DeviceType::new(DEVPTS_FIRST_MAJOR, 0),
1132                            FsCred::root(),
1133                        )
1134                    },
1135                )
1136                .expect("custom_pts");
1137            let node = NamespaceNode::new_anonymous(pts.clone());
1138            assert!(node.open(locked, task, OpenFlags::RDONLY, AccessCheck::skip()).is_err());
1139        })
1140        .await;
1141    }
1142
1143    #[fuchsia::test]
1144    async fn test_open_tty() {
1145        spawn_kernel_and_run(async |locked, task| {
1146            let kernel = task.kernel();
1147            tty_device_init(locked, &*task).expect("tty_device_init");
1148            let fs = new_pts_fs(locked, kernel);
1149            let devfs = crate::fs::devtmpfs::DevTmpFs::from_kernel(locked, kernel);
1150
1151            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1152            set_controlling_terminal(locked, task, &ptmx, false).expect("set_controlling_terminal");
1153            let tty = open_file_with_flags(locked, task, &devfs, "tty".into(), OpenFlags::RDWR)
1154                .expect("tty");
1155            // Check that tty is the main terminal by calling the ioctl TIOCGPTN and checking it is
1156            // has the same result as on ptmx.
1157            assert_eq!(
1158                ioctl::<i32>(locked, task, &tty, TIOCGPTN, &0),
1159                ioctl::<i32>(locked, task, &ptmx, TIOCGPTN, &0)
1160            );
1161
1162            // Detach the controlling terminal.
1163            ioctl::<i32>(locked, task, &ptmx, TIOCNOTTY, &0).expect("detach terminal");
1164            let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1165            set_controlling_terminal(locked, task, &pts, false).expect("set_controlling_terminal");
1166            let tty = open_file_with_flags(locked, task, &devfs, "tty".into(), OpenFlags::RDWR)
1167                .expect("tty");
1168            // TIOCGPTN is not implemented on replica terminals
1169            assert!(ioctl::<i32>(locked, task, &tty, TIOCGPTN, &0).is_err());
1170        })
1171        .await;
1172    }
1173
1174    #[fuchsia::test]
1175    async fn test_unknown_ioctl() {
1176        spawn_kernel_and_run(async |locked, task| {
1177            let kernel = task.kernel();
1178            tty_device_init(locked, &*task).expect("tty_device_init");
1179            let fs = new_pts_fs(locked, kernel);
1180
1181            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1182            assert_eq!(ptmx.ioctl(locked, task, 42, Default::default()), error!(ENOTTY));
1183
1184            let pts_file = open_file(locked, task, &fs, "0".into()).expect("open file");
1185            assert_eq!(pts_file.ioctl(locked, task, 42, Default::default()), error!(ENOTTY));
1186        })
1187        .await;
1188    }
1189
1190    #[fuchsia::test]
1191    async fn test_tiocgptn_ioctl() {
1192        spawn_kernel_and_run(async |locked, task| {
1193            let kernel = task.kernel();
1194            tty_device_init(locked, &*task).expect("tty_device_init");
1195            let fs = new_pts_fs(locked, kernel);
1196            let ptmx0 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1197            let ptmx1 = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1198
1199            let pts0 = ioctl::<u32>(locked, task, &ptmx0, TIOCGPTN, &0).expect("ioctl");
1200            assert_eq!(pts0, 0);
1201
1202            let pts1 = ioctl::<u32>(locked, task, &ptmx1, TIOCGPTN, &0).expect("ioctl");
1203            assert_eq!(pts1, 1);
1204        })
1205        .await;
1206    }
1207
1208    #[fuchsia::test]
1209    async fn test_new_terminal_is_locked() {
1210        spawn_kernel_and_run(async |locked, task| {
1211            let kernel = task.kernel();
1212            tty_device_init(locked, &*task).expect("tty_device_init");
1213            let fs = new_pts_fs(locked, kernel);
1214            let _ptmx_file = open_file(locked, task, &fs, "ptmx".into()).expect("open file");
1215
1216            let pts = lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1217            assert_eq!(
1218                pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).map(|_| ()),
1219                error!(EIO)
1220            );
1221        })
1222        .await;
1223    }
1224
1225    #[fuchsia::test]
1226    async fn test_lock_ioctls() {
1227        spawn_kernel_and_run(async |locked, task| {
1228            let kernel = task.kernel();
1229            tty_device_init(locked, &*task).expect("tty_device_init");
1230            let fs = new_pts_fs(locked, kernel);
1231            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1232            let pts = lookup_node(locked, task, &fs, "0".into()).expect("component_lookup");
1233
1234            // Check that the lock is not set.
1235            assert_eq!(ioctl::<i32>(locked, task, &ptmx, TIOCGPTLCK, &0), Ok(0));
1236            // /dev/pts/0 can be opened
1237            pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).expect("open");
1238
1239            // Lock the terminal
1240            ioctl::<i32>(locked, task, &ptmx, TIOCSPTLCK, &42).expect("ioctl");
1241            // Check that the lock is set.
1242            assert_eq!(ioctl::<i32>(locked, task, &ptmx, TIOCGPTLCK, &0), Ok(1));
1243            // /dev/pts/0 cannot be opened
1244            assert_eq!(
1245                pts.open(locked, task, OpenFlags::RDONLY, AccessCheck::default()).map(|_| ()),
1246                error!(EIO)
1247            );
1248        })
1249        .await;
1250    }
1251
1252    #[fuchsia::test]
1253    async fn test_ptmx_stats() {
1254        spawn_kernel_and_run(async |locked, task| {
1255            let kernel = task.kernel();
1256            tty_device_init(locked, &*task).expect("tty_device_init");
1257            task.set_creds(Credentials::with_ids(22, 22));
1258            let fs = new_pts_fs(locked, kernel);
1259            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1260            let ptmx_stat = ptmx.node().stat(locked, task).expect("stat");
1261            assert_eq!(ptmx_stat.st_blksize as usize, BLOCK_SIZE);
1262            let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1263            let pts_stats = pts.node().stat(locked, task).expect("stat");
1264            assert_eq!(pts_stats.st_mode & FileMode::PERMISSIONS.bits(), 0o600);
1265            assert_eq!(pts_stats.st_uid, 22);
1266            // TODO(qsr): Check that gid is tty.
1267        })
1268        .await;
1269    }
1270
1271    #[fuchsia::test]
1272    async fn test_attach_terminal_when_open() {
1273        spawn_kernel_and_run(async |locked, task| {
1274            let kernel = task.kernel();
1275            tty_device_init(locked, &*task).expect("tty_device_init");
1276            let fs = new_pts_fs(locked, kernel);
1277            let _opened_main = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1278            // Opening the main terminal should not set the terminal of the session.
1279            assert!(
1280                task.thread_group()
1281                    .read()
1282                    .process_group
1283                    .session
1284                    .read()
1285                    .controlling_terminal
1286                    .is_none()
1287            );
1288            // Opening the terminal should not set the terminal of the session with the NOCTTY flag.
1289            let _opened_replica2 = open_file_with_flags(
1290                locked,
1291                task,
1292                &fs,
1293                "0".into(),
1294                OpenFlags::RDWR | OpenFlags::NOCTTY,
1295            )
1296            .expect("open file");
1297            assert!(
1298                task.thread_group()
1299                    .read()
1300                    .process_group
1301                    .session
1302                    .read()
1303                    .controlling_terminal
1304                    .is_none()
1305            );
1306
1307            // Opening the replica terminal should set the terminal of the session.
1308            let _opened_replica2 =
1309                open_file_with_flags(locked, task, &fs, "0".into(), OpenFlags::RDWR)
1310                    .expect("open file");
1311            assert!(
1312                task.thread_group()
1313                    .read()
1314                    .process_group
1315                    .session
1316                    .read()
1317                    .controlling_terminal
1318                    .is_some()
1319            );
1320        })
1321        .await;
1322    }
1323
1324    #[fuchsia::test]
1325    async fn test_attach_terminal() {
1326        spawn_kernel_and_run(async |locked, task1| {
1327            let kernel = task1.kernel();
1328            tty_device_init(locked, &*task1).expect("tty_device_init");
1329            let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1330            task2.thread_group().setsid(locked).expect("setsid");
1331
1332            let fs = new_pts_fs(locked, kernel);
1333            let opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1334            let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1335
1336            assert_eq!(ioctl::<i32>(locked, task1, &opened_main, TIOCGPGRP, &0), error!(ENOTTY));
1337            assert_eq!(
1338                ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1339                error!(ENOTTY)
1340            );
1341
1342            set_controlling_terminal(locked, task1, &opened_main, false).unwrap();
1343            assert_eq!(
1344                ioctl::<i32>(locked, task1, &opened_main, TIOCGPGRP, &0),
1345                Ok(task1.thread_group().read().process_group.leader)
1346            );
1347            assert_eq!(
1348                ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1349                error!(ENOTTY)
1350            );
1351
1352            // Cannot steal terminal using the replica.
1353            assert_eq!(
1354                set_controlling_terminal(locked, &task2, &opened_replica, false),
1355                error!(EPERM)
1356            );
1357            assert_eq!(
1358                ioctl::<i32>(locked, &task2, &opened_replica, TIOCGPGRP, &0),
1359                error!(ENOTTY)
1360            );
1361        })
1362        .await;
1363    }
1364
1365    #[fuchsia::test]
1366    async fn test_steal_terminal() {
1367        spawn_kernel_and_run(async |locked, task1| {
1368            let kernel = task1.kernel();
1369            tty_device_init(locked, &*task1).expect("tty_device_init");
1370            task1.set_creds(Credentials::with_ids(1, 1));
1371
1372            let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1373
1374            let fs = new_pts_fs(locked, kernel);
1375            let _opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1376            let wo_opened_replica = open_file_with_flags(
1377                locked,
1378                task1,
1379                &fs,
1380                "0".into(),
1381                OpenFlags::WRONLY | OpenFlags::NOCTTY,
1382            )
1383            .expect("open file");
1384            assert!(!wo_opened_replica.can_read());
1385
1386            // FD must be readable for setting the terminal.
1387            assert_eq!(
1388                set_controlling_terminal(locked, task1, &wo_opened_replica, false),
1389                error!(EPERM)
1390            );
1391
1392            let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1393            // Task must be session leader for setting the terminal.
1394            assert_eq!(
1395                set_controlling_terminal(locked, &task2, &opened_replica, false),
1396                error!(EINVAL)
1397            );
1398
1399            // Associate terminal to task1.
1400            set_controlling_terminal(locked, task1, &opened_replica, false)
1401                .expect("Associate terminal to task1");
1402
1403            // One cannot associate a terminal to a process that has already one
1404            assert_eq!(
1405                set_controlling_terminal(locked, task1, &opened_replica, false),
1406                error!(EINVAL)
1407            );
1408
1409            task2.thread_group().setsid(locked).expect("setsid");
1410
1411            // One cannot associate a terminal that is already associated with another process.
1412            assert_eq!(
1413                set_controlling_terminal(locked, &task2, &opened_replica, false),
1414                error!(EPERM)
1415            );
1416
1417            // One cannot steal a terminal without the CAP_SYS_ADMIN capacility
1418            assert_eq!(
1419                set_controlling_terminal(locked, &task2, &opened_replica, true),
1420                error!(EPERM)
1421            );
1422
1423            // One can steal a terminal with the CAP_SYS_ADMIN capacility
1424            task2.set_creds(Credentials::with_ids(0, 0));
1425            // But not without specifying that one wants to steal it.
1426            assert_eq!(
1427                set_controlling_terminal(locked, &task2, &opened_replica, false),
1428                error!(EPERM)
1429            );
1430            set_controlling_terminal(locked, &task2, &opened_replica, true)
1431                .expect("Associate terminal to task2");
1432
1433            assert!(
1434                task1
1435                    .thread_group()
1436                    .read()
1437                    .process_group
1438                    .session
1439                    .read()
1440                    .controlling_terminal
1441                    .is_none()
1442            );
1443        })
1444        .await;
1445    }
1446
1447    #[fuchsia::test]
1448    async fn test_set_foreground_process() {
1449        spawn_kernel_and_run(async |locked, init| {
1450            let kernel = init.kernel();
1451            tty_device_init(locked, &*init).expect("tty_device_init");
1452            let task1 = init.clone_task_for_test(locked, 0, Some(SIGCHLD));
1453            task1.thread_group().setsid(locked).expect("setsid");
1454            let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1455            task2.thread_group().setpgid(locked, &task2, &task2, 0).expect("setpgid");
1456            let task2_pgid = task2.thread_group().read().process_group.leader;
1457
1458            assert_ne!(task2_pgid, task1.thread_group().read().process_group.leader);
1459
1460            let fs = new_pts_fs(locked, kernel);
1461            let _opened_main = open_ptmx_and_unlock(locked, init, &fs).expect("ptmx");
1462            let opened_replica = open_file(locked, &task2, &fs, "0".into()).expect("open file");
1463
1464            // Cannot change the foreground process group if the terminal is not the controlling
1465            // terminal
1466            assert_eq!(
1467                ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &task2_pgid),
1468                error!(ENOTTY)
1469            );
1470
1471            // Attach terminal to task1 and task2 session.
1472            set_controlling_terminal(locked, &task1, &opened_replica, false).unwrap();
1473            // The foreground process group should be the one of task1
1474            assert_eq!(
1475                ioctl::<i32>(locked, &task1, &opened_replica, TIOCGPGRP, &0),
1476                Ok(task1.thread_group().read().process_group.leader)
1477            );
1478
1479            // Cannot change the foreground process group to a negative pid.
1480            assert_eq!(
1481                ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &-1),
1482                error!(EINVAL)
1483            );
1484
1485            // Cannot change the foreground process group to a invalid process group.
1486            assert_eq!(
1487                ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &255),
1488                error!(ESRCH)
1489            );
1490
1491            // Cannot change the foreground process group to a process group in another session.
1492            let init_pgid = init.thread_group().read().process_group.leader;
1493            assert_eq!(
1494                ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &init_pgid),
1495                error!(EPERM)
1496            );
1497
1498            // Changing the foreground process while being in background generates SIGTTOU and fails.
1499            assert_eq!(
1500                ioctl::<i32>(locked, &task2, &opened_replica, TIOCSPGRP, &task2_pgid),
1501                error!(EINTR)
1502            );
1503            assert!(task2.read().has_signal_pending(SIGTTOU));
1504
1505            // Set the foreground process to task2 process group
1506            ioctl::<i32>(locked, &task1, &opened_replica, TIOCSPGRP, &task2_pgid).unwrap();
1507
1508            // Check that the foreground process has been changed.
1509            let terminal = Arc::clone(
1510                &task1
1511                    .thread_group()
1512                    .read()
1513                    .process_group
1514                    .session
1515                    .read()
1516                    .controlling_terminal
1517                    .as_ref()
1518                    .unwrap()
1519                    .terminal,
1520            );
1521            assert_eq!(
1522                terminal
1523                    .read()
1524                    .controller
1525                    .as_ref()
1526                    .unwrap()
1527                    .session
1528                    .upgrade()
1529                    .unwrap()
1530                    .read()
1531                    .get_foreground_process_group_leader(),
1532                task2_pgid
1533            );
1534        })
1535        .await;
1536    }
1537
1538    #[fuchsia::test]
1539    async fn test_detach_session() {
1540        spawn_kernel_and_run(async |locked, task1| {
1541            let kernel = task1.kernel();
1542            tty_device_init(locked, &*task1).expect("tty_device_init");
1543            let task2 = task1.clone_task_for_test(locked, 0, Some(SIGCHLD));
1544            task2.thread_group().setsid(locked).expect("setsid");
1545
1546            let fs = new_pts_fs(locked, kernel);
1547            let _opened_main = open_ptmx_and_unlock(locked, task1, &fs).expect("ptmx");
1548            let opened_replica = open_file(locked, task1, &fs, "0".into()).expect("open file");
1549
1550            // Cannot detach the controlling terminal when none is attached terminal
1551            assert_eq!(ioctl::<i32>(locked, task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1552
1553            set_controlling_terminal(locked, &task2, &opened_replica, false)
1554                .expect("set controlling terminal");
1555
1556            // Cannot detach the controlling terminal when not the session leader.
1557            assert_eq!(ioctl::<i32>(locked, task1, &opened_replica, TIOCNOTTY, &0), error!(ENOTTY));
1558
1559            // Detach the terminal
1560            ioctl::<i32>(locked, &task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
1561            assert!(
1562                task2
1563                    .thread_group()
1564                    .read()
1565                    .process_group
1566                    .session
1567                    .read()
1568                    .controlling_terminal
1569                    .is_none()
1570            );
1571        })
1572        .await;
1573    }
1574
1575    #[fuchsia::test]
1576    async fn test_send_data_back_and_forth() {
1577        spawn_kernel_and_run(async |locked, task| {
1578            let kernel = task.kernel();
1579            tty_device_init(locked, &*task).expect("tty_device_init");
1580            let fs = new_pts_fs(locked, kernel);
1581            let ptmx = open_ptmx_and_unlock(locked, task, &fs).expect("ptmx");
1582            let pts = open_file(locked, task, &fs, "0".into()).expect("open file");
1583
1584            let has_data_ready_to_read = |locked: &mut Locked<Unlocked>, fd: &FileHandle| {
1585                fd.query_events(locked, task).expect("query_events").contains(FdEvents::POLLIN)
1586            };
1587
1588            let write_and_assert = |locked: &mut Locked<Unlocked>, fd: &FileHandle, data: &[u8]| {
1589                assert_eq!(
1590                    fd.write(locked, task, &mut VecInputBuffer::new(data)).expect("write"),
1591                    data.len()
1592                );
1593            };
1594
1595            let read_and_check = |locked: &mut Locked<Unlocked>, fd: &FileHandle, data: &[u8]| {
1596                assert!(has_data_ready_to_read(locked, fd));
1597                let mut buffer = VecOutputBuffer::new(data.len() + 1);
1598                assert_eq!(fd.read(locked, task, &mut buffer).expect("read"), data.len());
1599                assert_eq!(data, buffer.data());
1600            };
1601
1602            let hello_buffer = b"hello\n";
1603            let hello_transformed_buffer = b"hello\r\n";
1604
1605            // Main to replica
1606            write_and_assert(locked, &ptmx, hello_buffer);
1607            read_and_check(locked, &pts, hello_buffer);
1608
1609            // Data has been echoed
1610            read_and_check(locked, &ptmx, hello_transformed_buffer);
1611
1612            // Replica to main
1613            write_and_assert(locked, &pts, hello_buffer);
1614            read_and_check(locked, &ptmx, hello_transformed_buffer);
1615
1616            // Data has not been echoed
1617            assert!(!has_data_ready_to_read(locked, &pts));
1618        })
1619        .await;
1620    }
1621}