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::{Kernel, Pid, ThreadGroup, WaitQueue, Waiter};
12use crate::vfs::{FsStr, FsString, PathBuilder};
13use starnix_logging::{CATEGORY_STARNIX, log_warn, track_stub};
14use starnix_sync::{
15    CgroupChildrenLock, CgroupPidTableLock, CgroupStateLock, CgroupV1Level, LockDepGuard,
16    LockDepMutex, allow_subclass,
17};
18use starnix_uapi::errors::Errno;
19use starnix_uapi::signals::SIGKILL;
20use starnix_uapi::{errno, error, pid_t};
21use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map, hash_map};
22use std::ops::{Deref, DerefMut};
23use std::sync::atomic::{AtomicU64, Ordering};
24use std::sync::{Arc, Weak};
25
26use crate::signals::KernelSignal;
27use zx;
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    /// `PidTableLock` -> `CgroupPidTableLock` -> `CgroupStateLock` -> `ThreadGroupMutableStateLock`
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, controllers.clone());
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(&self, controller: ControllerType, pid: &Pid) -> Option<Weak<Cgroup>> {
158        let cgroup1 = self.cgroup1.lock();
159        let root = cgroup1.controllers.get(&controller)?;
160        root.get_cgroup(pid)
161    }
162}
163
164impl Default for KernelCgroups {
165    fn default() -> Self {
166        Self {
167            cgroup2: CgroupRoot::new(0, ControllerType::ALL.iter().copied().collect()),
168            cgroup1: Default::default(),
169        }
170    }
171}
172
173#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
174pub enum FreezerState {
175    Thawed,
176    Frozen,
177}
178
179impl Default for FreezerState {
180    fn default() -> Self {
181        FreezerState::Thawed
182    }
183}
184
185impl std::fmt::Display for FreezerState {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        match self {
188            FreezerState::Frozen => write!(f, "1"),
189            FreezerState::Thawed => write!(f, "0"),
190        }
191    }
192}
193
194#[derive(Default)]
195pub struct CgroupFreezerState {
196    /// Cgroups's own freezer state as set by the `cgroup.freeze` file.
197    pub self_freezer_state: FreezerState,
198    /// Considers both the cgroup's self freezer state as set by the `cgroup.freeze` file and
199    /// the freezer state of its ancestors. A cgroup is considered frozen if either itself or any
200    /// of its ancestors is frozen.
201    pub effective_freezer_state: FreezerState,
202}
203
204#[derive(Debug, Default, Clone, PartialEq, Eq)]
205pub struct CpusetControllerState {
206    pub cpus: Option<Vec<u32>>,
207}
208
209#[derive(Debug, Default, Clone, PartialEq, Eq)]
210pub struct FreezerControllerState {
211    pub self_freezer_state: FreezerState,
212    pub inherited_freezer_state: FreezerState,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum ControllerState {
217    Cpuset(CpusetControllerState),
218    Freezer(FreezerControllerState),
219}
220
221pub trait FreezerOps: Send + Sync + 'static {
222    /// Get the freezer `self state` and `effective state`.
223    fn get_freezer_state(&self) -> CgroupFreezerState;
224
225    /// Freeze all tasks in the cgroup.
226    fn freeze(&self);
227
228    /// Thaw all tasks in the cgroup.
229    fn thaw(&self);
230}
231
232pub trait CpusetOps: Send + Sync + 'static {
233    /// Returns the cpuset cpus of the cgroup.
234    fn cpuset_cpus(&self) -> Vec<u32>;
235
236    /// Sets the cpuset cpus of the cgroup.
237    fn set_cpuset_cpus(&self, cpus: Vec<u32>);
238}
239
240/// Common operations of all cgroups.
241pub trait CgroupOps: Send + Sync + 'static {
242    /// Returns the unique ID of the cgroup. ID of root cgroup is 0.
243    fn id(&self) -> u64;
244
245    /// Add a process to a cgroup. Errors if the cgroup has been deleted.
246    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno>;
247
248    /// Create a new sub-cgroup as a child of this cgroup. Errors if the cgroup is deleted, or a
249    /// child with `name` already exists.
250    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
251
252    /// Gets all children of this cgroup.
253    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno>;
254
255    /// Gets the child with `name`, errors if not found.
256    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
257
258    /// Remove a child from this cgroup and return it, if found. Errors if cgroup is deleted, or a
259    /// child with `name` is not found.
260    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno>;
261
262    /// Return all pids that belong to this cgroup.
263    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t>;
264
265    /// Kills all processes in the cgroup and its descendants.
266    fn kill(&self);
267
268    /// Whether the cgroup or any of its descendants have any processes.
269    fn is_populated(&self) -> bool;
270
271    /// Returns freezer ops if freezer controller is supported.
272    fn freezer(&self) -> Option<&dyn FreezerOps>;
273
274    /// Returns cpuset ops if cpuset controller is supported.
275    fn cpuset(&self) -> Option<&dyn CpusetOps>;
276}
277
278/// `CgroupPidTable` contains the mapping of `ThreadGroup` (by pid) to non-root cgroup.
279/// If `pid` is valid but does not exist in the mapping, then it is assumed to be in the root cgroup.
280#[derive(Debug, Default)]
281pub struct CgroupPidTable(HashMap<Pid, Weak<Cgroup>>);
282impl Deref for CgroupPidTable {
283    type Target = HashMap<Pid, Weak<Cgroup>>;
284
285    fn deref(&self) -> &Self::Target {
286        &self.0
287    }
288}
289impl DerefMut for CgroupPidTable {
290    fn deref_mut(&mut self) -> &mut Self::Target {
291        &mut self.0
292    }
293}
294
295impl CgroupPidTable {
296    /// Add a newly created `ThreadGroup` to the same cgroup as its parent. Assumes that
297    /// `ThreadGroup` does not have any `Task` associated with it.
298    pub fn inherit_cgroup(&mut self, parent: &ThreadGroup, child: &ThreadGroup) {
299        assert!(child.read().tasks_count() == 0, "threadgroup must be newly created");
300        if let Some(weak_cgroup) = self.0.get(&parent.leader).cloned() {
301            let Some(cgroup) = weak_cgroup.upgrade() else {
302                log_warn!("ignored attempt to inherit a non-existant cgroup");
303                return;
304            };
305            assert!(
306                self.0.insert(child.leader.clone(), weak_cgroup).is_none(),
307                "child pid should not exist when inheriting"
308            );
309            // Skip freezer propagation because the `ThreadGroup` is newly created and has no tasks.
310            cgroup.state.lock().processes.insert(child.leader.clone());
311        }
312    }
313
314    /// Creates a new `KernelSignal` for a new `Task`, if that `Task` is added to a frozen cgroup.
315    pub fn maybe_create_freeze_signal(&self, pid: &Pid) -> Option<KernelSignal> {
316        let Some(weak_cgroup) = self.0.get(pid) else {
317            return None;
318        };
319        let Some(cgroup) = weak_cgroup.upgrade() else {
320            return None;
321        };
322        let state = cgroup.state.lock();
323        if state.get_effective_freezer_state() != FreezerState::Frozen {
324            return None;
325        }
326        Some(KernelSignal::Freeze(state.create_freeze_waiter()))
327    }
328
329    /// Remove a `ThreadGroup` from the root cgroup pid table and from the cgroup it is in.
330    pub fn remove_process(&mut self, pid: &Pid) {
331        if let Some(entry) = self.remove(pid) {
332            if let Some(cgroup) = entry.upgrade() {
333                cgroup.state.lock().processes.remove(pid);
334            }
335        }
336    }
337}
338
339/// `CgroupRoot` is the root of the cgroup hierarchy. The root cgroup is different from the rest of
340/// the cgroups in a cgroup hierarchy (sub-cgroups of the root) in a few ways:
341///
342/// - The root contains all known processes on cgroup creation, and all new processes as they are
343/// spawned. As such, the root cgroup reports processes belonging to it differently than its
344/// sub-cgroups.
345///
346/// - The root does not contain resource controller interface files, as otherwise they would apply
347/// to the whole system.
348///
349/// - The root does not own a `FsNode` as it is created and owned by the `FileSystem` instead.
350#[derive(Debug)]
351pub struct CgroupRoot {
352    /// The ID of this hierarchy. 0 is reserved for cgroup v2.
353    pub hierarchy_id: u32,
354
355    /// Controllers supported by this hierarchy.
356    pub controllers: BTreeSet<ControllerType>,
357
358    /// Look up cgroup by pid. Must be locked before child states.
359    pid_table: LockDepMutex<CgroupPidTable, CgroupPidTableLock>,
360
361    /// Sub-cgroups of this cgroup.
362    children: LockDepMutex<CgroupChildren, CgroupChildrenLock>,
363
364    /// Weak reference to self, used when creating child cgroups.
365    weak_self: Weak<CgroupRoot>,
366
367    /// Used to generate IDs for descendent Cgroups.
368    next_id: AtomicU64,
369}
370
371impl CgroupRoot {
372    pub fn new(hierarchy_id: u32, controllers: BTreeSet<ControllerType>) -> Arc<CgroupRoot> {
373        Arc::new_cyclic(|weak_self| Self {
374            hierarchy_id,
375            controllers,
376            pid_table: Default::default(),
377            children: Default::default(),
378            weak_self: weak_self.clone(),
379            next_id: AtomicU64::new(1),
380        })
381    }
382
383    pub fn has_controller(&self, controller: ControllerType) -> bool {
384        self.controllers.contains(&controller)
385    }
386
387    fn get_next_id(&self) -> u64 {
388        self.next_id.fetch_add(1, Ordering::Relaxed)
389    }
390
391    pub fn get_cgroup(&self, pid: &Pid) -> Option<Weak<Cgroup>> {
392        self.pid_table.lock().get(pid).cloned()
393    }
394
395    pub fn get_cgroup_inspect(&self) -> fuchsia_inspect::Inspector {
396        let inspector = fuchsia_inspect::Inspector::default();
397        let cgroups = inspector.root();
398        cgroups.record_uint("pids", self.pid_table.lock().len() as u64);
399        cgroups.record_uint("count", self.children.lock().count_descendants());
400        inspector
401    }
402}
403
404impl CgroupOps for CgroupRoot {
405    fn id(&self) -> u64 {
406        0
407    }
408
409    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno> {
410        let mut pid_table = self.pid_table.lock();
411        // If the process is currently in a child cgroup, we must remove it from that cgroup's
412        // tracking. If it's not in the pid table, it is already implicitly in the root cgroup,
413        // so adding it to root is a no-op.
414        if let Some(entry) = pid_table.remove(&thread_group.leader) {
415            if let Some(cgroup) = entry.upgrade() {
416                cgroup.state.lock().remove_process(thread_group)?;
417            }
418        }
419
420        let tasks = thread_group.read().tasks();
421        if self.has_controller(ControllerType::Cpuset) {
422            for task in &tasks {
423                task.write().cpuset_path = "/".to_string();
424            }
425        }
426
427        // Re-evaluate roles for all threads in the thread group.
428        for task in tasks {
429            if let Err(e) = task.sync_scheduler_state_to_role() {
430                log_warn!("Failed to set thread role for task {}: {:?}", task.tid, e);
431            }
432        }
433
434        Ok(())
435    }
436
437    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
438        let id = self.get_next_id();
439        let new_child = Cgroup::new(id, name, &self.weak_self, None);
440        let mut children = self.children.lock();
441        children.insert_child(name.into(), new_child)
442    }
443
444    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
445        let children = self.children.lock();
446        children.get_child(name).ok_or_else(|| errno!(ENOENT))
447    }
448
449    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
450        let mut children = self.children.lock();
451        children.remove_child(name)
452    }
453
454    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
455        let children = self.children.lock();
456        Ok(children.get_children())
457    }
458
459    fn get_pids(&self, kernel: &Kernel) -> Vec<pid_t> {
460        let controlled_pids: HashSet<pid_t> = self
461            .pid_table
462            .lock()
463            .keys()
464            .filter_map(|v| v.get_thread_group().ok().map(|tg| tg.leader.id))
465            .collect();
466        let kernel_pids = kernel.pids.process_ids();
467        kernel_pids.into_iter().filter(|pid| !controlled_pids.contains(pid)).collect()
468    }
469
470    fn kill(&self) {
471        unreachable!("Root cgroup cannot kill its processes.");
472    }
473
474    fn is_populated(&self) -> bool {
475        false
476    }
477
478    fn freezer(&self) -> Option<&dyn FreezerOps> {
479        None
480    }
481
482    fn cpuset(&self) -> Option<&dyn CpusetOps> {
483        if self.controllers.contains(&ControllerType::Cpuset) { Some(self) } else { None }
484    }
485}
486
487impl CpusetOps for CgroupRoot {
488    fn cpuset_cpus(&self) -> Vec<u32> {
489        (0..zx::system_get_num_cpus()).collect()
490    }
491
492    fn set_cpuset_cpus(&self, _cpus: Vec<u32>) {}
493}
494
495#[derive(Debug, Default)]
496struct CgroupChildren(BTreeMap<FsString, CgroupHandle>);
497impl CgroupChildren {
498    fn insert_child(&mut self, name: FsString, child: CgroupHandle) -> Result<CgroupHandle, Errno> {
499        let btree_map::Entry::Vacant(child_entry) = self.0.entry(name) else {
500            return error!(EEXIST);
501        };
502        Ok(child_entry.insert(child).clone())
503    }
504
505    fn remove_child(&mut self, name: &FsStr) -> Result<CgroupHandle, Errno> {
506        let btree_map::Entry::Occupied(child_entry) = self.0.entry(name.into()) else {
507            return error!(ENOENT);
508        };
509        let child = child_entry.get();
510
511        // This allow_subclass is safe because the lock is being acquired
512        // in a strictly top-down traversal of the Cgroup tree (from parent
513        // to child), so no lock ordering cycles can be formed.
514        let _token = allow_subclass();
515        let mut child_state = child.state.lock();
516        assert!(!child_state.deleted, "child cannot be deleted");
517
518        child_state.update_processes();
519        if !child_state.processes.is_empty() {
520            return error!(EBUSY);
521        }
522        if !child_state.children.is_empty() {
523            return error!(EBUSY);
524        }
525
526        child_state.deleted = true;
527        drop(child_state);
528
529        Ok(child_entry.remove())
530    }
531
532    fn get_child(&self, name: &FsStr) -> Option<CgroupHandle> {
533        self.0.get(name).cloned()
534    }
535
536    fn get_children(&self) -> Vec<CgroupHandle> {
537        self.0.values().cloned().collect()
538    }
539
540    fn count_descendants(&self) -> u64 {
541        self.0
542            .values()
543            .map(|child| {
544                1 + {
545                    // This allow_subclass is safe because the lock is being acquired
546                    // in a strictly top-down traversal of the Cgroup tree (from parent
547                    // to child), so no lock ordering cycles can be formed.
548                    let _token = allow_subclass();
549                    child.count_descendants()
550                }
551            })
552            .sum()
553    }
554}
555
556impl Deref for CgroupChildren {
557    type Target = BTreeMap<FsString, CgroupHandle>;
558
559    fn deref(&self) -> &Self::Target {
560        &self.0
561    }
562}
563
564#[derive(Debug, Default)]
565struct CgroupState {
566    /// Subgroups of this control group.
567    children: CgroupChildren,
568
569    /// The tasks that are part of this control group.
570    processes: HashSet<Pid>,
571
572    /// If true, can no longer add children or tasks.
573    deleted: bool,
574
575    /// Wait queue to thaw all blocked tasks in this cgroup.
576    wait_queue: WaitQueue,
577
578    /// Controller-specific state.
579    controllers: HashMap<ControllerType, ControllerState>,
580}
581
582impl CgroupState {
583    fn freezer(&self) -> Option<&FreezerControllerState> {
584        match self.controllers.get(&ControllerType::Freezer) {
585            Some(ControllerState::Freezer(state)) => Some(state),
586            _ => None,
587        }
588    }
589
590    fn freezer_mut(&mut self) -> Option<&mut FreezerControllerState> {
591        match self.controllers.get_mut(&ControllerType::Freezer) {
592            Some(ControllerState::Freezer(state)) => Some(state),
593            _ => None,
594        }
595    }
596
597    fn cpuset(&self) -> Option<&CpusetControllerState> {
598        match self.controllers.get(&ControllerType::Cpuset) {
599            Some(ControllerState::Cpuset(state)) => Some(state),
600            _ => None,
601        }
602    }
603
604    fn cpuset_mut(&mut self) -> Option<&mut CpusetControllerState> {
605        match self.controllers.get_mut(&ControllerType::Cpuset) {
606            Some(ControllerState::Cpuset(state)) => Some(state),
607            _ => None,
608        }
609    }
610
611    /// Creates a new Waiter that subscribes to the Cgroup's freezer WaitQueue. This `Waiter` can be
612    /// sent as a part of a `KernelSignal::Freeze` to freeze a `Task`.
613    fn create_freeze_waiter(&self) -> Waiter {
614        let waiter = Waiter::new();
615        self.wait_queue.wait_async(&waiter);
616        waiter
617    }
618
619    // Goes through `processes` and remove processes that are no longer alive.
620    fn update_processes(&mut self) {
621        self.processes.retain(|thread_group| {
622            let Ok(thread_group) = thread_group.get_thread_group() else {
623                return false;
624            };
625            let running = thread_group.read().is_running();
626            running
627        });
628    }
629
630    fn freeze_thread_group(&self, thread_group: &ThreadGroup) {
631        let tasks = thread_group.read().tasks();
632        for task in tasks {
633            send_freeze_signal(&task, self.create_freeze_waiter())
634                .expect("sending freeze signal should not fail");
635        }
636    }
637
638    fn thaw_thread_group(&self, thread_group: &ThreadGroup) {
639        let tasks = thread_group.read().tasks();
640        for task in tasks {
641            task.write().thaw();
642            task.interrupt();
643        }
644    }
645
646    fn get_effective_freezer_state(&self) -> FreezerState {
647        if let Some(freezer) = self.freezer() {
648            std::cmp::max(freezer.self_freezer_state, freezer.inherited_freezer_state)
649        } else {
650            FreezerState::Thawed
651        }
652    }
653
654    fn add_process(&mut self, thread_group: &ThreadGroup) -> Result<(), Errno> {
655        if self.deleted {
656            return error!(ENOENT);
657        }
658        self.processes.insert(thread_group.leader.clone());
659
660        if self.get_effective_freezer_state() == FreezerState::Frozen {
661            self.freeze_thread_group(&thread_group);
662        }
663        Ok(())
664    }
665
666    fn remove_process(&mut self, thread_group: &ThreadGroup) -> Result<(), Errno> {
667        if self.deleted {
668            return error!(ENOENT);
669        }
670        self.processes.remove(&thread_group.leader);
671
672        if self.get_effective_freezer_state() == FreezerState::Frozen {
673            self.thaw_thread_group(thread_group);
674        }
675        Ok(())
676    }
677
678    fn propagate_freeze(&mut self, inherited_freezer_state: FreezerState) {
679        let prev_effective_freezer_state = self.get_effective_freezer_state();
680        if let Some(freezer) = self.freezer_mut() {
681            freezer.inherited_freezer_state = inherited_freezer_state;
682        }
683        if prev_effective_freezer_state == FreezerState::Frozen {
684            return;
685        }
686
687        for pid in self.processes.iter() {
688            let Ok(thread_group) = pid.get_thread_group() else {
689                continue;
690            };
691            self.freeze_thread_group(&thread_group);
692        }
693
694        // Freeze all children cgroups while holding self state lock
695        for child in self.children.get_children() {
696            // This allow_subclass is safe because the lock is being acquired
697            // in a strictly top-down traversal of the Cgroup tree (from parent
698            // to child), so no lock ordering cycles can be formed.
699            let _token = allow_subclass();
700            child.state.lock().propagate_freeze(FreezerState::Frozen);
701        }
702    }
703
704    fn propagate_thaw(&mut self, inherited_freezer_state: FreezerState) {
705        if let Some(freezer) = self.freezer_mut() {
706            freezer.inherited_freezer_state = inherited_freezer_state;
707        }
708        if self.get_effective_freezer_state() == FreezerState::Thawed {
709            self.wait_queue.notify_all();
710            for child in self.children.get_children() {
711                // This allow_subclass is safe because the lock is being acquired
712                // in a strictly top-down traversal of the Cgroup tree (from parent
713                // to child), so no lock ordering cycles can be formed.
714                let _token = allow_subclass();
715                child.state.lock().propagate_thaw(FreezerState::Thawed);
716            }
717        }
718    }
719
720    fn propagate_kill(&self) {
721        for pid in self.processes.iter() {
722            let Ok(thread_group) = pid.get_thread_group() else {
723                continue;
724            };
725            thread_group.write().send_signal(SignalInfo::kernel(SIGKILL));
726        }
727
728        // Recursively lock and kill children cgroups' processes.
729        for child in self.children.get_children() {
730            // This allow_subclass is safe because the lock is being acquired
731            // in a strictly top-down traversal of the Cgroup tree (from parent
732            // to child), so no lock ordering cycles can be formed.
733            let _token = allow_subclass();
734            child.state.lock().propagate_kill();
735        }
736    }
737}
738
739/// `Cgroup` is a non-root cgroup in a cgroup hierarchy, and can have other `Cgroup`s as children.
740#[derive(Debug)]
741pub struct Cgroup {
742    root: Weak<CgroupRoot>,
743
744    /// ID of the cgroup.
745    id: u64,
746
747    /// Name of the cgroup.
748    name: FsString,
749
750    /// Weak reference to its parent cgroup, `None` if direct descendent of the root cgroup.
751    /// This field is useful in implementing features that only apply to non-root cgroups.
752    parent: Option<Weak<Cgroup>>,
753
754    /// Internal state of the Cgroup.
755    state: LockDepMutex<CgroupState, CgroupStateLock>,
756
757    weak_self: Weak<Cgroup>,
758}
759pub type CgroupHandle = Arc<Cgroup>;
760
761/// Returns the path from the root to this `cgroup`.
762pub fn path_from_root(weak_cgroup: Option<Weak<Cgroup>>) -> Result<FsString, Errno> {
763    let cgroup = match weak_cgroup {
764        Some(weak_cgroup) => Weak::upgrade(&weak_cgroup).ok_or_else(|| errno!(ENODEV))?,
765        None => return Ok("/".into()),
766    };
767    let mut path = PathBuilder::new();
768    let mut current = Some(cgroup);
769    while let Some(cgroup) = current {
770        path.prepend_element(cgroup.name());
771        current = cgroup.parent()?;
772    }
773    Ok(path.build_absolute())
774}
775
776impl Cgroup {
777    pub fn new(
778        id: u64,
779        name: &FsStr,
780        root: &Weak<CgroupRoot>,
781        parent: Option<Weak<Cgroup>>,
782    ) -> CgroupHandle {
783        let root_shared = root.upgrade().expect("root must exist");
784        let mut controllers = HashMap::new();
785        for controller in &root_shared.controllers {
786            match controller {
787                ControllerType::Cpuset => {
788                    controllers.insert(
789                        *controller,
790                        ControllerState::Cpuset(CpusetControllerState::default()),
791                    );
792                }
793                ControllerType::Freezer => {
794                    controllers.insert(
795                        *controller,
796                        ControllerState::Freezer(FreezerControllerState::default()),
797                    );
798                }
799                _ => {}
800            }
801        }
802
803        Arc::new_cyclic(|weak| Self {
804            id,
805            root: root.clone(),
806            name: name.to_owned(),
807            parent,
808            state: LockDepMutex::new(CgroupState {
809                children: Default::default(),
810                processes: Default::default(),
811                deleted: false,
812                wait_queue: Default::default(),
813                controllers,
814            }),
815            weak_self: weak.clone(),
816        })
817    }
818
819    pub fn name(&self) -> &FsStr {
820        self.name.as_ref()
821    }
822
823    fn root(&self) -> Result<Arc<CgroupRoot>, Errno> {
824        self.root.upgrade().ok_or_else(|| errno!(ENODEV))
825    }
826
827    /// Returns the upgraded parent cgroup, or `Ok(None)` if cgroup is a direct desendent of root.
828    /// Errors if parent node is no longer around.
829    fn parent(&self) -> Result<Option<CgroupHandle>, Errno> {
830        self.parent.as_ref().map(|weak| weak.upgrade().ok_or_else(|| errno!(ENODEV))).transpose()
831    }
832
833    fn count_descendants(&self) -> u64 {
834        self.state.lock().children.count_descendants()
835    }
836
837    fn is_controller_supported(&self, controller: ControllerType) -> bool {
838        self.root().map(|r| r.controllers.contains(&controller)).unwrap_or(false)
839    }
840
841    fn cpuset_path(&self, root: &CgroupRoot) -> Option<String> {
842        if !root.has_controller(ControllerType::Cpuset) {
843            return None;
844        }
845        let bytes = path_from_root(Some(self.weak_self.clone())).ok()?;
846        std::str::from_utf8(&bytes).ok().map(|s| s.to_string())
847    }
848}
849
850impl CgroupOps for Cgroup {
851    fn id(&self) -> u64 {
852        self.id
853    }
854
855    fn add_process(&self, thread_group: &ThreadGroup) -> Result<(), Errno> {
856        let root = self.root()?;
857        let mut pid_table = root.pid_table.lock();
858        match pid_table.entry(thread_group.leader.clone()) {
859            hash_map::Entry::Occupied(mut entry) => {
860                // Check if thread_group is already in the current cgroup. Linux does not return an error if
861                // it already exists.
862                if std::ptr::eq(self, entry.get().as_ptr()) {
863                    return Ok(());
864                }
865
866                // If thread_group is in another cgroup, we need to remove it first.
867                track_stub!(TODO("https://fxbug.dev/383374687"), "check permissions");
868                if let Some(other_cgroup) = entry.get().upgrade() {
869                    other_cgroup.state.lock().remove_process(thread_group)?;
870                }
871
872                self.state.lock().add_process(thread_group)?;
873                entry.insert(self.weak_self.clone());
874            }
875            hash_map::Entry::Vacant(entry) => {
876                self.state.lock().add_process(thread_group)?;
877                entry.insert(self.weak_self.clone());
878            }
879        }
880
881        let tasks = thread_group.read().tasks();
882        if let Some(cpuset_path) = self.cpuset_path(&root) {
883            for task in &tasks {
884                task.write().cpuset_path = cpuset_path.clone();
885            }
886        }
887
888        // Re-evaluate roles for all threads in the thread group.
889        for task in tasks {
890            if let Err(e) = task.sync_scheduler_state_to_role() {
891                log_warn!("Failed to set thread role for task {}: {:?}", task.tid, e);
892            }
893        }
894
895        Ok(())
896    }
897
898    fn new_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
899        let id = self.root()?.get_next_id();
900        let new_child = Cgroup::new(id, name, &self.root, Some(self.weak_self.clone()));
901        let mut state = self.state.lock();
902        if state.deleted {
903            return error!(ENOENT);
904        }
905        // New child should inherit the effective freezer state of the current cgroup.
906        // This allow_subclass is safe because the lock is being acquired
907        // in a strictly top-down traversal of the Cgroup tree (from parent
908        // to child), so no lock ordering cycles can be formed.
909        let effective_freezer = state.get_effective_freezer_state();
910        let _token = allow_subclass();
911        if let Some(freezer) = new_child.state.lock().freezer_mut() {
912            freezer.inherited_freezer_state = effective_freezer;
913        }
914        state.children.insert_child(name.into(), new_child)
915    }
916
917    fn get_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
918        let state = self.state.lock();
919        state.children.get_child(name).ok_or_else(|| errno!(ENOENT))
920    }
921
922    fn remove_child(&self, name: &FsStr) -> Result<CgroupHandle, Errno> {
923        let mut state = self.state.lock();
924        if state.deleted {
925            return error!(ENOENT);
926        }
927        state.children.remove_child(name)
928    }
929
930    fn get_children(&self) -> Result<Vec<CgroupHandle>, Errno> {
931        let state = self.state.lock();
932        if state.deleted {
933            return error!(ENOENT);
934        }
935        Ok(state.children.get_children())
936    }
937
938    fn get_pids(&self, _kernel: &Kernel) -> Vec<pid_t> {
939        let mut state = self.state.lock();
940        state.update_processes();
941        state
942            .processes
943            .iter()
944            .filter_map(|v| v.get_thread_group().ok().map(|tg| tg.leader.id))
945            .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!(root.get_cgroup(&process.pid).unwrap().as_ptr(), Arc::as_ptr(&cgroup));
1073
1074            let thread = process.clone_task_for_test(
1075                (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM) as u64,
1076                Some(SIGCHLD),
1077            );
1078
1079            let thread_state = thread.read();
1080            let kernel_signals = thread_state.kernel_signals_for_test();
1081            assert_matches!(kernel_signals.front(), Some(KernelSignal::Freeze(_)));
1082        })
1083        .await;
1084    }
1085
1086    #[::fuchsia::test]
1087    async fn cgroup_tg_release_removes_pid() {
1088        spawn_kernel_and_run(async |current_task| {
1089            let kernel = current_task.kernel();
1090            let root = &kernel.cgroups.cgroup2;
1091            let cgroup = root.new_child("test".into()).expect("new_child on root cgroup succeeds");
1092
1093            let process = current_task.clone_task_for_test(0, Some(SIGCHLD));
1094            cgroup.add_process(process.thread_group()).expect("add process to cgroup");
1095
1096            assert_eq!(root.get_cgroup(&process.pid).unwrap().as_ptr(), Arc::as_ptr(&cgroup));
1097
1098            // Drop the process to release it.
1099            drop(process);
1100
1101            // Verify that the process is removed from the cgroup pid table.
1102            assert!(root.pid_table.lock().is_empty());
1103        })
1104        .await;
1105    }
1106}