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