1use starnix_core::security;
6use starnix_core::task::{Cgroup, CgroupOps, CgroupRoot, CgroupV1Key, ControllerType, CurrentTask};
7use starnix_core::vfs::{
8 CacheMode, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNodeHandle,
9 FsNodeInfo, FsStr,
10};
11use starnix_logging::log_warn;
12use starnix_sync::{
13 CgroupDirectoryNodesLock, FileOpsCore, LockDepMutex, LockEqualOrBefore, Locked, Unlocked,
14};
15use starnix_types::vfs::default_statfs;
16use starnix_uapi::auth::FsCred;
17use starnix_uapi::errors::Errno;
18use starnix_uapi::{CGROUP_SUPER_MAGIC, CGROUP2_SUPER_MAGIC, errno, error, mode, statfs};
19
20use std::collections::{BTreeSet, HashMap};
21use std::sync::{Arc, Weak};
22
23use crate::directory::{CgroupDirectory, CgroupDirectoryHandle};
24
25pub struct CgroupV1Fs {
26 pub root: Arc<CgroupRoot>,
27
28 pub dir_nodes: Arc<DirectoryNodes>,
30
31 pub name: &'static FsStr,
34
35 pub hierarchy_key: CgroupV1Key,
37}
38
39impl CgroupV1Fs {
40 pub fn new_fs(
41 locked: &mut Locked<Unlocked>,
42 current_task: &CurrentTask,
43 options: FileSystemOptions,
44 ) -> Result<FileSystemHandle, Errno> {
45 Self::new_fs_inner(locked, current_task, options, b"cgroup".into())
46 }
47
48 pub fn new_fs_cpuset(
49 locked: &mut Locked<Unlocked>,
50 current_task: &CurrentTask,
51 options: FileSystemOptions,
52 ) -> Result<FileSystemHandle, Errno> {
53 Self::new_fs_inner(locked, current_task, options, b"cpuset".into())
54 }
55
56 fn new_fs_inner(
57 locked: &mut Locked<Unlocked>,
58 current_task: &CurrentTask,
59 options: FileSystemOptions,
60 fs_name: &'static FsStr,
61 ) -> Result<FileSystemHandle, Errno> {
62 let kernel = current_task.kernel();
63
64 let mut params = options.params.clone();
65 let _ = security::sb_eat_lsm_opts(kernel, &mut params)?;
70
71 let name = params.get(b"name").map(|n| String::from_utf8_lossy(n.as_ref()).to_string());
72
73 let mut controllers = BTreeSet::new();
74 for key in params.keys() {
75 let key_str = String::from_utf8_lossy(key.as_ref());
76 if let Ok(controller) = key_str.parse::<ControllerType>() {
77 controllers.insert(controller);
78 } else {
79 if key_str != "name"
82 && key_str != "none"
83 && key_str != "noprefix"
84 && key_str != "cpuset_v2_mode"
85 && key_str != "clone_children"
86 {
87 log_warn!("cgroup v1: invalid controller or option: {}", key_str);
88 }
89 continue;
90 }
91 }
92
93 if params.get(b"none").is_some() && !controllers.is_empty() {
94 return error!(EINVAL);
95 }
96
97 if fs_name == b"cpuset" {
98 if !controllers.is_empty()
100 && (controllers.len() > 1 || !controllers.contains(&ControllerType::Cpuset))
101 {
102 return error!(EINVAL);
103 }
104 controllers.insert(ControllerType::Cpuset);
105 }
106
107 if controllers.is_empty() && name.is_none() {
108 log_warn!("Mounting cgroup v1 without controllers or name is not supported");
109 return error!(EINVAL);
110 }
111
112 let root = kernel.cgroups.get_or_create_cgroup1(&controllers, name.as_deref())?;
113
114 let hierarchy_key = CgroupV1Key { controllers, name };
115
116 let dir_nodes =
117 DirectoryNodes::new(Arc::downgrade(&root), CgroupVersion::V1(hierarchy_key.clone()));
118 let root_dir = dir_nodes.root.clone();
119 let fs = FileSystem::new(
120 locked,
121 kernel,
122 CacheMode::Uncached,
123 CgroupV1Fs {
124 dir_nodes: dir_nodes.clone(),
125 root: root.clone(),
126 name: fs_name,
127 hierarchy_key,
128 },
129 options,
130 )?;
131 root_dir.create_root_interface_files(&fs);
132 let root_ino = fs.allocate_ino();
133 fs.create_root(root_ino, root_dir);
134
135 dir_nodes.populate_from_root(&fs, &root, FsCred::root())?;
137
138 Ok(fs)
139 }
140}
141impl FileSystemOps for CgroupV1Fs {
142 fn name(&self) -> &'static FsStr {
143 self.name
144 }
145 fn statfs(
146 &self,
147 _locked: &mut Locked<FileOpsCore>,
148 _fs: &FileSystem,
149 _current_task: &CurrentTask,
150 ) -> Result<statfs, Errno> {
151 Ok(default_statfs(CGROUP_SUPER_MAGIC))
152 }
153}
154
155pub struct CgroupV2Fs {
156 pub dir_nodes: Arc<DirectoryNodes>,
158}
159
160struct CgroupV2FsHandle(FileSystemHandle);
161pub fn cgroup2_fs(
162 locked: &mut Locked<Unlocked>,
163 current_task: &CurrentTask,
164 options: FileSystemOptions,
165) -> Result<FileSystemHandle, Errno> {
166 Ok(current_task
167 .kernel()
168 .expando
169 .get_or_try_init(|| {
170 Ok(CgroupV2FsHandle(CgroupV2Fs::new_fs(locked, current_task, options)?))
171 })?
172 .0
173 .clone())
174}
175
176impl CgroupV2Fs {
177 fn new_fs<L>(
178 locked: &mut Locked<L>,
179 current_task: &CurrentTask,
180 options: FileSystemOptions,
181 ) -> Result<FileSystemHandle, Errno>
182 where
183 L: LockEqualOrBefore<FileOpsCore>,
184 {
185 let kernel = current_task.kernel();
186 let dir_nodes =
187 DirectoryNodes::new(Arc::downgrade(&kernel.cgroups.cgroup2), CgroupVersion::V2);
188 let root = dir_nodes.root.clone();
189 let fs = FileSystem::new(
190 locked,
191 kernel,
192 CacheMode::Uncached,
193 CgroupV2Fs { dir_nodes },
194 options,
195 )?;
196 root.create_root_interface_files(&fs);
197 let root_ino = fs.allocate_ino();
198 fs.create_root(root_ino, root);
199 Ok(fs)
200 }
201}
202
203impl FileSystemOps for CgroupV2Fs {
204 fn name(&self) -> &'static FsStr {
205 b"cgroup2".into()
206 }
207 fn statfs(
208 &self,
209 _locked: &mut Locked<FileOpsCore>,
210 _fs: &FileSystem,
211 _current_task: &CurrentTask,
212 ) -> Result<statfs, Errno> {
213 Ok(default_statfs(CGROUP2_SUPER_MAGIC))
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
219pub enum CgroupVersion {
220 V1(CgroupV1Key),
221 V2,
222}
223
224pub struct DirectoryNodes {
225 root: CgroupDirectoryHandle,
228
229 nodes: LockDepMutex<HashMap<u64, FsNodeHandle>, CgroupDirectoryNodesLock>,
232
233 pub version: CgroupVersion,
235}
236
237impl DirectoryNodes {
238 pub fn new(root_cgroup: Weak<CgroupRoot>, version: CgroupVersion) -> Arc<DirectoryNodes> {
239 Arc::new_cyclic(|weak_self| Self {
240 root: CgroupDirectory::new_root(root_cgroup, weak_self.clone()),
241 nodes: Default::default(),
242 version,
243 })
244 }
245
246 pub fn get_node(&self, cgroup: &Arc<Cgroup>) -> Result<FsNodeHandle, Errno> {
248 let nodes = self.nodes.lock();
249 nodes.get(&cgroup.id()).cloned().ok_or_else(|| errno!(ENOENT))
250 }
251
252 pub fn get_nodes(&self, cgroups: &Vec<Arc<Cgroup>>) -> Vec<Option<FsNodeHandle>> {
254 let nodes = self.nodes.lock();
255 cgroups.iter().map(|cgroup| nodes.get(&cgroup.id()).cloned()).collect()
256 }
257
258 pub fn add_node(
260 &self,
261 cgroup: &Arc<Cgroup>,
262 directory: CgroupDirectoryHandle,
263 fs: &FileSystemHandle,
264 owner: FsCred,
265 ) -> FsNodeHandle {
266 let id = cgroup.id();
267 let node = fs.create_node_and_allocate_node_id(
268 directory,
269 FsNodeInfo::new(mode!(IFDIR, 0o755), owner),
270 );
271 let mut nodes = self.nodes.lock();
272 nodes.insert(id, node.clone());
273 node
274 }
275
276 pub fn remove_node(&self, cgroup: &Arc<Cgroup>) -> Result<FsNodeHandle, Errno> {
278 let id = cgroup.id();
279 let mut nodes = self.nodes.lock();
280 nodes.remove(&id).ok_or_else(|| errno!(ENOENT))
281 }
282
283 pub fn populate_from_root(
284 self: &Arc<Self>,
285 fs: &FileSystemHandle,
286 root: &Arc<CgroupRoot>,
287 owner: FsCred,
288 ) -> Result<(), Errno> {
289 let children = root.get_children()?;
290 for child in children {
291 self.populate_recursive(fs, &child, owner.clone())?;
292 }
293 Ok(())
294 }
295
296 fn populate_recursive(
297 self: &Arc<Self>,
298 fs: &FileSystemHandle,
299 cgroup: &Arc<Cgroup>,
300 owner: FsCred,
301 ) -> Result<(), Errno> {
302 let directory = CgroupDirectory::new(
303 Arc::downgrade(cgroup) as Weak<dyn CgroupOps>,
304 fs,
305 self,
306 owner.clone(),
307 );
308 self.add_node(cgroup, directory, fs, owner.clone());
309
310 let children = cgroup.get_children()?;
311 for child in children {
312 self.populate_recursive(fs, &child, owner.clone())?;
313 }
314 Ok(())
315 }
316}
317
318#[cfg(test)]
319mod test {
320 use super::*;
321
322 #[allow(deprecated, reason = "pre-existing usage")]
323 use starnix_core::testing::create_kernel_task_and_unlocked;
324 use starnix_core::vfs::FsNodeOps;
325 use starnix_core::vfs::fs_args::MountParams;
326 use starnix_core::vfs::fs_registry::FsRegistry;
327 use starnix_uapi::file_mode::FileMode;
328
329 #[::fuchsia::test]
330 async fn test_filesystem_creates_nodes() {
331 #[allow(deprecated, reason = "pre-existing usage")]
332 let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
333 let registry = kernel.expando.get::<FsRegistry>();
334 registry.register(b"cgroup2".into(), cgroup2_fs);
335
336 let fs = current_task
337 .create_filesystem(locked, b"cgroup2".into(), Default::default())
338 .expect("create_filesystem");
339
340 let cgroupfs = fs.downcast_ops::<CgroupV2Fs>().expect("downcast_ops");
341 let dir_nodes = cgroupfs.dir_nodes.clone();
342 assert!(dir_nodes.nodes.lock().is_empty(), "new filesystem does not contain nodes");
343
344 let root_dir = dir_nodes.root.clone();
345 assert!(root_dir.has_interface_files(), "root directory is initialized");
346 }
347
348 #[::fuchsia::test]
349 async fn test_cgroup_v1_remount_preserves_tree() {
350 #[allow(deprecated, reason = "pre-existing usage")]
351 let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
352 let registry = kernel.expando.get::<FsRegistry>();
353 registry.register(b"cgroup".into(), CgroupV1Fs::new_fs);
354
355 let options = FileSystemOptions {
356 params: MountParams::parse(b"memory".into()).unwrap(),
357 ..Default::default()
358 };
359
360 {
361 let fs1 = current_task
362 .create_filesystem(&mut *locked, b"cgroup".into(), options.clone())
363 .expect("create_filesystem");
364 let cgroupfs1 = fs1.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
365 let dir_nodes1 = cgroupfs1.dir_nodes.clone();
366 let root_dir1 = dir_nodes1.root.clone();
367 let root_node1 = fs1.root();
368
369 let locked_file_ops = locked.cast_locked::<FileOpsCore>();
370 root_dir1
371 .mkdir(
372 locked_file_ops,
373 &root_node1.node,
374 ¤t_task,
375 "test_child".into(),
376 FileMode::default(),
377 FsCred::root(),
378 )
379 .expect("mkdir");
380
381 let lookup_result1 = root_dir1.lookup(
382 locked_file_ops,
383 &root_node1.node,
384 ¤t_task,
385 "test_child".into(),
386 );
387 assert!(lookup_result1.is_ok());
388 }
389
390 let fs2 = current_task
391 .create_filesystem(&mut *locked, b"cgroup".into(), options)
392 .expect("create_filesystem");
393 let cgroupfs2 = fs2.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
394 let dir_nodes2 = cgroupfs2.dir_nodes.clone();
395 let root_dir2 = dir_nodes2.root.clone();
396 let root_node2 = fs2.root();
397
398 let locked_file_ops2 = locked.cast_locked::<FileOpsCore>();
399 let lookup_result2 = root_dir2.lookup(
400 locked_file_ops2,
401 &root_node2.node,
402 ¤t_task,
403 "test_child".into(),
404 );
405 assert!(lookup_result2.is_ok(), "child cgroup should be preserved on remount");
406 }
407}