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::{CGROUP_SUPER_MAGIC, CGROUP2_SUPER_MAGIC, errno, error, mode, statfs};
17
18use std::collections::{BTreeSet, HashMap};
19use std::sync::{Arc, Weak};
20
21use crate::directory::{CgroupDirectory, CgroupDirectoryHandle};
22
23pub struct CgroupV1Fs {
24 pub root: Arc<CgroupRoot>,
25
26 pub dir_nodes: Arc<DirectoryNodes>,
28
29 pub name: &'static FsStr,
32
33 pub hierarchy_key: CgroupV1Key,
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.clone()));
113 let root_dir = dir_nodes.root.clone();
114 let fs = FileSystem::new(
115 kernel,
116 CacheMode::Uncached,
117 CgroupV1Fs {
118 dir_nodes: dir_nodes.clone(),
119 root: root.clone(),
120 name: fs_name,
121 hierarchy_key,
122 },
123 options,
124 )?;
125 root_dir.create_root_interface_files(&fs);
126 let root_ino = fs.allocate_ino();
127 fs.create_root(root_ino, root_dir);
128
129 dir_nodes.populate_from_root(&fs, &root, FsCred::root())?;
131
132 Ok(fs)
133 }
134}
135impl FileSystemOps for CgroupV1Fs {
136 fn name(&self) -> &'static FsStr {
137 self.name
138 }
139 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
140 Ok(default_statfs(CGROUP_SUPER_MAGIC))
141 }
142}
143
144pub struct CgroupV2Fs {
145 pub 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(kernel, CacheMode::Uncached, CgroupV2Fs { dir_nodes }, options)?;
172 root.create_root_interface_files(&fs);
173 let root_ino = fs.allocate_ino();
174 fs.create_root(root_ino, root);
175 Ok(fs)
176 }
177}
178
179impl FileSystemOps for CgroupV2Fs {
180 fn name(&self) -> &'static FsStr {
181 b"cgroup2".into()
182 }
183 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
184 Ok(default_statfs(CGROUP2_SUPER_MAGIC))
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum CgroupVersion {
191 V1(CgroupV1Key),
192 V2,
193}
194
195pub struct DirectoryNodes {
196 root: CgroupDirectoryHandle,
199
200 nodes: LockDepMutex<HashMap<u64, FsNodeHandle>, CgroupDirectoryNodesLock>,
203
204 pub version: CgroupVersion,
206}
207
208impl DirectoryNodes {
209 pub fn new(root_cgroup: Weak<CgroupRoot>, version: CgroupVersion) -> Arc<DirectoryNodes> {
210 Arc::new_cyclic(|weak_self| Self {
211 root: CgroupDirectory::new_root(root_cgroup, weak_self.clone()),
212 nodes: Default::default(),
213 version,
214 })
215 }
216
217 pub fn get_node(&self, cgroup: &Arc<Cgroup>) -> Result<FsNodeHandle, Errno> {
219 let nodes = self.nodes.lock();
220 nodes.get(&cgroup.id()).cloned().ok_or_else(|| errno!(ENOENT))
221 }
222
223 pub fn get_nodes(&self, cgroups: &Vec<Arc<Cgroup>>) -> Vec<Option<FsNodeHandle>> {
225 let nodes = self.nodes.lock();
226 cgroups.iter().map(|cgroup| nodes.get(&cgroup.id()).cloned()).collect()
227 }
228
229 pub fn add_node(
231 &self,
232 cgroup: &Arc<Cgroup>,
233 directory: CgroupDirectoryHandle,
234 fs: &FileSystemHandle,
235 owner: FsCred,
236 ) -> FsNodeHandle {
237 let id = cgroup.id();
238 let node = fs.create_node_and_allocate_node_id(
239 directory,
240 FsNodeInfo::new(mode!(IFDIR, 0o755), owner),
241 );
242 let mut nodes = self.nodes.lock();
243 nodes.insert(id, node.clone());
244 node
245 }
246
247 pub fn remove_node(&self, cgroup: &Arc<Cgroup>) -> Result<FsNodeHandle, Errno> {
249 let id = cgroup.id();
250 let mut nodes = self.nodes.lock();
251 nodes.remove(&id).ok_or_else(|| errno!(ENOENT))
252 }
253
254 pub fn populate_from_root(
255 self: &Arc<Self>,
256 fs: &FileSystemHandle,
257 root: &Arc<CgroupRoot>,
258 owner: FsCred,
259 ) -> Result<(), Errno> {
260 let children = root.get_children()?;
261 for child in children {
262 self.populate_recursive(fs, &child, owner.clone())?;
263 }
264 Ok(())
265 }
266
267 fn populate_recursive(
268 self: &Arc<Self>,
269 fs: &FileSystemHandle,
270 cgroup: &Arc<Cgroup>,
271 owner: FsCred,
272 ) -> Result<(), Errno> {
273 let directory = CgroupDirectory::new(
274 Arc::downgrade(cgroup) as Weak<dyn CgroupOps>,
275 fs,
276 self,
277 owner.clone(),
278 );
279 self.add_node(cgroup, directory, fs, owner.clone());
280
281 let children = cgroup.get_children()?;
282 for child in children {
283 self.populate_recursive(fs, &child, owner.clone())?;
284 }
285 Ok(())
286 }
287}
288
289#[cfg(test)]
290mod test {
291 use super::*;
292 use starnix_core::testing::spawn_kernel_and_run;
293 use starnix_core::vfs::FsNodeOps;
294 use starnix_core::vfs::fs_args::MountParams;
295 use starnix_core::vfs::fs_registry::FsRegistry;
296 use starnix_uapi::file_mode::FileMode;
297 use starnix_uapi::fs_type::FileSystemTypeFlags;
298
299 #[::fuchsia::test]
300 async fn test_filesystem_creates_nodes() {
301 spawn_kernel_and_run(async move |current_task| {
302 let kernel = current_task.kernel();
303 let registry = kernel.expando.get::<FsRegistry>();
304 registry.register(b"cgroup2".into(), FileSystemTypeFlags::empty(), cgroup2_fs);
305
306 let fs = current_task
307 .create_filesystem(b"cgroup2".into(), Default::default())
308 .expect("create_filesystem");
309
310 let cgroupfs = fs.downcast_ops::<CgroupV2Fs>().expect("downcast_ops");
311 let dir_nodes = cgroupfs.dir_nodes.clone();
312 assert!(dir_nodes.nodes.lock().is_empty(), "new filesystem does not contain nodes");
313
314 let root_dir = dir_nodes.root.clone();
315 assert!(root_dir.has_interface_files(), "root directory is initialized");
316 })
317 .await;
318 }
319 #[::fuchsia::test]
320 async fn test_cgroup_v1_remount_preserves_tree() {
321 spawn_kernel_and_run(async move |current_task| {
322 let kernel = current_task.kernel();
323 let registry = kernel.expando.get::<FsRegistry>();
324 registry.register(b"cgroup".into(), FileSystemTypeFlags::empty(), CgroupV1Fs::new_fs);
325
326 let options = FileSystemOptions {
327 params: MountParams::parse(b"memory".into()).unwrap(),
328 ..Default::default()
329 };
330
331 {
332 let fs1 = current_task
333 .create_filesystem(b"cgroup".into(), options.clone())
334 .expect("create_filesystem");
335 let cgroupfs1 = fs1.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
336 let dir_nodes1 = cgroupfs1.dir_nodes.clone();
337 let root_dir1 = dir_nodes1.root.clone();
338 let root_node1 = fs1.root();
339
340 root_dir1
341 .mkdir(
342 &root_node1.node,
343 current_task,
344 "test_child".into(),
345 FileMode::default(),
346 FsCred::root(),
347 )
348 .expect("mkdir");
349
350 let lookup_result1 =
351 root_dir1.lookup(&root_node1.node, current_task, "test_child".into());
352 assert!(lookup_result1.is_ok());
353 }
354
355 let fs2 = current_task
356 .create_filesystem(b"cgroup".into(), options)
357 .expect("create_filesystem");
358 let cgroupfs2 = fs2.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
359 let dir_nodes2 = cgroupfs2.dir_nodes.clone();
360 let root_dir2 = dir_nodes2.root.clone();
361 let root_node2 = fs2.root();
362
363 let lookup_result2 =
364 root_dir2.lookup(&root_node2.node, current_task, "test_child".into());
365 assert!(lookup_result2.is_ok(), "child cgroup should be preserved on remount");
366 })
367 .await;
368 }
369}