| // Copyright 2022 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. |
| |
| //! This file implements control group hierarchy. |
| //! |
| //! There is no support for actual resource constraints, or any operations outside of adding tasks |
| //! to a control group (for the duration of their lifetime). |
| |
| use crate::signals::{SignalInfo, send_freeze_signal}; |
| use crate::task::waiter::WaiterOptions; |
| use crate::task::{Kernel, ThreadGroup, ThreadGroupKey, WaitQueue, Waiter}; |
| use crate::vfs::{FsStr, FsString, PathBuilder}; |
| use starnix_logging::{CATEGORY_STARNIX, log_warn, trace_duration, track_stub}; |
| use starnix_sync::{FileOpsCore, LockBefore, Locked, Mutex, MutexGuard, ThreadGroupLimits}; |
| use starnix_types::ownership::TempRef; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::signals::SIGKILL; |
| use starnix_uapi::{errno, error, pid_t}; |
| use std::collections::{BTreeMap, HashMap, HashSet, btree_map, hash_map}; |
| use std::ops::{Deref, DerefMut}; |
| use std::sync::atomic::{AtomicU64, Ordering}; |
| use std::sync::{Arc, Weak}; |
| |
| use crate::signals::KernelSignal; |
| |
| /// All cgroups of the kernel. There is a single cgroup v2 hierarchy, and one-or-more cgroup v1 |
| /// hierarchies. |
| /// TODO(https://fxbug.dev/389748287): Add cgroup v1 hierarchies on the kernel. |
| #[derive(Debug)] |
| pub struct KernelCgroups { |
| pub cgroup2: Arc<CgroupRoot>, |
| } |
| |
| impl KernelCgroups { |
| /// Returns a locked `CgroupPidTable`, which guarantees that processes would not move in this |
| /// cgroup hierarchy until the lock is freed. |
| /// |
| /// Note: Mutex dependency graph: |
| /// |
| /// `KernelPidTable` -> `CgroupRootPidTable` -> `CgroupState` -> `ThreadGroupState` |
| pub fn lock_cgroup2_pid_table(&self) -> MutexGuard<'_, CgroupPidTable> { |
| self.cgroup2.pid_table.lock() |
| } |
| } |
| |
| impl Default for KernelCgroups { |
| fn default() -> Self { |
| Self { cgroup2: CgroupRoot::new() } |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub enum FreezerState { |
| Thawed, |
| Frozen, |
| } |
| |
| impl Default for FreezerState { |
| fn default() -> Self { |
| FreezerState::Thawed |
| } |
| } |
| |
| impl std::fmt::Display for FreezerState { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| FreezerState::Frozen => write!(f, "1"), |
| FreezerState::Thawed => write!(f, "0"), |
| } |
| } |
| } |
| |
| #[derive(Default)] |
| pub struct CgroupFreezerState { |
| /// Cgroups's own freezer state as set by the `cgroup.freeze` file. |
| pub self_freezer_state: FreezerState, |
| /// Considers both the cgroup's self freezer state as set by the `cgroup.freeze` file and |
| /// the freezer state of its ancestors. A cgroup is considered frozen if either itself or any |
| /// of its ancestors is frozen. |
| pub effective_freezer_state: FreezerState, |
| } |
| |
| /// Common operations of all cgroups. |
| pub trait CgroupOps: Send + Sync + 'static { |
| /// Returns the unique ID of the cgroup. ID of root cgroup is 0. |
| fn id(&self) -> u64; |
| |
| /// Add a process to a cgroup. Errors if the cgroup has been deleted. |
| fn add_process( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| thread_group: &ThreadGroup, |
| ) -> Result<(), Errno>; |
| |
| /// Create a new sub-cgroup as a child of this cgroup. Errors if the cgroup is deleted, or a |
| /// child with `name` already exists. |
| fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>; |
| |
| /// Gets all children of this cgroup. |
| fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno>; |
| |
| /// Gets the child with `name`, errors if not found. |
| fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>; |
| |
| /// Remove a child from this cgroup and return it, if found. Errors if cgroup is deleted, or a |
| /// child with `name` is not found. |
| fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>; |
| |
| /// Return all pids that belong to this cgroup. |
| fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t>; |
| |
| /// Kills all processes in the cgroup and its descendants. |
| fn kill(&self); |
| |
| /// Whether the cgroup or any of its descendants have any processes. |
| fn is_populated(&self) -> bool; |
| |
| /// Get the freezer `self state` and `effective state`. |
| fn get_freezer_state(&self) -> CgroupFreezerState; |
| |
| /// Freeze all tasks in the cgroup. |
| fn freeze(&self, locked: &mut Locked<FileOpsCore>); |
| |
| /// Thaw all tasks in the cgroup. |
| fn thaw(&self); |
| } |
| |
| /// `CgroupPidTable` contains the mapping of `ThreadGroup` (by pid) to non-root cgroup. |
| /// If `pid` is valid but does not exist in the mapping, then it is assumed to be in the root cgroup. |
| #[derive(Debug, Default)] |
| pub struct CgroupPidTable(HashMap<ThreadGroupKey, Weak<Cgroup>>); |
| impl Deref for CgroupPidTable { |
| type Target = HashMap<ThreadGroupKey, Weak<Cgroup>>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| impl DerefMut for CgroupPidTable { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.0 |
| } |
| } |
| |
| impl CgroupPidTable { |
| /// Add a newly created `ThreadGroup` to the same cgroup as its parent. Assumes that |
| /// `ThreadGroup` does not have any `Task` associated with it. |
| pub fn inherit_cgroup(&mut self, parent: &ThreadGroup, child: &ThreadGroup) { |
| assert!(child.read().tasks_count() == 0, "threadgroup must be newly created"); |
| if let Some(weak_cgroup) = self.0.get(&parent.into()).cloned() { |
| let Some(cgroup) = weak_cgroup.upgrade() else { |
| log_warn!("ignored attempt to inherit a non-existant cgroup"); |
| return; |
| }; |
| assert!( |
| self.0.insert(child.into(), weak_cgroup).map(|c| c.strong_count() == 0).is_none(), |
| "child pid should not exist when inheriting" |
| ); |
| // Skip freezer propagation because the `ThreadGroup` is newly created and has no tasks. |
| cgroup.state.lock().processes.insert(child.into()); |
| } |
| } |
| |
| /// Creates a new `KernelSignal` for a new `Task`, if that `Task` is added to a frozen cgroup. |
| pub fn maybe_create_freeze_signal<TG: Copy + Into<ThreadGroupKey>>( |
| &self, |
| tg: TG, |
| ) -> Option<KernelSignal> { |
| let Some(weak_cgroup) = self.0.get(&tg.into()) else { |
| return None; |
| }; |
| let Some(cgroup) = weak_cgroup.upgrade() else { |
| return None; |
| }; |
| let state = cgroup.state.lock(); |
| if state.get_effective_freezer_state() != FreezerState::Frozen { |
| return None; |
| } |
| Some(KernelSignal::Freeze(state.create_freeze_waiter())) |
| } |
| |
| /// Remove a `ThreadGroup` from the root cgroup pid table and from the cgroup it is in. |
| pub fn remove_process(&mut self, thread_group_key: ThreadGroupKey) { |
| if let Some(entry) = self.remove(&thread_group_key) { |
| if let Some(cgroup) = entry.upgrade() { |
| cgroup.state.lock().processes.remove(&thread_group_key); |
| } |
| } |
| } |
| } |
| |
| /// `CgroupRoot` is the root of the cgroup hierarchy. The root cgroup is different from the rest of |
| /// the cgroups in a cgroup hierarchy (sub-cgroups of the root) in a few ways: |
| /// |
| /// - The root contains all known processes on cgroup creation, and all new processes as they are |
| /// spawned. As such, the root cgroup reports processes belonging to it differently than its |
| /// sub-cgroups. |
| /// |
| /// - The root does not contain resource controller interface files, as otherwise they would apply |
| /// to the whole system. |
| /// |
| /// - The root does not own a `FsNode` as it is created and owned by the `FileSystem` instead. |
| #[derive(Debug)] |
| pub struct CgroupRoot { |
| /// Look up cgroup by pid. Must be locked before child states. |
| pid_table: Mutex<CgroupPidTable>, |
| |
| /// Sub-cgroups of this cgroup. |
| children: Mutex<CgroupChildren>, |
| |
| /// Weak reference to self, used when creating child cgroups. |
| weak_self: Weak<CgroupRoot>, |
| |
| /// Used to generate IDs for descendent Cgroups. |
| next_id: AtomicU64, |
| } |
| |
| impl CgroupRoot { |
| pub fn new() -> Arc<CgroupRoot> { |
| Arc::new_cyclic(|weak_self| Self { |
| pid_table: Default::default(), |
| children: Default::default(), |
| weak_self: weak_self.clone(), |
| next_id: AtomicU64::new(1), |
| }) |
| } |
| |
| fn get_next_id(&self) -> u64 { |
| self.next_id.fetch_add(1, Ordering::Relaxed) |
| } |
| |
| pub fn get_cgroup<TG: Copy + Into<ThreadGroupKey>>(&self, tg: TG) -> Option<Weak<Cgroup>> { |
| self.pid_table.lock().get(&tg.into()).cloned() |
| } |
| |
| pub fn get_cgroup_inspect(&self) -> fuchsia_inspect::Inspector { |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let cgroups = inspector.root(); |
| cgroups.record_uint("pids", self.pid_table.lock().len() as u64); |
| cgroups.record_uint("count", self.children.lock().count_descendants()); |
| inspector |
| } |
| } |
| |
| impl CgroupOps for CgroupRoot { |
| fn id(&self) -> u64 { |
| 0 |
| } |
| |
| fn add_process( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| thread_group: &ThreadGroup, |
| ) -> Result<(), Errno> { |
| let mut pid_table = self.pid_table.lock(); |
| if let Some(entry) = pid_table.remove(&thread_group.into()) { |
| // If pid is in a child cgroup, remove it. |
| if let Some(cgroup) = entry.upgrade() { |
| cgroup.state.lock().remove_process(locked, thread_group)?; |
| } |
| } |
| // Else if pid is not in a child cgroup, then it must be in the root cgroup already. |
| // This does not throw an error on Linux, so just return success here. |
| |
| Ok(()) |
| } |
| |
| fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let id = self.get_next_id(); |
| let new_child = Cgroup::new(id, name, &self.weak_self, None); |
| let mut children = self.children.lock(); |
| children.insert_child(name.into(), new_child) |
| } |
| |
| fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let children = self.children.lock(); |
| children.get_child(name).ok_or_else(|| errno!(ENOENT)) |
| } |
| |
| fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let mut children = self.children.lock(); |
| children.remove_child(name) |
| } |
| |
| fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> { |
| let children = self.children.lock(); |
| Ok(children.get_children()) |
| } |
| |
| fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t> { |
| let controlled_pids: HashSet<pid_t> = |
| self.pid_table.lock().keys().filter_map(|v| v.upgrade().map(|tg| tg.leader)).collect(); |
| let kernel_pids = kernel.pids.read().process_ids(); |
| kernel_pids.into_iter().filter(|pid| !controlled_pids.contains(pid)).collect() |
| } |
| |
| fn kill(&self) { |
| unreachable!("Root cgroup cannot kill its processes."); |
| } |
| |
| fn is_populated(&self) -> bool { |
| false |
| } |
| |
| fn get_freezer_state(&self) -> CgroupFreezerState { |
| Default::default() |
| } |
| |
| fn freeze(&self, _locked: &mut Locked<FileOpsCore>) { |
| unreachable!("Root cgroup cannot freeze any processes."); |
| } |
| |
| fn thaw(&self) { |
| unreachable!("Root cgroup cannot thaw any processes."); |
| } |
| } |
| |
| #[derive(Debug, Default)] |
| struct CgroupChildren(BTreeMap<FsString, CgroupHandle>); |
| impl CgroupChildren { |
| fn insert_child(&mut self, name: FsString, child: CgroupHandle) -> Result<CgroupHandle, Errno> { |
| let btree_map::Entry::Vacant(child_entry) = self.0.entry(name) else { |
| return error!(EEXIST); |
| }; |
| Ok(child_entry.insert(child).clone()) |
| } |
| |
| fn remove_child(&mut self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let btree_map::Entry::Occupied(child_entry) = self.0.entry(name.into()) else { |
| return error!(ENOENT); |
| }; |
| let child = child_entry.get(); |
| |
| let mut child_state = child.state.lock(); |
| assert!(!child_state.deleted, "child cannot be deleted"); |
| |
| child_state.update_processes(); |
| if !child_state.processes.is_empty() { |
| return error!(EBUSY); |
| } |
| if !child_state.children.is_empty() { |
| return error!(EBUSY); |
| } |
| |
| child_state.deleted = true; |
| drop(child_state); |
| |
| Ok(child_entry.remove()) |
| } |
| |
| fn get_child(&self, name: &FsStr) -> Option<CgroupHandle> { |
| self.0.get(name).cloned() |
| } |
| |
| fn get_children(&self) -> Vec<CgroupHandle> { |
| self.0.values().cloned().collect() |
| } |
| |
| fn count_descendants(&self) -> u64 { |
| self.0.values().map(|child| 1 + child.count_descendants()).sum() |
| } |
| } |
| |
| impl Deref for CgroupChildren { |
| type Target = BTreeMap<FsString, CgroupHandle>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| |
| #[derive(Debug, Default)] |
| struct CgroupState { |
| /// Subgroups of this control group. |
| children: CgroupChildren, |
| |
| /// The tasks that are part of this control group. |
| processes: HashSet<ThreadGroupKey>, |
| |
| /// If true, can no longer add children or tasks. |
| deleted: bool, |
| |
| /// Wait queue to thaw all blocked tasks in this cgroup. |
| wait_queue: WaitQueue, |
| |
| /// The cgroup's own freezer state. |
| self_freezer_state: FreezerState, |
| |
| /// Effective freezer state inherited from the parent cgroup. |
| inherited_freezer_state: FreezerState, |
| } |
| |
| impl CgroupState { |
| /// Creates a new Waiter that subscribes to the Cgroup's freezer WaitQueue. This `Waiter` can be |
| /// sent as a part of a `KernelSignal::Freeze` to freeze a `Task`. |
| fn create_freeze_waiter(&self) -> Waiter { |
| let waiter = Waiter::with_options(WaiterOptions::IGNORE_SIGNALS); |
| self.wait_queue.wait_async(&waiter); |
| waiter |
| } |
| |
| // Goes through `processes` and remove processes that are no longer alive. |
| fn update_processes(&mut self) { |
| self.processes.retain(|thread_group| { |
| let Some(thread_group) = thread_group.upgrade() else { |
| return false; |
| }; |
| let terminating = thread_group.read().is_terminating(); |
| !terminating |
| }); |
| } |
| |
| fn freeze_thread_group<L>(&self, locked: &mut Locked<L>, thread_group: &ThreadGroup) |
| where |
| L: LockBefore<ThreadGroupLimits>, |
| { |
| // Create static-lifetime TempRefs of Tasks so that we avoid don't hold the ThreadGroup |
| // lock while iterating and sending the signal. |
| // SAFETY: static TempRefs are released after all signals are queued. |
| let tasks = thread_group.read().tasks().map(TempRef::into_static).collect::<Vec<_>>(); |
| for task in tasks { |
| send_freeze_signal(locked, &task, self.create_freeze_waiter()) |
| .expect("sending freeze signal should not fail"); |
| } |
| } |
| |
| fn thaw_thread_group<L>(&self, _locked: &mut Locked<L>, thread_group: &ThreadGroup) |
| where |
| L: LockBefore<ThreadGroupLimits>, |
| { |
| // Create static-lifetime TempRefs of Tasks so that we avoid don't hold the ThreadGroup |
| // lock while iterating and sending the signal. |
| // SAFETY: static TempRefs are released after all signals are queued. |
| let tasks = thread_group.read().tasks().map(TempRef::into_static).collect::<Vec<_>>(); |
| for task in tasks { |
| task.write().thaw(); |
| task.interrupt(); |
| } |
| } |
| |
| fn get_effective_freezer_state(&self) -> FreezerState { |
| std::cmp::max(self.self_freezer_state, self.inherited_freezer_state) |
| } |
| |
| fn add_process<L>( |
| &mut self, |
| locked: &mut Locked<L>, |
| thread_group: &ThreadGroup, |
| ) -> Result<(), Errno> |
| where |
| L: LockBefore<ThreadGroupLimits>, |
| { |
| if self.deleted { |
| return error!(ENOENT); |
| } |
| self.processes.insert(thread_group.into()); |
| |
| if self.get_effective_freezer_state() == FreezerState::Frozen { |
| self.freeze_thread_group(locked, &thread_group); |
| } |
| Ok(()) |
| } |
| |
| fn remove_process<L>( |
| &mut self, |
| locked: &mut Locked<L>, |
| thread_group: &ThreadGroup, |
| ) -> Result<(), Errno> |
| where |
| L: LockBefore<ThreadGroupLimits>, |
| { |
| if self.deleted { |
| return error!(ENOENT); |
| } |
| self.processes.remove(&thread_group.into()); |
| |
| if self.get_effective_freezer_state() == FreezerState::Frozen { |
| self.thaw_thread_group(locked, thread_group); |
| } |
| Ok(()) |
| } |
| |
| fn propagate_freeze<L>(&mut self, locked: &mut Locked<L>, inherited_freezer_state: FreezerState) |
| where |
| L: LockBefore<ThreadGroupLimits>, |
| { |
| let prev_effective_freezer_state = self.get_effective_freezer_state(); |
| self.inherited_freezer_state = inherited_freezer_state; |
| if prev_effective_freezer_state == FreezerState::Frozen { |
| return; |
| } |
| |
| for thread_group in self.processes.iter() { |
| let Some(thread_group) = thread_group.upgrade() else { |
| continue; |
| }; |
| self.freeze_thread_group(locked, &thread_group); |
| } |
| |
| // Freeze all children cgroups while holding self state lock |
| for child in self.children.get_children() { |
| child.state.lock().propagate_freeze(locked, FreezerState::Frozen); |
| } |
| } |
| |
| fn propagate_thaw(&mut self, inherited_freezer_state: FreezerState) { |
| self.inherited_freezer_state = inherited_freezer_state; |
| if self.get_effective_freezer_state() == FreezerState::Thawed { |
| self.wait_queue.notify_all(); |
| for child in self.children.get_children() { |
| child.state.lock().propagate_thaw(FreezerState::Thawed); |
| } |
| } |
| } |
| |
| fn propagate_kill(&self) { |
| for thread_group in self.processes.iter() { |
| let Some(thread_group) = thread_group.upgrade() else { |
| continue; |
| }; |
| thread_group.write().send_signal(SignalInfo::default(SIGKILL)); |
| } |
| |
| // Recursively lock and kill children cgroups' processes. |
| for child in self.children.get_children() { |
| child.state.lock().propagate_kill(); |
| } |
| } |
| } |
| |
| /// `Cgroup` is a non-root cgroup in a cgroup hierarchy, and can have other `Cgroup`s as children. |
| #[derive(Debug)] |
| pub struct Cgroup { |
| root: Weak<CgroupRoot>, |
| |
| /// ID of the cgroup. |
| id: u64, |
| |
| /// Name of the cgroup. |
| name: FsString, |
| |
| /// Weak reference to its parent cgroup, `None` if direct descendent of the root cgroup. |
| /// This field is useful in implementing features that only apply to non-root cgroups. |
| parent: Option<Weak<Cgroup>>, |
| |
| /// Internal state of the Cgroup. |
| state: Mutex<CgroupState>, |
| |
| weak_self: Weak<Cgroup>, |
| } |
| pub type CgroupHandle = Arc<Cgroup>; |
| |
| /// Returns the path from the root to this `cgroup`. |
| pub fn path_from_root(weak_cgroup: Option<Weak<Cgroup>>) -> Result<FsString, Errno> { |
| let cgroup = match weak_cgroup { |
| Some(weak_cgroup) => Weak::upgrade(&weak_cgroup).ok_or_else(|| errno!(ENODEV))?, |
| None => return Ok("/".into()), |
| }; |
| let mut path = PathBuilder::new(); |
| let mut current = Some(cgroup); |
| while let Some(cgroup) = current { |
| path.prepend_element(cgroup.name()); |
| current = cgroup.parent()?; |
| } |
| Ok(path.build_absolute()) |
| } |
| |
| impl Cgroup { |
| pub fn new( |
| id: u64, |
| name: &FsStr, |
| root: &Weak<CgroupRoot>, |
| parent: Option<Weak<Cgroup>>, |
| ) -> CgroupHandle { |
| Arc::new_cyclic(|weak| Self { |
| id, |
| root: root.clone(), |
| name: name.to_owned(), |
| parent, |
| state: Default::default(), |
| weak_self: weak.clone(), |
| }) |
| } |
| |
| pub fn name(&self) -> &FsStr { |
| self.name.as_ref() |
| } |
| |
| fn root(&self) -> Result<Arc<CgroupRoot>, Errno> { |
| self.root.upgrade().ok_or_else(|| errno!(ENODEV)) |
| } |
| |
| /// Returns the upgraded parent cgroup, or `Ok(None)` if cgroup is a direct desendent of root. |
| /// Errors if parent node is no longer around. |
| fn parent(&self) -> Result<Option<CgroupHandle>, Errno> { |
| self.parent.as_ref().map(|weak| weak.upgrade().ok_or_else(|| errno!(ENODEV))).transpose() |
| } |
| |
| fn count_descendants(&self) -> u64 { |
| self.state.lock().children.count_descendants() |
| } |
| } |
| |
| impl CgroupOps for Cgroup { |
| fn id(&self) -> u64 { |
| self.id |
| } |
| |
| fn add_process( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| thread_group: &ThreadGroup, |
| ) -> Result<(), Errno> { |
| let root = self.root()?; |
| let mut pid_table = root.pid_table.lock(); |
| match pid_table.entry(thread_group.into()) { |
| hash_map::Entry::Occupied(mut entry) => { |
| // Check if thread_group is already in the current cgroup. Linux does not return an error if |
| // it already exists. |
| if std::ptr::eq(self, entry.get().as_ptr()) { |
| return Ok(()); |
| } |
| |
| // If thread_group is in another cgroup, we need to remove it first. |
| track_stub!(TODO("https://fxbug.dev/383374687"), "check permissions"); |
| if let Some(other_cgroup) = entry.get().upgrade() { |
| other_cgroup.state.lock().remove_process(locked, thread_group)?; |
| } |
| |
| self.state.lock().add_process(locked, thread_group)?; |
| entry.insert(self.weak_self.clone()); |
| } |
| hash_map::Entry::Vacant(entry) => { |
| self.state.lock().add_process(locked, thread_group)?; |
| entry.insert(self.weak_self.clone()); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let id = self.root()?.get_next_id(); |
| let new_child = Cgroup::new(id, name, &self.root, Some(self.weak_self.clone())); |
| let mut state = self.state.lock(); |
| if state.deleted { |
| return error!(ENOENT); |
| } |
| // New child should inherit the effective freezer state of the current cgroup. |
| new_child.state.lock().inherited_freezer_state = state.get_effective_freezer_state(); |
| state.children.insert_child(name.into(), new_child) |
| } |
| |
| fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let state = self.state.lock(); |
| state.children.get_child(name).ok_or_else(|| errno!(ENOENT)) |
| } |
| |
| fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> { |
| let mut state = self.state.lock(); |
| if state.deleted { |
| return error!(ENOENT); |
| } |
| state.children.remove_child(name) |
| } |
| |
| fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> { |
| let state = self.state.lock(); |
| if state.deleted { |
| return error!(ENOENT); |
| } |
| Ok(state.children.get_children()) |
| } |
| |
| fn get_pids(&self, _kernel: &Kernel) -> Vec<pid_t> { |
| let mut state = self.state.lock(); |
| state.update_processes(); |
| state.processes.iter().filter_map(|v| v.upgrade().map(|tg| tg.leader)).collect() |
| } |
| |
| fn kill(&self) { |
| trace_duration!(CATEGORY_STARNIX, "CgroupKill"); |
| let state = self.state.lock(); |
| state.propagate_kill(); |
| } |
| |
| fn is_populated(&self) -> bool { |
| let mut state = self.state.lock(); |
| if state.deleted { |
| return false; |
| } |
| state.update_processes(); |
| if !state.processes.is_empty() { |
| return true; |
| } |
| |
| state.children.get_children().into_iter().any(|child| child.is_populated()) |
| } |
| |
| fn get_freezer_state(&self) -> CgroupFreezerState { |
| let state = self.state.lock(); |
| CgroupFreezerState { |
| self_freezer_state: state.self_freezer_state, |
| effective_freezer_state: state.get_effective_freezer_state(), |
| } |
| } |
| |
| fn freeze(&self, locked: &mut Locked<FileOpsCore>) { |
| trace_duration!(CATEGORY_STARNIX, "CgroupFreeze"); |
| let mut state = self.state.lock(); |
| let inherited_freezer_state = state.inherited_freezer_state; |
| state.propagate_freeze(locked, inherited_freezer_state); |
| state.self_freezer_state = FreezerState::Frozen; |
| } |
| |
| fn thaw(&self) { |
| trace_duration!(CATEGORY_STARNIX, "CgroupThaw"); |
| let mut state = self.state.lock(); |
| state.self_freezer_state = FreezerState::Thawed; |
| let inherited_freezer_state = state.inherited_freezer_state; |
| state.propagate_thaw(inherited_freezer_state); |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::testing::spawn_kernel_and_run; |
| use assert_matches::assert_matches; |
| use starnix_uapi::signals::SIGCHLD; |
| use starnix_uapi::{CLONE_SIGHAND, CLONE_THREAD, CLONE_VM}; |
| |
| #[::fuchsia::test] |
| async fn cgroup_path_from_root() { |
| spawn_kernel_and_run(async |_, _| { |
| let root = CgroupRoot::new(); |
| |
| let test_cgroup = |
| root.new_child("test".into()).expect("new_child on root cgroup succeeds"); |
| let child_cgroup = test_cgroup |
| .new_child("child".into()) |
| .expect("new_child on non-root cgroup succeeds"); |
| |
| assert_eq!(path_from_root(Some(Arc::downgrade(&test_cgroup))), Ok("/test".into())); |
| assert_eq!( |
| path_from_root(Some(Arc::downgrade(&child_cgroup))), |
| Ok("/test/child".into()) |
| ); |
| }) |
| .await; |
| } |
| |
| #[::fuchsia::test] |
| async fn cgroup_clone_task_in_frozen_cgroup() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let kernel = current_task.kernel(); |
| let root = &kernel.cgroups.cgroup2; |
| let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds"); |
| |
| let process = current_task.clone_task_for_test(locked, 0, Some(SIGCHLD)); |
| cgroup |
| .add_process(locked.cast_locked(), process.thread_group()) |
| .expect("add process to cgroup"); |
| cgroup.freeze(locked.cast_locked()); |
| assert_eq!(cgroup.get_pids(&kernel).first(), Some(process.get_pid()).as_ref()); |
| assert_eq!( |
| root.get_cgroup(process.thread_group()).unwrap().as_ptr(), |
| Arc::as_ptr(&cgroup) |
| ); |
| |
| let thread = process.clone_task_for_test( |
| locked, |
| (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM) as u64, |
| Some(SIGCHLD), |
| ); |
| |
| let thread_state = thread.read(); |
| let kernel_signals = thread_state.kernel_signals_for_test(); |
| assert_matches!(kernel_signals.front(), Some(KernelSignal::Freeze(_))); |
| }) |
| .await; |
| } |
| |
| #[::fuchsia::test] |
| async fn cgroup_tg_release_removes_pid() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let kernel = current_task.kernel(); |
| let root = &kernel.cgroups.cgroup2; |
| let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds"); |
| |
| let process = current_task.clone_task_for_test(locked, 0, Some(SIGCHLD)); |
| cgroup |
| .add_process(locked.cast_locked(), process.thread_group()) |
| .expect("add process to cgroup"); |
| |
| assert_eq!( |
| root.get_cgroup(process.temp_task().thread_group()).unwrap().as_ptr(), |
| Arc::as_ptr(&cgroup) |
| ); |
| |
| // Drop the process to release it. |
| drop(process); |
| |
| // Verify that the process is removed from the cgroup pid table. |
| assert!(root.pid_table.lock().is_empty()); |
| }) |
| .await; |
| } |
| } |