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
298 #[::fuchsia::test]
299 async fn test_filesystem_creates_nodes() {
300 spawn_kernel_and_run(async move |current_task| {
301 let kernel = current_task.kernel();
302 let registry = kernel.expando.get::<FsRegistry>();
303 registry.register(b"cgroup2".into(), cgroup2_fs);
304
305 let fs = current_task
306 .create_filesystem(b"cgroup2".into(), Default::default())
307 .expect("create_filesystem");
308
309 let cgroupfs = fs.downcast_ops::<CgroupV2Fs>().expect("downcast_ops");
310 let dir_nodes = cgroupfs.dir_nodes.clone();
311 assert!(dir_nodes.nodes.lock().is_empty(), "new filesystem does not contain nodes");
312
313 let root_dir = dir_nodes.root.clone();
314 assert!(root_dir.has_interface_files(), "root directory is initialized");
315 })
316 .await;
317 }
318 #[::fuchsia::test]
319 async fn test_cgroup_v1_remount_preserves_tree() {
320 spawn_kernel_and_run(async move |current_task| {
321 let kernel = current_task.kernel();
322 let registry = kernel.expando.get::<FsRegistry>();
323 registry.register(b"cgroup".into(), CgroupV1Fs::new_fs);
324
325 let options = FileSystemOptions {
326 params: MountParams::parse(b"memory".into()).unwrap(),
327 ..Default::default()
328 };
329
330 {
331 let fs1 = current_task
332 .create_filesystem(b"cgroup".into(), options.clone())
333 .expect("create_filesystem");
334 let cgroupfs1 = fs1.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
335 let dir_nodes1 = cgroupfs1.dir_nodes.clone();
336 let root_dir1 = dir_nodes1.root.clone();
337 let root_node1 = fs1.root();
338
339 root_dir1
340 .mkdir(
341 &root_node1.node,
342 current_task,
343 "test_child".into(),
344 FileMode::default(),
345 FsCred::root(),
346 )
347 .expect("mkdir");
348
349 let lookup_result1 =
350 root_dir1.lookup(&root_node1.node, current_task, "test_child".into());
351 assert!(lookup_result1.is_ok());
352 }
353
354 let fs2 = current_task
355 .create_filesystem(b"cgroup".into(), options)
356 .expect("create_filesystem");
357 let cgroupfs2 = fs2.downcast_ops::<CgroupV1Fs>().expect("downcast_ops");
358 let dir_nodes2 = cgroupfs2.dir_nodes.clone();
359 let root_dir2 = dir_nodes2.root.clone();
360 let root_node2 = fs2.root();
361
362 let lookup_result2 =
363 root_dir2.lookup(&root_node2.node, current_task, "test_child".into());
364 assert!(lookup_result2.is_ok(), "child cgroup should be preserved on remount");
365 })
366 .await;
367 }
368}