blob: 4f10244d6279828f20755f5e8c76c095c6b0aab7 [file]
// 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 fuchsia_zircon::{self as zx, AsHandleRef, Task as zxTask};
use fuchsia_zircon_sys as sys;
use parking_lot::{Mutex, RwLock};
use std::collections::HashSet;
use std::ffi::CStr;
use std::sync::Arc;
use crate::errno;
use crate::from_status_like_fdio;
use crate::signals::*;
use crate::task::*;
use crate::types::*;
pub struct ThreadGroup {
/// The kernel to which this thread group belongs.
pub kernel: Arc<Kernel>,
/// A handle to the underlying Zircon process object.
///
/// Currently, we have a 1-to-1 mapping between thread groups and zx::process
/// objects. This approach might break down if/when we implement CLONE_VM
/// without CLONE_THREAD because that creates a situation where two thread
/// groups share an address space. To implement that situation, we might
/// need to break the 1-to-1 mapping between thread groups and zx::process
/// or teach zx::process to share address spaces.
pub process: zx::Process,
/// The lead task of this thread group.
///
/// The lead task is typically the initial thread created in the thread group.
pub leader: pid_t,
/// The tasks in the thread group.
pub tasks: RwLock<HashSet<pid_t>>,
/// The itimers for this thread group.
pub itimers: RwLock<[itimerval; 3]>,
zombie_leader: Mutex<Option<ZombieTask>>,
/// WaitQueue for updates to the zombie_children lists of tasks in this group.
pub child_exit_waiters: Mutex<WaitQueue>,
}
impl PartialEq for ThreadGroup {
fn eq(&self, other: &Self) -> bool {
self.leader == other.leader
}
}
impl ThreadGroup {
pub fn new(kernel: Arc<Kernel>, process: zx::Process, leader: pid_t) -> ThreadGroup {
let mut tasks = HashSet::new();
tasks.insert(leader);
ThreadGroup {
kernel,
process,
leader,
tasks: RwLock::new(tasks),
itimers: Default::default(),
zombie_leader: Mutex::new(None),
child_exit_waiters: Mutex::default(),
}
}
pub fn exit(&self) {
// TODO: Set the error_code on the Zircon process object. Currently missing a way
// to do this in Zircon. Might be easier in the new execution model.
self.process.kill().expect("Failed to terminate process.");
// TODO: Should we wait for the process to actually terminate?
}
pub fn add(&self, task: &Task) {
let mut tasks = self.tasks.write();
tasks.insert(task.id);
}
fn remove_internal(&self, task: &Arc<Task>) -> bool {
let mut tasks = self.tasks.write();
self.kernel.pids.write().remove_task(task.id);
tasks.remove(&task.id);
if task.id == self.leader {
*self.zombie_leader.lock() = Some(task.as_zombie());
}
tasks.is_empty()
}
pub fn remove(&self, task: &Arc<Task>) {
if self.remove_internal(task) {
let zombie =
self.zombie_leader.lock().take().expect("Failed to capture zombie leader.");
if let Some(parent) = self.kernel.pids.read().get_task(zombie.parent) {
parent.zombie_children.lock().push(zombie);
// TODO: Should this be zombie_leader.exit_signal?
send_signal(&parent, SignalInfo::default(SIGCHLD));
}
if let Some(parent) = self.kernel.pids.read().get_task(task.parent) {
parent.thread_group.child_exit_waiters.lock().notify_all();
}
self.kernel.pids.write().remove_thread_group(self.leader);
}
}
/// Sets the name of process associated with this thread group.
///
/// - `name`: The name to set for the process. If the length of `name` is >= `ZX_MAX_NAME_LEN`,
/// a truncated version of the name will be used.
pub fn set_name(&self, name: &CStr) -> Result<(), Errno> {
if name.to_bytes().len() >= sys::ZX_MAX_NAME_LEN {
// TODO: Might want to use [..sys::ZX_MAX_NAME_LEN] of only the last path component.
let mut clone = name.clone().to_owned().into_bytes();
clone[sys::ZX_MAX_NAME_LEN - 1] = 0;
let name = CStr::from_bytes_with_nul(&clone[..sys::ZX_MAX_NAME_LEN])
.map_err(|_| errno!(EINVAL))?;
self.process.set_name(name).map_err(|status| from_status_like_fdio!(status))
} else {
self.process.set_name(name).map_err(|status| from_status_like_fdio!(status))
}
}
}
#[cfg(test)]
mod test {
use fuchsia_async as fasync;
use super::*;
use crate::testing::*;
use std::ffi::CString;
#[fasync::run_singlethreaded(test)]
async fn test_long_name() {
let (_kernel, current_task) = create_kernel_and_task();
let bytes = [1; sys::ZX_MAX_NAME_LEN];
let name = CString::new(bytes).unwrap();
let max_bytes = [1; sys::ZX_MAX_NAME_LEN - 1];
let expected_name = CString::new(max_bytes).unwrap();
assert!(current_task.thread_group.set_name(&name).is_ok());
assert_eq!(current_task.thread_group.process.get_name(), Ok(expected_name));
}
#[fasync::run_singlethreaded(test)]
async fn test_max_length_name() {
let (_kernel, current_task) = create_kernel_and_task();
let bytes = [1; sys::ZX_MAX_NAME_LEN - 1];
let name = CString::new(bytes).unwrap();
assert!(current_task.thread_group.set_name(&name).is_ok());
assert_eq!(current_task.thread_group.process.get_name(), Ok(name));
}
#[fasync::run_singlethreaded(test)]
async fn test_short_name() {
let (_kernel, current_task) = create_kernel_and_task();
let bytes = [1; sys::ZX_MAX_NAME_LEN - 10];
let name = CString::new(bytes).unwrap();
assert!(current_task.thread_group.set_name(&name).is_ok());
assert_eq!(current_task.thread_group.process.get_name(), Ok(name));
}
}