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