blob: e09365d4b7959a6a9cbb1a1eadee68e7f763c341 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::sync::Arc;
use crate::device::terminal::*;
use crate::device::DeviceOps;
use crate::device::WithStaticDeviceId;
use crate::fs::tmpfs::*;
use crate::fs::*;
use crate::syscalls::*;
use crate::task::*;
use crate::types::*;
// See https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
const DEVPTS_FIRST_MAJOR: u32 = 136;
const DEVPTS_MAJOR_COUNT: u32 = 4;
// The device identifier is encoded through the major and minor device identifier of the
// device. Each major identifier can contain 256 pts replicas.
pub const DEVPTS_COUNT: u32 = DEVPTS_MAJOR_COUNT * 256;
// The block size of the node in the devpts file system. Value has been taken from
// https://github.com/google/gvisor/blob/master/test/syscalls/linux/pty.cc
const BLOCK_SIZE: i64 = 1024;
pub fn dev_pts_fs(kernel: &Arc<Kernel>) -> &FileSystemHandle {
kernel.dev_pts_fs.get_or_init(|| init_devpts(kernel))
}
pub fn create_pts_node(fs: &FileSystemHandle, task: &CurrentTask, id: u32) -> Result<(), Errno> {
let device_type = get_device_type_for_pts(id);
let pts = fs.root().create_node(
id.to_string().as_bytes(),
FileMode::IFCHR | FileMode::from_bits(0o620),
device_type,
)?;
let mut info = pts.node.info_write();
info.blksize = BLOCK_SIZE;
info.uid = task.read().creds.euid;
// TODO(qsr): set gid to the tty group
info.gid = 0;
Ok(())
}
fn init_devpts(kernel: &Arc<Kernel>) -> FileSystemHandle {
let fs = TmpFs::new(kernel);
// Create ptmx
let ptmx = fs
.root()
.create_node(b"ptmx", FileMode::IFCHR | FileMode::from_bits(0o666), DeviceType::PTMX)
.unwrap();
ptmx.node.info_write().blksize = BLOCK_SIZE;
{
let state = Arc::new(TTYState::new(fs.clone()));
let mut registry = kernel.device_registry.write();
// Register /dev/pts/X device type
for n in 0..DEVPTS_MAJOR_COUNT {
registry
.register_default_chrdev(DevPts::new(state.clone()), DEVPTS_FIRST_MAJOR + n)
.unwrap();
}
// Register ptmx device type
registry.register_chrdev(DevPtmx::new(fs.clone(), state), DeviceType::PTMX).unwrap();
}
fs
}
// Construct the DeviceType associated with the given pts replicas.
fn get_device_type_for_pts(id: u32) -> DeviceType {
DeviceType::new(DEVPTS_FIRST_MAJOR + id / 256, id % 256)
}
fn unlink_ptr_node_if_exists(fs: &FileSystemHandle, id: u32) -> Result<(), Errno> {
let pts_filename = id.to_string();
match fs.root().unlink(pts_filename.as_bytes(), UnlinkKind::NonDirectory) {
Err(e) if e == ENOENT => Ok(()),
other => other,
}
}
struct DevPtmx {
fs: FileSystemHandle,
state: Arc<TTYState>,
}
impl DevPtmx {
pub fn new(fs: FileSystemHandle, state: Arc<TTYState>) -> Self {
Self { fs, state }
}
}
impl WithStaticDeviceId for DevPtmx {
const ID: DeviceType = DeviceType::PTMX;
}
impl DeviceOps for DevPtmx {
fn open(
&self,
current_task: &CurrentTask,
_id: DeviceType,
_node: &FsNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
let terminal = self.state.get_next_terminal(current_task)?;
Ok(Box::new(DevPtmxFile::new(self.fs.clone(), terminal)))
}
}
struct DevPtmxFile {
fs: FileSystemHandle,
terminal: Arc<Terminal>,
}
impl DevPtmxFile {
pub fn new(fs: FileSystemHandle, terminal: Arc<Terminal>) -> Self {
Self { fs, terminal }
}
}
impl Drop for DevPtmxFile {
fn drop(&mut self) {
unlink_ptr_node_if_exists(&self.fs, self.terminal.id).unwrap();
}
}
impl FileOps for DevPtmxFile {
fileops_impl_nonseekable!();
fn close(&self, _file: &FileObject) {}
fn read(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_data: &[UserBuffer],
) -> Result<usize, Errno> {
error!(EOPNOTSUPP)
}
fn write(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_data: &[UserBuffer],
) -> Result<usize, Errno> {
error!(EOPNOTSUPP)
}
fn wait_async(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_waiter: &Arc<Waiter>,
_events: FdEvents,
_handler: EventHandler,
) -> WaitKey {
WaitKey::empty()
}
fn cancel_wait(&self, _current_task: &CurrentTask, _waiter: &Arc<Waiter>, _key: WaitKey) {}
fn query_events(&self, _current_task: &CurrentTask) -> FdEvents {
FdEvents::empty()
}
fn fcntl(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_cmd: u32,
_arg: u64,
) -> Result<SyscallResult, Errno> {
error!(EOPNOTSUPP)
}
fn ioctl(
&self,
_file: &FileObject,
current_task: &CurrentTask,
request: u32,
user_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
match request {
TIOCGPTN => {
// Get the therminal id.
let value: u32 = self.terminal.id as u32;
current_task.mm.write_object(UserRef::<u32>::new(user_addr), &value)?;
Ok(SUCCESS)
}
TIOCGPTLCK => {
// Get the lock status.
let value = if self.terminal.read().locked { 1 } else { 0 };
current_task.mm.write_object(UserRef::<i32>::new(user_addr), &value)?;
Ok(SUCCESS)
}
TIOCSPTLCK => {
// Lock/Unlock the terminal.
let mut value = 0;
current_task.mm.read_object(UserRef::<i32>::new(user_addr), &mut value)?;
self.terminal.write().locked = value != 0;
Ok(SUCCESS)
}
_ => shared_ioctl(&self.terminal, true, _file, current_task, request, user_addr),
}
}
}
struct DevPts {
state: Arc<TTYState>,
}
impl DevPts {
pub fn new(state: Arc<TTYState>) -> Self {
Self { state }
}
}
impl DeviceOps for DevPts {
fn open(
&self,
current_task: &CurrentTask,
id: DeviceType,
_node: &FsNode,
flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
let pts_id = (id.major() - DEVPTS_FIRST_MAJOR) * 256 + id.minor();
let terminal = self.state.terminals.read().get(&pts_id).ok_or(EIO)?.upgrade().ok_or(EIO)?;
if terminal.read().locked {
return error!(EIO);
}
if !flags.contains(OpenFlags::NOCTTY) {
// Opening a replica sets the process' controlling TTY when possible. An error indicates it cannot
// be set, and is ignored silently.
let _ = current_task.thread_group.set_controlling_terminal(
current_task,
&terminal,
false, /* is_main */
false, /* steal */
flags.can_read(),
);
}
Ok(Box::new(DevPtsFile::new(terminal)))
}
}
struct DevPtsFile {
terminal: Arc<Terminal>,
}
impl DevPtsFile {
pub fn new(terminal: Arc<Terminal>) -> Self {
Self { terminal }
}
}
impl FileOps for DevPtsFile {
fileops_impl_nonseekable!();
fn close(&self, _file: &FileObject) {}
fn read(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_data: &[UserBuffer],
) -> Result<usize, Errno> {
error!(EOPNOTSUPP)
}
fn write(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_data: &[UserBuffer],
) -> Result<usize, Errno> {
error!(EOPNOTSUPP)
}
fn wait_async(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_waiter: &Arc<Waiter>,
_events: FdEvents,
_handler: EventHandler,
) -> WaitKey {
WaitKey::empty()
}
fn cancel_wait(&self, _current_task: &CurrentTask, _waiter: &Arc<Waiter>, _key: WaitKey) {}
fn query_events(&self, _current_task: &CurrentTask) -> FdEvents {
FdEvents::empty()
}
fn fcntl(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_cmd: u32,
_arg: u64,
) -> Result<SyscallResult, Errno> {
error!(EOPNOTSUPP)
}
fn ioctl(
&self,
file: &FileObject,
current_task: &CurrentTask,
request: u32,
user_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
shared_ioctl(&self.terminal, false, file, current_task, request, user_addr)
}
}
/// The ioctl behaviour common to main and replica terminal file descriptors.
fn shared_ioctl(
terminal: &Arc<Terminal>,
is_main: bool,
file: &FileObject,
current_task: &CurrentTask,
request: u32,
user_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
match request {
TIOCSCTTY => {
// Make the given terminal the controlling terminal of the calling process.
let steal = !user_addr.is_null();
current_task.thread_group.set_controlling_terminal(
current_task,
terminal,
is_main,
steal,
file.can_read(),
)?;
Ok(SUCCESS)
}
TIOCNOTTY => {
// Release the controlling terminal.
current_task.thread_group.release_controlling_terminal(
current_task,
terminal,
is_main,
)?;
Ok(SUCCESS)
}
TIOCGPGRP => {
// Get the foreground process group.
let pgid = current_task.thread_group.get_foreground_process_group(terminal, is_main)?;
current_task.mm.write_object(UserRef::<pid_t>::new(user_addr), &pgid)?;
Ok(SUCCESS)
}
TIOCSPGRP => {
// Set the foreground process group.
let mut pgid = 0;
current_task.mm.read_object(UserRef::<pid_t>::new(user_addr), &mut pgid)?;
current_task.thread_group.set_foreground_process_group(
current_task,
terminal,
is_main,
pgid,
)?;
Ok(SUCCESS)
}
TIOCGWINSZ => {
// Get the window size
current_task.mm.write_object(
UserRef::<uapi::winsize>::new(user_addr),
&terminal.read().window_size,
)?;
Ok(SUCCESS)
}
TIOCSWINSZ => {
// Set the window size
current_task.mm.read_object(
UserRef::<uapi::winsize>::new(user_addr),
&mut terminal.write().window_size,
)?;
// Send a SIGWINCH signal to the foreground process group.
let foreground_process_group_id = terminal
.read()
.get_controlling_session(is_main)
.as_ref()
.map(|cs| cs.foregound_process_group);
let process_group: Option<Arc<ProcessGroup>> = foreground_process_group_id
.map(|id| current_task.thread_group.kernel.pids.read().get_process_group(id))
.flatten();
if let Some(process_group) = process_group {
process_group.send_signals(&[SIGWINCH]);
}
Ok(SUCCESS)
}
TCGETS => {
// N.B. TCGETS on the main terminal actually returns the configuration of the replica
// end.
current_task
.mm
.write_object(UserRef::<uapi::termios>::new(user_addr), &terminal.read().termios)?;
Ok(SUCCESS)
}
TCSETS => {
// N.B. TCSETS on the main terminal actually affects the configuration of the replica
// end.
current_task.mm.read_object(
UserRef::<uapi::termios>::new(user_addr),
&mut terminal.write().termios,
)?;
Ok(SUCCESS)
}
TCSETSW => {
// TODO(qsr): This should drain the output queue first.
current_task.mm.read_object(
UserRef::<uapi::termios>::new(user_addr),
&mut terminal.write().termios,
)?;
Ok(SUCCESS)
}
_ => {
tracing::error!(
"{} received unknown ioctl request 0x{:08x}",
if is_main { "ptmx" } else { "pts" },
request
);
error!(EINVAL)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::Credentials;
use crate::testing::*;
fn ioctl<T: zerocopy::AsBytes + zerocopy::FromBytes + Copy>(
task: &CurrentTask,
file: &FileHandle,
command: u32,
value: &T,
) -> Result<T, Errno> {
let address = map_memory(&task, UserAddress::default(), std::mem::size_of::<T>() as u64);
let address_ref = UserRef::<T>::new(address);
task.mm.write_object(address_ref, value)?;
file.ioctl(&task, command, address)?;
let mut result = *value;
task.mm.read_object(address_ref, &mut result)?;
Ok(result)
}
fn set_controlling_terminal(
task: &CurrentTask,
file: &FileHandle,
steal: bool,
) -> Result<SyscallResult, Errno> {
file.ioctl(&task, TIOCSCTTY, UserAddress::from(if steal { 1 } else { 0 }))
}
fn open_file_with_flags(
task: &CurrentTask,
fs: &FileSystemHandle,
name: &FsStr,
flags: OpenFlags,
) -> Result<FileHandle, Errno> {
let component = fs.root().component_lookup(name)?;
Ok(FileObject::new_anonymous(
component.node.open(task, flags)?,
component.node.clone(),
flags,
))
}
fn open_file(
task: &CurrentTask,
fs: &FileSystemHandle,
name: &FsStr,
) -> Result<FileHandle, Errno> {
open_file_with_flags(task, fs, name, OpenFlags::RDWR | OpenFlags::NOCTTY)
}
fn open_ptmx_and_unlock(
task: &CurrentTask,
fs: &FileSystemHandle,
) -> Result<FileHandle, Errno> {
let file = open_file_with_flags(task, fs, b"ptmx", OpenFlags::RDWR)?;
// Unlock terminal
ioctl::<i32>(task, &file, TIOCSPTLCK, &0)?;
Ok(file)
}
#[::fuchsia::test]
fn opening_ptmx_creates_pts() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let root = fs.root();
root.component_lookup(b"0").unwrap_err();
let _ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
root.component_lookup(b"0").expect("pty");
}
#[::fuchsia::test]
fn closing_ptmx_closes_pts() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let root = fs.root();
root.component_lookup(b"0").unwrap_err();
let ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let _pts = open_file(&task, &fs, b"0").expect("open file");
std::mem::drop(ptmx);
root.component_lookup(b"0").unwrap_err();
}
#[::fuchsia::test]
fn pts_are_reused() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let root = fs.root();
let _ptmx0 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let mut _ptmx1 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let _ptmx2 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
root.component_lookup(b"0").expect("component_lookup");
root.component_lookup(b"1").expect("component_lookup");
root.component_lookup(b"2").expect("component_lookup");
std::mem::drop(_ptmx1);
root.component_lookup(b"1").unwrap_err();
_ptmx1 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
root.component_lookup(b"1").expect("component_lookup");
}
#[::fuchsia::test]
fn opening_inexistant_replica_fails() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let pts = fs
.root()
.create_node(
b"custom_pts",
FileMode::IFCHR | FileMode::from_bits(0o666),
DeviceType::new(DEVPTS_FIRST_MAJOR, 0),
)
.expect("custom_pts");
assert!(pts.node.open(&task, OpenFlags::RDONLY).is_err());
}
#[::fuchsia::test]
fn deleting_pts_nodes_do_not_crash() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let root = fs.root();
let ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
root.component_lookup(b"0").expect("component_lookup");
root.unlink(b"0", UnlinkKind::NonDirectory).expect("unlink");
root.component_lookup(b"0").unwrap_err();
std::mem::drop(ptmx);
}
#[::fuchsia::test]
fn test_unknown_ioctl() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
assert_eq!(ptmx.ioctl(&task, 42, UserAddress::default()), Err(EINVAL));
let pts_file = open_file(&task, &fs, b"0").expect("open file");
assert_eq!(pts_file.ioctl(&task, 42, UserAddress::default()), Err(EINVAL));
}
#[::fuchsia::test]
fn test_tiocgptn_ioctl() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let ptmx0 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let ptmx1 = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let pts0 = ioctl::<u32>(&task, &ptmx0, TIOCGPTN, &0).expect("ioctl");
assert_eq!(pts0, 0);
let pts1 = ioctl::<u32>(&task, &ptmx1, TIOCGPTN, &0).expect("ioctl");
assert_eq!(pts1, 1);
}
#[::fuchsia::test]
fn test_new_terminal_is_locked() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let _ptmx_file = open_file(&task, &fs, b"ptmx").expect("open file");
let pts = fs.root().component_lookup(b"0").expect("component_lookup");
assert_eq!(pts.node.open(&task, OpenFlags::RDONLY).map(|_| ()), Err(EIO));
}
#[::fuchsia::test]
fn test_lock_ioctls() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let pts = fs.root().component_lookup(b"0").expect("component_lookup");
// Check that the lock is not set.
assert_eq!(ioctl::<i32>(&task, &ptmx, TIOCGPTLCK, &0), Ok(0));
// /dev/pts/0 can be opened
pts.node.open(&task, OpenFlags::RDONLY).expect("open");
// Lock the terminal
ioctl::<i32>(&task, &ptmx, TIOCSPTLCK, &42).expect("ioctl");
// Check that the lock is set.
assert_eq!(ioctl::<i32>(&task, &ptmx, TIOCGPTLCK, &0), Ok(1));
// /dev/pts/0 cannot be opened
assert_eq!(pts.node.open(&task, OpenFlags::RDONLY).map(|_| ()), Err(EIO));
}
#[::fuchsia::test]
fn test_ptmx_stats() {
let (kernel, task) = create_kernel_and_task();
task.write().creds = Credentials::from_passwd("nobody:x:22:22").expect("credentials");
let fs = dev_pts_fs(&kernel);
let ptmx = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
let ptmx_stat = ptmx.node().stat().expect("stat");
assert_eq!(ptmx_stat.st_blksize, BLOCK_SIZE);
let pts = open_file(&task, &fs, b"0").expect("open file");
let pts_stats = pts.node().stat().expect("stat");
assert_eq!(pts_stats.st_mode & FileMode::PERMISSIONS.bits(), 0o620);
assert_eq!(pts_stats.st_uid, 22);
// TODO(qsr): Check that gid is tty.
}
#[::fuchsia::test]
fn test_attach_terminal_when_open() {
let (kernel, task) = create_kernel_and_task();
let fs = dev_pts_fs(&kernel);
let _opened_main = open_ptmx_and_unlock(&task, &fs).expect("ptmx");
// Opening the main terminal should not set the terminal of the session.
assert!(task
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.is_none());
// Opening the terminal should not set the terminal of the session with the NOCTTY flag.
let _opened_replica2 =
open_file_with_flags(&task, &fs, b"0", OpenFlags::RDWR | OpenFlags::NOCTTY)
.expect("open file");
assert!(task
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.is_none());
// Opening the replica terminal should set the terminal of the session.
let _opened_replica2 =
open_file_with_flags(&task, &fs, b"0", OpenFlags::RDWR).expect("open file");
assert!(task
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.is_some());
}
#[::fuchsia::test]
fn test_attach_terminal() {
let (kernel, task1) = create_kernel_and_task();
let task2 = task1.clone_task_for_test(0);
task2.thread_group.setsid().expect("setsid");
let fs = dev_pts_fs(&kernel);
let opened_main = open_ptmx_and_unlock(&task1, &fs).expect("ptmx");
let opened_replica = open_file(&task2, &fs, b"0").expect("open file");
assert_eq!(ioctl::<i32>(&task1, &opened_main, TIOCGPGRP, &0), Err(ENOTTY));
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), Err(ENOTTY));
set_controlling_terminal(&task1, &opened_main, false).unwrap();
assert_eq!(
ioctl::<i32>(&task1, &opened_main, TIOCGPGRP, &0),
Ok(task1.thread_group.read().process_group.leader)
);
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0), Err(ENOTTY));
set_controlling_terminal(&task2, &opened_replica, false).unwrap();
assert_eq!(
ioctl::<i32>(&task2, &opened_replica, TIOCGPGRP, &0),
Ok(task2.thread_group.read().process_group.leader)
);
}
#[::fuchsia::test]
fn test_steal_terminal() {
let (kernel, task1) = create_kernel_and_task();
task1.write().creds = Credentials::from_passwd("nobody:x:1:1").expect("credentials");
let task2 = task1.clone_task_for_test(0);
let fs = dev_pts_fs(&kernel);
let _opened_main = open_ptmx_and_unlock(&task1, &fs).expect("ptmx");
let wo_opened_replica =
open_file_with_flags(&task1, &fs, b"0", OpenFlags::WRONLY | OpenFlags::NOCTTY)
.expect("open file");
assert!(!wo_opened_replica.can_read());
// FD must be readable for setting the terminal.
assert_eq!(set_controlling_terminal(&task1, &wo_opened_replica, false), Err(EPERM));
let opened_replica = open_file(&task2, &fs, b"0").expect("open file");
// Task must be session leader for setting the terminal.
assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), Err(EINVAL));
// Associate terminal to task1.
set_controlling_terminal(&task1, &opened_replica, false)
.expect("Associate terminal to task1");
// One cannot associate a terminal to a process that has already one
assert_eq!(set_controlling_terminal(&task1, &opened_replica, false), Err(EINVAL));
task2.thread_group.setsid().expect("setsid");
// One cannot associate a terminal that is already associated with another process.
assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), Err(EPERM));
// One cannot steal a terminal without the CAP_SYS_ADMIN capacility
assert_eq!(set_controlling_terminal(&task2, &opened_replica, true), Err(EPERM));
// One can steal a terminal with the CAP_SYS_ADMIN capacility
task2.write().creds = Credentials::from_passwd("root:x:0:0").expect("credentials");
// But not without specifying that one wants to steal it.
assert_eq!(set_controlling_terminal(&task2, &opened_replica, false), Err(EPERM));
set_controlling_terminal(&task2, &opened_replica, true)
.expect("Associate terminal to task2");
assert!(task1
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.is_none());
}
#[::fuchsia::test]
fn test_set_foreground_process() {
let (kernel, init) = create_kernel_and_task();
let task1 = init.clone_task_for_test(0);
task1.thread_group.setsid().expect("setsid");
let task2 = task1.clone_task_for_test(0);
task2.thread_group.setpgid(&task2, 0).expect("setpgid");
let task2_pgid = task2.thread_group.read().process_group.leader;
assert_ne!(task2_pgid, task1.thread_group.read().process_group.leader);
let fs = dev_pts_fs(&kernel);
let _opened_main = open_ptmx_and_unlock(&init, &fs).expect("ptmx");
let opened_replica = open_file(&task2, &fs, b"0").expect("open file");
// Cannot change the foreground process group if the terminal is not the controlling
// terminal
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &task2_pgid), Err(ENOTTY));
// Attach terminal to task1 and task2 session.
set_controlling_terminal(&task1, &opened_replica, false).unwrap();
// The foreground process group should be the one of task1
assert_eq!(
ioctl::<i32>(&task1, &opened_replica, TIOCGPGRP, &0),
Ok(task1.thread_group.read().process_group.leader)
);
// Cannot change the foreground process group to a negative pid.
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &-1), Err(EINVAL));
// Cannot change the foreground process group to a invalid process group.
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &255), Err(ESRCH));
// Cannot change the foreground process group to a process group in another session.
let init_pgid = init.thread_group.read().process_group.leader;
assert_eq!(ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &init_pgid,), Err(EPERM));
// Set the foregound process to task2 process group
ioctl::<i32>(&task2, &opened_replica, TIOCSPGRP, &task2_pgid).unwrap();
// Check that the foreground process has been changed.
let terminal = Arc::clone(
&task1
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.as_ref()
.unwrap()
.terminal,
);
assert_eq!(
terminal
.read()
.get_controlling_session(false)
.as_ref()
.unwrap()
.foregound_process_group,
task2_pgid
);
}
#[::fuchsia::test]
fn test_detach_session() {
let (kernel, task1) = create_kernel_and_task();
let task2 = task1.clone_task_for_test(0);
task2.thread_group.setsid().expect("setsid");
let fs = dev_pts_fs(&kernel);
let _opened_main = open_ptmx_and_unlock(&task1, &fs).expect("ptmx");
let opened_replica = open_file(&task1, &fs, b"0").expect("open file");
// Cannot detach the controlling terminal when none is attached terminal
assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCNOTTY, &0), Err(ENOTTY));
set_controlling_terminal(&task2, &opened_replica, false).expect("set controlling terminal");
// Cannot detach the controlling terminal when not the session leader.
assert_eq!(ioctl::<i32>(&task1, &opened_replica, TIOCNOTTY, &0), Err(ENOTTY));
// Detach the terminal
ioctl::<i32>(&task2, &opened_replica, TIOCNOTTY, &0).expect("detach terminal");
assert!(task2
.thread_group
.read()
.process_group
.session
.read()
.controlling_terminal
.is_none());
}
}