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