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