Skip to main content

starnix_core/task/
cgroup.rs

1// Copyright 2022 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
5//! This file implements control group hierarchy.
6//!
7//! There is no support for actual resource constraints, or any operations outside of adding tasks
8//! to a control group (for the duration of their lifetime).
9
10use crate::signals::{SignalInfo, send_freeze_signal};
11use crate::task::waiter::WaiterOptions;
12use crate::task::{Kernel, ThreadGroup, ThreadGroupKey, WaitQueue, Waiter};
13use crate::vfs::{FsStr, FsString, PathBuilder};
14use starnix_logging::{CATEGORY_STARNIX, log_warn, track_stub};
15use starnix_sync::{
16    CgroupChildrenLock, CgroupPidTableLock, CgroupStateLock, CgroupV1Level, FileOpsCore,
17    LockBefore, LockDepGuard, LockDepMutex, Locked, ThreadGroupLimits, allow_subclass,
18};
19use starnix_uapi::errors::Errno;
20use starnix_uapi::signals::SIGKILL;
21use starnix_uapi::{errno, error, pid_t};
22use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map, hash_map};
23use std::ops::{Deref, DerefMut};
24use std::sync::atomic::{AtomicU64, Ordering};
25use std::sync::{Arc, Weak};
26
27use crate::signals::KernelSignal;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub enum ControllerType {
31    Cpu,
32    Cpuacct,
33    Cpuset,
34    Memory,
35    Freezer,
36    Blkio,
37}
38
39impl ControllerType {
40    pub const ALL: [Self; 6] =
41        [Self::Cpu, Self::Cpuacct, Self::Cpuset, Self::Memory, Self::Freezer, Self::Blkio];
42
43    pub fn as_str(&self) -> &'static str {
44        match self {
45            Self::Cpu => "cpu",
46            Self::Cpuacct => "cpuacct",
47            Self::Cpuset => "cpuset",
48            Self::Memory => "memory",
49            Self::Freezer => "freezer",
50            Self::Blkio => "blkio",
51        }
52    }
53}
54
55impl std::str::FromStr for ControllerType {
56    type Err = ();
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        match s {
59            "cpu" => Ok(Self::Cpu),
60            "cpuacct" => Ok(Self::Cpuacct),
61            "cpuset" => Ok(Self::Cpuset),
62            "memory" => Ok(Self::Memory),
63            "freezer" => Ok(Self::Freezer),
64            "blkio" => Ok(Self::Blkio),
65            _ => Err(()),
66        }
67    }
68}
69
70impl std::fmt::Display for ControllerType {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}", self.as_str())
73    }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct CgroupV1Key {
78    pub controllers: BTreeSet<ControllerType>,
79    pub name: Option<String>,
80}
81
82#[derive(Default, Debug)]
83pub struct CgroupV1State {
84    // TODO(https://fxbug.dev/401298305): Support removing cgroup hierarchies when they are
85    // unmounted and no longer have any tasks or child cgroups. Currently, they are kept alive
86    // indefinitely by these Arc references.
87    /// Maps controller to its active hierarchy.
88    pub controllers: HashMap<ControllerType, Arc<CgroupRoot>>,
89    /// Maps named hierarchies.
90    pub named: HashMap<String, Arc<CgroupRoot>>,
91    /// List of all unique hierarchies, naturally sorted.
92    pub hierarchies: BTreeMap<CgroupV1Key, Arc<CgroupRoot>>,
93    /// The next ID to assign to a cgroup v1 hierarchy.
94    next_hierarchy_id: u32,
95}
96
97/// All cgroups of the kernel. There is a single cgroup v2 hierarchy, and one-or-more cgroup v1
98/// hierarchies.
99#[derive(Debug)]
100pub struct KernelCgroups {
101    /// The single cgroup v2 hierarchy.
102    pub cgroup2: Arc<CgroupRoot>,
103    /// The cgroup v1 hierarchies state, protected by a lockdep mutex.
104    pub cgroup1: LockDepMutex<CgroupV1State, CgroupV1Level>,
105}
106
107impl KernelCgroups {
108    /// Returns a locked `CgroupPidTable`, which guarantees that processes would not move in this
109    /// cgroup hierarchy until the lock is freed.
110    ///
111    /// Note: Mutex dependency graph:
112    ///
113    /// `KernelPidTable` -> `CgroupRootPidTable` -> `CgroupState` -> `ThreadGroupState`
114    pub fn lock_cgroup2_pid_table(&self) -> LockDepGuard<'_, CgroupPidTable> {
115        self.cgroup2.pid_table.lock()
116    }
117
118    pub fn get_or_create_cgroup1(
119        &self,
120        controllers: &BTreeSet<ControllerType>,
121        name: Option<&str>,
122    ) -> Result<Arc<CgroupRoot>, Errno> {
123        let mut cgroup1 = self.cgroup1.lock();
124
125        let key = CgroupV1Key { controllers: controllers.clone(), name: name.map(String::from) };
126
127        if let Some(root) = cgroup1.hierarchies.get(&key) {
128            return Ok(root.clone());
129        }
130
131        for c in controllers {
132            if cgroup1.controllers.contains_key(c) {
133                return error!(EBUSY);
134            }
135        }
136
137        if let Some(n) = name {
138            if cgroup1.named.contains_key(n) {
139                return error!(EBUSY);
140            }
141        }
142
143        cgroup1.next_hierarchy_id += 1;
144        let hierarchy_id = cgroup1.next_hierarchy_id;
145        let root = CgroupRoot::new(hierarchy_id);
146        cgroup1.hierarchies.insert(key, root.clone());
147        for c in controllers {
148            cgroup1.controllers.insert(*c, root.clone());
149        }
150        if let Some(n) = name {
151            cgroup1.named.insert(n.to_string(), root.clone());
152        }
153
154        Ok(root)
155    }
156
157    pub fn get_cgroup1<TG: Copy + Into<ThreadGroupKey>>(
158        &self,
159        controller: ControllerType,
160        tg: TG,
161    ) -> Option<Weak<Cgroup>> {
162        let cgroup1 = self.cgroup1.lock();
163        let root = cgroup1.controllers.get(&controller)?;
164        root.get_cgroup(tg)
165    }
166}
167
168impl Default for KernelCgroups {
169    fn default() -> Self {
170        Self { cgroup2: CgroupRoot::new(0), cgroup1: Default::default() }
171    }
172}
173
174#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
175pub enum FreezerState {
176    Thawed,
177    Frozen,
178}
179
180impl Default for FreezerState {
181    fn default() -> Self {
182        FreezerState::Thawed
183    }
184}
185
186impl std::fmt::Display for FreezerState {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            FreezerState::Frozen => write!(f, "1"),
190            FreezerState::Thawed => write!(f, "0"),
191        }
192    }
193}
194
195#[derive(Default)]
196pub struct CgroupFreezerState {
197    /// Cgroups's own freezer state as set by the `cgroup.freeze` file.
198    pub self_freezer_state: FreezerState,
199    /// Considers both the cgroup's self freezer state as set by the `cgroup.freeze` file and
200    /// the freezer state of its ancestors. A cgroup is considered frozen if either itself or any
201    /// of its ancestors is frozen.
202    pub effective_freezer_state: FreezerState,
203}
204
205/// Common operations of all cgroups.
206pub trait CgroupOps: Send + Sync + 'static {
207    /// Returns the unique ID of the cgroup. ID of root cgroup is 0.
208    fn id(&self) -> u64;
209
210    /// Add a process to a cgroup. Errors if the cgroup has been deleted.
211    fn add_process(
212        &self,
213        locked: &mut Locked<FileOpsCore>,
214        thread_group: &ThreadGroup,
215    ) -> Result<(), Errno>;
216
217    /// Create a new sub-cgroup as a child of this cgroup. Errors if the cgroup is deleted, or a
218    /// child with `name` already exists.
219    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
220
221    /// Gets all children of this cgroup.
222    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno>;
223
224    /// Gets the child with `name`, errors if not found.
225    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
226
227    /// Remove a child from this cgroup and return it, if found. Errors if cgroup is deleted, or a
228    /// child with `name` is not found.
229    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
230
231    /// Return all pids that belong to this cgroup.
232    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t>;
233
234    /// Kills all processes in the cgroup and its descendants.
235    fn kill(&self);
236
237    /// Whether the cgroup or any of its descendants have any processes.
238    fn is_populated(&self) -> bool;
239
240    /// Get the freezer `self state` and `effective state`.
241    fn get_freezer_state(&self) -> CgroupFreezerState;
242
243    /// Freeze all tasks in the cgroup.
244    fn freeze(&self, locked: &mut Locked<FileOpsCore>);
245
246    /// Thaw all tasks in the cgroup.
247    fn thaw(&self);
248}
249
250/// `CgroupPidTable` contains the mapping of `ThreadGroup` (by pid) to non-root cgroup.
251/// If `pid` is valid but does not exist in the mapping, then it is assumed to be in the root cgroup.
252#[derive(Debug, Default)]
253pub struct CgroupPidTable(HashMap<ThreadGroupKey, Weak<Cgroup>>);
254impl Deref for CgroupPidTable {
255    type Target = HashMap<ThreadGroupKey, Weak<Cgroup>>;
256
257    fn deref(&self) -> &Self::Target {
258        &self.0
259    }
260}
261impl DerefMut for CgroupPidTable {
262    fn deref_mut(&mut self) -> &mut Self::Target {
263        &mut self.0
264    }
265}
266
267impl CgroupPidTable {
268    /// Add a newly created `ThreadGroup` to the same cgroup as its parent. Assumes that
269    /// `ThreadGroup` does not have any `Task` associated with it.
270    pub fn inherit_cgroup(&mut self, parent: &ThreadGroup, child: &ThreadGroup) {
271        assert!(child.read().tasks_count() == 0, "threadgroup must be newly created");
272        if let Some(weak_cgroup) = self.0.get(&parent.into()).cloned() {
273            let Some(cgroup) = weak_cgroup.upgrade() else {
274                log_warn!("ignored attempt to inherit a non-existant cgroup");
275                return;
276            };
277            assert!(
278                self.0.insert(child.into(), weak_cgroup).map(|c| c.strong_count() == 0).is_none(),
279                "child pid should not exist when inheriting"
280            );
281            // Skip freezer propagation because the `ThreadGroup` is newly created and has no tasks.
282            cgroup.state.lock().processes.insert(child.into());
283        }
284    }
285
286    /// Creates a new `KernelSignal` for a new `Task`, if that `Task` is added to a frozen cgroup.
287    pub fn maybe_create_freeze_signal<TG: Copy + Into<ThreadGroupKey>>(
288        &self,
289        tg: TG,
290    ) -> Option<KernelSignal> {
291        let Some(weak_cgroup) = self.0.get(&tg.into()) else {
292            return None;
293        };
294        let Some(cgroup) = weak_cgroup.upgrade() else {
295            return None;
296        };
297        let state = cgroup.state.lock();
298        if state.get_effective_freezer_state() != FreezerState::Frozen {
299            return None;
300        }
301        Some(KernelSignal::Freeze(state.create_freeze_waiter()))
302    }
303
304    /// Remove a `ThreadGroup` from the root cgroup pid table and from the cgroup it is in.
305    pub fn remove_process(&mut self, thread_group_key: ThreadGroupKey) {
306        if let Some(entry) = self.remove(&thread_group_key) {
307            if let Some(cgroup) = entry.upgrade() {
308                cgroup.state.lock().processes.remove(&thread_group_key);
309            }
310        }
311    }
312}
313
314/// `CgroupRoot` is the root of the cgroup hierarchy. The root cgroup is different from the rest of
315/// the cgroups in a cgroup hierarchy (sub-cgroups of the root) in a few ways:
316///
317/// - The root contains all known processes on cgroup creation, and all new processes as they are
318/// spawned. As such, the root cgroup reports processes belonging to it differently than its
319/// sub-cgroups.
320///
321/// - The root does not contain resource controller interface files, as otherwise they would apply
322/// to the whole system.
323///
324/// - The root does not own a `FsNode` as it is created and owned by the `FileSystem` instead.
325#[derive(Debug)]
326pub struct CgroupRoot {
327    /// The ID of this hierarchy. 0 is reserved for cgroup v2.
328    pub hierarchy_id: u32,
329
330    /// Look up cgroup by pid. Must be locked before child states.
331    pid_table: LockDepMutex<CgroupPidTable, CgroupPidTableLock>,
332
333    /// Sub-cgroups of this cgroup.
334    children: LockDepMutex<CgroupChildren, CgroupChildrenLock>,
335
336    /// Weak reference to self, used when creating child cgroups.
337    weak_self: Weak<CgroupRoot>,
338
339    /// Used to generate IDs for descendent Cgroups.
340    next_id: AtomicU64,
341}
342
343impl CgroupRoot {
344    pub fn new(hierarchy_id: u32) -> Arc<CgroupRoot> {
345        Arc::new_cyclic(|weak_self| Self {
346            hierarchy_id,
347            pid_table: Default::default(),
348            children: Default::default(),
349            weak_self: weak_self.clone(),
350            next_id: AtomicU64::new(1),
351        })
352    }
353
354    fn get_next_id(&self) -> u64 {
355        self.next_id.fetch_add(1, Ordering::Relaxed)
356    }
357
358    pub fn get_cgroup<TG: Copy + Into<ThreadGroupKey>>(&self, tg: TG) -> Option<Weak<Cgroup>> {
359        self.pid_table.lock().get(&tg.into()).cloned()
360    }
361
362    pub fn get_cgroup_inspect(&self) -> fuchsia_inspect::Inspector {
363        let inspector = fuchsia_inspect::Inspector::default();
364        let cgroups = inspector.root();
365        cgroups.record_uint("pids", self.pid_table.lock().len() as u64);
366        cgroups.record_uint("count", self.children.lock().count_descendants());
367        inspector
368    }
369}
370
371impl CgroupOps for CgroupRoot {
372    fn id(&self) -> u64 {
373        0
374    }
375
376    fn add_process(
377        &self,
378        locked: &mut Locked<FileOpsCore>,
379        thread_group: &ThreadGroup,
380    ) -> Result<(), Errno> {
381        let mut pid_table = self.pid_table.lock();
382        // If the process is currently in a child cgroup, we must remove it from that cgroup's
383        // tracking. If it's not in the pid table, it is already implicitly in the root cgroup,
384        // so adding it to root is a no-op.
385        if let Some(entry) = pid_table.remove(&thread_group.into()) {
386            if let Some(cgroup) = entry.upgrade() {
387                cgroup.state.lock().remove_process(locked, thread_group)?;
388            }
389        }
390
391        Ok(())
392    }
393
394    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
395        let id = self.get_next_id();
396        let new_child = Cgroup::new(id, name, &self.weak_self, None);
397        let mut children = self.children.lock();
398        children.insert_child(name.into(), new_child)
399    }
400
401    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
402        let children = self.children.lock();
403        children.get_child(name).ok_or_else(|| errno!(ENOENT))
404    }
405
406    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
407        let mut children = self.children.lock();
408        children.remove_child(name)
409    }
410
411    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
412        let children = self.children.lock();
413        Ok(children.get_children())
414    }
415
416    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t> {
417        let controlled_pids: HashSet<pid_t> =
418            self.pid_table.lock().keys().filter_map(|v| v.upgrade().map(|tg| tg.leader)).collect();
419        let kernel_pids = kernel.pids.read().process_ids();
420        kernel_pids.into_iter().filter(|pid| !controlled_pids.contains(pid)).collect()
421    }
422
423    fn kill(&self) {
424        unreachable!("Root cgroup cannot kill its processes.");
425    }
426
427    fn is_populated(&self) -> bool {
428        false
429    }
430
431    fn get_freezer_state(&self) -> CgroupFreezerState {
432        Default::default()
433    }
434
435    fn freeze(&self, _locked: &mut Locked<FileOpsCore>) {
436        unreachable!("Root cgroup cannot freeze any processes.");
437    }
438
439    fn thaw(&self) {
440        unreachable!("Root cgroup cannot thaw any processes.");
441    }
442}
443
444#[derive(Debug, Default)]
445struct CgroupChildren(BTreeMap<FsString, CgroupHandle>);
446impl CgroupChildren {
447    fn insert_child(&mut self, name: FsString, child: CgroupHandle) -> Result<CgroupHandle, Errno> {
448        let btree_map::Entry::Vacant(child_entry) = self.0.entry(name) else {
449            return error!(EEXIST);
450        };
451        Ok(child_entry.insert(child).clone())
452    }
453
454    fn remove_child(&mut self, name: &FsStr) -> Result<CgroupHandle, Errno> {
455        let btree_map::Entry::Occupied(child_entry) = self.0.entry(name.into()) else {
456            return error!(ENOENT);
457        };
458        let child = child_entry.get();
459
460        // This allow_subclass is safe because the lock is being acquired
461        // in a strictly top-down traversal of the Cgroup tree (from parent
462        // to child), so no lock ordering cycles can be formed.
463        let _token = allow_subclass();
464        let mut child_state = child.state.lock();
465        assert!(!child_state.deleted, "child cannot be deleted");
466
467        child_state.update_processes();
468        if !child_state.processes.is_empty() {
469            return error!(EBUSY);
470        }
471        if !child_state.children.is_empty() {
472            return error!(EBUSY);
473        }
474
475        child_state.deleted = true;
476        drop(child_state);
477
478        Ok(child_entry.remove())
479    }
480
481    fn get_child(&self, name: &FsStr) -> Option<CgroupHandle> {
482        self.0.get(name).cloned()
483    }
484
485    fn get_children(&self) -> Vec<CgroupHandle> {
486        self.0.values().cloned().collect()
487    }
488
489    fn count_descendants(&self) -> u64 {
490        self.0
491            .values()
492            .map(|child| {
493                1 + {
494                    // This allow_subclass is safe because the lock is being acquired
495                    // in a strictly top-down traversal of the Cgroup tree (from parent
496                    // to child), so no lock ordering cycles can be formed.
497                    let _token = allow_subclass();
498                    child.count_descendants()
499                }
500            })
501            .sum()
502    }
503}
504
505impl Deref for CgroupChildren {
506    type Target = BTreeMap<FsString, CgroupHandle>;
507
508    fn deref(&self) -> &Self::Target {
509        &self.0
510    }
511}
512
513#[derive(Debug, Default)]
514struct CgroupState {
515    /// Subgroups of this control group.
516    children: CgroupChildren,
517
518    /// The tasks that are part of this control group.
519    processes: HashSet<ThreadGroupKey>,
520
521    /// If true, can no longer add children or tasks.
522    deleted: bool,
523
524    /// Wait queue to thaw all blocked tasks in this cgroup.
525    wait_queue: WaitQueue,
526
527    /// The cgroup's own freezer state.
528    self_freezer_state: FreezerState,
529
530    /// Effective freezer state inherited from the parent cgroup.
531    inherited_freezer_state: FreezerState,
532}
533
534impl CgroupState {
535    /// Creates a new Waiter that subscribes to the Cgroup's freezer WaitQueue. This `Waiter` can be
536    /// sent as a part of a `KernelSignal::Freeze` to freeze a `Task`.
537    fn create_freeze_waiter(&self) -> Waiter {
538        let waiter = Waiter::with_options(WaiterOptions::IGNORE_SIGNALS);
539        self.wait_queue.wait_async(&waiter);
540        waiter
541    }
542
543    // Goes through `processes` and remove processes that are no longer alive.
544    fn update_processes(&mut self) {
545        self.processes.retain(|thread_group| {
546            let Some(thread_group) = thread_group.upgrade() else {
547                return false;
548            };
549            let running = thread_group.read().is_running();
550            running
551        });
552    }
553
554    fn freeze_thread_group<L>(&self, locked: &mut Locked<L>, thread_group: &ThreadGroup)
555    where
556        L: LockBefore<ThreadGroupLimits>,
557    {
558        let tasks = thread_group.read().tasks();
559        for task in tasks {
560            send_freeze_signal(locked, &task, self.create_freeze_waiter())
561                .expect("sending freeze signal should not fail");
562        }
563    }
564
565    fn thaw_thread_group<L>(&self, _locked: &mut Locked<L>, thread_group: &ThreadGroup)
566    where
567        L: LockBefore<ThreadGroupLimits>,
568    {
569        let tasks = thread_group.read().tasks();
570        for task in tasks {
571            task.write().thaw();
572            task.interrupt();
573        }
574    }
575
576    fn get_effective_freezer_state(&self) -> FreezerState {
577        std::cmp::max(self.self_freezer_state, self.inherited_freezer_state)
578    }
579
580    fn add_process<L>(
581        &mut self,
582        locked: &mut Locked<L>,
583        thread_group: &ThreadGroup,
584    ) -> Result<(), Errno>
585    where
586        L: LockBefore<ThreadGroupLimits>,
587    {
588        if self.deleted {
589            return error!(ENOENT);
590        }
591        self.processes.insert(thread_group.into());
592
593        if self.get_effective_freezer_state() == FreezerState::Frozen {
594            self.freeze_thread_group(locked, &thread_group);
595        }
596        Ok(())
597    }
598
599    fn remove_process<L>(
600        &mut self,
601        locked: &mut Locked<L>,
602        thread_group: &ThreadGroup,
603    ) -> Result<(), Errno>
604    where
605        L: LockBefore<ThreadGroupLimits>,
606    {
607        if self.deleted {
608            return error!(ENOENT);
609        }
610        self.processes.remove(&thread_group.into());
611
612        if self.get_effective_freezer_state() == FreezerState::Frozen {
613            self.thaw_thread_group(locked, thread_group);
614        }
615        Ok(())
616    }
617
618    fn propagate_freeze<L>(&mut self, locked: &mut Locked<L>, inherited_freezer_state: FreezerState)
619    where
620        L: LockBefore<ThreadGroupLimits>,
621    {
622        let prev_effective_freezer_state = self.get_effective_freezer_state();
623        self.inherited_freezer_state = inherited_freezer_state;
624        if prev_effective_freezer_state == FreezerState::Frozen {
625            return;
626        }
627
628        for thread_group in self.processes.iter() {
629            let Some(thread_group) = thread_group.upgrade() else {
630                continue;
631            };
632            self.freeze_thread_group(locked, &thread_group);
633        }
634
635        // Freeze all children cgroups while holding self state lock
636        for child in self.children.get_children() {
637            // This allow_subclass is safe because the lock is being acquired
638            // in a strictly top-down traversal of the Cgroup tree (from parent
639            // to child), so no lock ordering cycles can be formed.
640            let _token = allow_subclass();
641            child.state.lock().propagate_freeze(locked, FreezerState::Frozen);
642        }
643    }
644
645    fn propagate_thaw(&mut self, inherited_freezer_state: FreezerState) {
646        self.inherited_freezer_state = inherited_freezer_state;
647        if self.get_effective_freezer_state() == FreezerState::Thawed {
648            self.wait_queue.notify_all();
649            for child in self.children.get_children() {
650                // This allow_subclass is safe because the lock is being acquired
651                // in a strictly top-down traversal of the Cgroup tree (from parent
652                // to child), so no lock ordering cycles can be formed.
653                let _token = allow_subclass();
654                child.state.lock().propagate_thaw(FreezerState::Thawed);
655            }
656        }
657    }
658
659    fn propagate_kill(&self) {
660        for thread_group in self.processes.iter() {
661            let Some(thread_group) = thread_group.upgrade() else {
662                continue;
663            };
664            thread_group.write().send_signal(SignalInfo::kernel(SIGKILL));
665        }
666
667        // Recursively lock and kill children cgroups' processes.
668        for child in self.children.get_children() {
669            // This allow_subclass is safe because the lock is being acquired
670            // in a strictly top-down traversal of the Cgroup tree (from parent
671            // to child), so no lock ordering cycles can be formed.
672            let _token = allow_subclass();
673            child.state.lock().propagate_kill();
674        }
675    }
676}
677
678/// `Cgroup` is a non-root cgroup in a cgroup hierarchy, and can have other `Cgroup`s as children.
679#[derive(Debug)]
680pub struct Cgroup {
681    root: Weak<CgroupRoot>,
682
683    /// ID of the cgroup.
684    id: u64,
685
686    /// Name of the cgroup.
687    name: FsString,
688
689    /// Weak reference to its parent cgroup, `None` if direct descendent of the root cgroup.
690    /// This field is useful in implementing features that only apply to non-root cgroups.
691    parent: Option<Weak<Cgroup>>,
692
693    /// Internal state of the Cgroup.
694    state: LockDepMutex<CgroupState, CgroupStateLock>,
695
696    weak_self: Weak<Cgroup>,
697}
698pub type CgroupHandle = Arc<Cgroup>;
699
700/// Returns the path from the root to this `cgroup`.
701pub fn path_from_root(weak_cgroup: Option<Weak<Cgroup>>) -> Result<FsString, Errno> {
702    let cgroup = match weak_cgroup {
703        Some(weak_cgroup) => Weak::upgrade(&weak_cgroup).ok_or_else(|| errno!(ENODEV))?,
704        None => return Ok("/".into()),
705    };
706    let mut path = PathBuilder::new();
707    let mut current = Some(cgroup);
708    while let Some(cgroup) = current {
709        path.prepend_element(cgroup.name());
710        current = cgroup.parent()?;
711    }
712    Ok(path.build_absolute())
713}
714
715impl Cgroup {
716    pub fn new(
717        id: u64,
718        name: &FsStr,
719        root: &Weak<CgroupRoot>,
720        parent: Option<Weak<Cgroup>>,
721    ) -> CgroupHandle {
722        Arc::new_cyclic(|weak| Self {
723            id,
724            root: root.clone(),
725            name: name.to_owned(),
726            parent,
727            state: Default::default(),
728            weak_self: weak.clone(),
729        })
730    }
731
732    pub fn name(&self) -> &FsStr {
733        self.name.as_ref()
734    }
735
736    fn root(&self) -> Result<Arc<CgroupRoot>, Errno> {
737        self.root.upgrade().ok_or_else(|| errno!(ENODEV))
738    }
739
740    /// Returns the upgraded parent cgroup, or `Ok(None)` if cgroup is a direct desendent of root.
741    /// Errors if parent node is no longer around.
742    fn parent(&self) -> Result<Option<CgroupHandle>, Errno> {
743        self.parent.as_ref().map(|weak| weak.upgrade().ok_or_else(|| errno!(ENODEV))).transpose()
744    }
745
746    fn count_descendants(&self) -> u64 {
747        self.state.lock().children.count_descendants()
748    }
749}
750
751impl CgroupOps for Cgroup {
752    fn id(&self) -> u64 {
753        self.id
754    }
755
756    fn add_process(
757        &self,
758        locked: &mut Locked<FileOpsCore>,
759        thread_group: &ThreadGroup,
760    ) -> Result<(), Errno> {
761        let root = self.root()?;
762        let mut pid_table = root.pid_table.lock();
763        match pid_table.entry(thread_group.into()) {
764            hash_map::Entry::Occupied(mut entry) => {
765                // Check if thread_group is already in the current cgroup. Linux does not return an error if
766                // it already exists.
767                if std::ptr::eq(self, entry.get().as_ptr()) {
768                    return Ok(());
769                }
770
771                // If thread_group is in another cgroup, we need to remove it first.
772                track_stub!(TODO("https://fxbug.dev/383374687"), "check permissions");
773                if let Some(other_cgroup) = entry.get().upgrade() {
774                    other_cgroup.state.lock().remove_process(locked, thread_group)?;
775                }
776
777                self.state.lock().add_process(locked, thread_group)?;
778                entry.insert(self.weak_self.clone());
779            }
780            hash_map::Entry::Vacant(entry) => {
781                self.state.lock().add_process(locked, thread_group)?;
782                entry.insert(self.weak_self.clone());
783            }
784        }
785
786        Ok(())
787    }
788
789    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
790        let id = self.root()?.get_next_id();
791        let new_child = Cgroup::new(id, name, &self.root, Some(self.weak_self.clone()));
792        let mut state = self.state.lock();
793        if state.deleted {
794            return error!(ENOENT);
795        }
796        // New child should inherit the effective freezer state of the current cgroup.
797        // This allow_subclass is safe because the lock is being acquired
798        // in a strictly top-down traversal of the Cgroup tree (from parent
799        // to child), so no lock ordering cycles can be formed.
800        let _token = allow_subclass();
801        new_child.state.lock().inherited_freezer_state = state.get_effective_freezer_state();
802        state.children.insert_child(name.into(), new_child)
803    }
804
805    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
806        let state = self.state.lock();
807        state.children.get_child(name).ok_or_else(|| errno!(ENOENT))
808    }
809
810    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
811        let mut state = self.state.lock();
812        if state.deleted {
813            return error!(ENOENT);
814        }
815        state.children.remove_child(name)
816    }
817
818    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
819        let state = self.state.lock();
820        if state.deleted {
821            return error!(ENOENT);
822        }
823        Ok(state.children.get_children())
824    }
825
826    fn get_pids(&self, _kernel: &Kernel) -> Vec<pid_t> {
827        let mut state = self.state.lock();
828        state.update_processes();
829        state.processes.iter().filter_map(|v| v.upgrade().map(|tg| tg.leader)).collect()
830    }
831
832    fn kill(&self) {
833        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupKill");
834        let state = self.state.lock();
835        state.propagate_kill();
836    }
837
838    fn is_populated(&self) -> bool {
839        let mut state = self.state.lock();
840        if state.deleted {
841            return false;
842        }
843        state.update_processes();
844        if !state.processes.is_empty() {
845            return true;
846        }
847
848        state.children.get_children().into_iter().any(|child| {
849            // This allow_subclass is safe because the lock is being acquired
850            // in a strictly top-down traversal of the Cgroup tree (from parent
851            // to child), so no lock ordering cycles can be formed.
852            let _token = allow_subclass();
853            child.is_populated()
854        })
855    }
856
857    fn get_freezer_state(&self) -> CgroupFreezerState {
858        let state = self.state.lock();
859        CgroupFreezerState {
860            self_freezer_state: state.self_freezer_state,
861            effective_freezer_state: state.get_effective_freezer_state(),
862        }
863    }
864
865    fn freeze(&self, locked: &mut Locked<FileOpsCore>) {
866        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupFreeze");
867        let mut state = self.state.lock();
868        let inherited_freezer_state = state.inherited_freezer_state;
869        state.propagate_freeze(locked, inherited_freezer_state);
870        state.self_freezer_state = FreezerState::Frozen;
871    }
872
873    fn thaw(&self) {
874        fuchsia_trace::duration!(CATEGORY_STARNIX, "CgroupThaw");
875        let mut state = self.state.lock();
876        state.self_freezer_state = FreezerState::Thawed;
877        let inherited_freezer_state = state.inherited_freezer_state;
878        state.propagate_thaw(inherited_freezer_state);
879    }
880}
881
882#[cfg(test)]
883mod test {
884    use super::*;
885    use crate::testing::spawn_kernel_and_run;
886    use assert_matches::assert_matches;
887    use starnix_uapi::signals::SIGCHLD;
888    use starnix_uapi::{CLONE_SIGHAND, CLONE_THREAD, CLONE_VM};
889
890    #[::fuchsia::test]
891    async fn cgroup_path_from_root() {
892        spawn_kernel_and_run(async |_, _| {
893            let root = CgroupRoot::new(0);
894
895            let test_cgroup =
896                root.new_child("test".into()).expect("new_child on root cgroup succeeds");
897            let child_cgroup = test_cgroup
898                .new_child("child".into())
899                .expect("new_child on non-root cgroup succeeds");
900
901            assert_eq!(path_from_root(Some(Arc::downgrade(&test_cgroup))), Ok("/test".into()));
902            assert_eq!(
903                path_from_root(Some(Arc::downgrade(&child_cgroup))),
904                Ok("/test/child".into())
905            );
906        })
907        .await;
908    }
909
910    #[::fuchsia::test]
911    async fn cgroup_clone_task_in_frozen_cgroup() {
912        spawn_kernel_and_run(async |locked, current_task| {
913            let kernel = current_task.kernel();
914            let root = &kernel.cgroups.cgroup2;
915            let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds");
916
917            let process = current_task.clone_task_for_test(locked, 0, Some(SIGCHLD));
918            cgroup
919                .add_process(locked.cast_locked(), process.thread_group())
920                .expect("add process to cgroup");
921            cgroup.freeze(locked.cast_locked());
922            assert_eq!(cgroup.get_pids(&kernel).first(), Some(process.get_pid()).as_ref());
923            assert_eq!(
924                root.get_cgroup(process.thread_group()).unwrap().as_ptr(),
925                Arc::as_ptr(&cgroup)
926            );
927
928            let thread = process.clone_task_for_test(
929                locked,
930                (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM) as u64,
931                Some(SIGCHLD),
932            );
933
934            let thread_state = thread.read();
935            let kernel_signals = thread_state.kernel_signals_for_test();
936            assert_matches!(kernel_signals.front(), Some(KernelSignal::Freeze(_)));
937        })
938        .await;
939    }
940
941    #[::fuchsia::test]
942    async fn cgroup_tg_release_removes_pid() {
943        spawn_kernel_and_run(async |locked, current_task| {
944            let kernel = current_task.kernel();
945            let root = &kernel.cgroups.cgroup2;
946            let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds");
947
948            let process = current_task.clone_task_for_test(locked, 0, Some(SIGCHLD));
949            cgroup
950                .add_process(locked.cast_locked(), process.thread_group())
951                .expect("add process to cgroup");
952
953            assert_eq!(
954                root.get_cgroup(process.thread_group()).unwrap().as_ptr(),
955                Arc::as_ptr(&cgroup)
956            );
957
958            // Drop the process to release it.
959            drop(process);
960
961            // Verify that the process is removed from the cgroup pid table.
962            assert!(root.pid_table.lock().is_empty());
963        })
964        .await;
965    }
966}