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