Skip to main content

starnix_core/fs/
devpts.rs

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