1use itertools::Itertools;
6use regex_lite::Regex;
7use starnix_core::mm::{
8 MemoryAccessor, MemoryAccessorExt, MemoryManager, PAGE_SIZE, ProcMapsFile, ProcSmapsFile,
9};
10use starnix_core::security;
11use starnix_core::task::{
12 CurrentTask, Task, TaskPersistentInfo, TaskStateCode, ThreadGroup, ThreadGroupKey,
13 path_from_root,
14};
15use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
16use starnix_core::vfs::pseudo::dynamic_file::{DynamicFile, DynamicFileBuf, DynamicFileSource};
17use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
18use starnix_core::vfs::pseudo::simple_file::{
19 BytesFile, BytesFileOps, SimpleFileNode, parse_i32_file, parse_unsigned_file,
20 serialize_for_file,
21};
22use starnix_core::vfs::pseudo::stub_empty_file::StubEmptyFile;
23use starnix_core::vfs::pseudo::vec_directory::{VecDirectory, VecDirectoryEntry};
24use starnix_core::vfs::{
25 CallbackSymlinkNode, CloseFreeSafe, DirectoryEntryType, DirentSink, FdNumber, FileObject,
26 FileOps, FileSystemHandle, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
27 ProcMountinfoFile, ProcMountsFile, SeekTarget, SymlinkTarget, default_seek, emit_dotdot,
28 fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
29 fileops_impl_unbounded_seek, fs_node_impl_dir_readonly,
30};
31use starnix_logging::{bug_ref, track_stub};
32use starnix_sync::{FileOpsCore, Locked};
33use starnix_task_command::TaskCommand;
34use starnix_types::time::duration_to_scheduler_clock;
35use starnix_uapi::auth::{
36 CAP_SYS_NICE, CAP_SYS_RESOURCE, Capabilities, PTRACE_MODE_ATTACH_FSCREDS, PTRACE_MODE_NOAUDIT,
37 PTRACE_MODE_READ_FSCREDS, PtraceAccessMode,
38};
39use starnix_uapi::device_id::DeviceId;
40use starnix_uapi::errors::Errno;
41use starnix_uapi::file_mode::{Access, FileMode, mode};
42use starnix_uapi::open_flags::OpenFlags;
43use starnix_uapi::resource_limits::Resource;
44use starnix_uapi::user_address::UserAddress;
45use starnix_uapi::{
46 OOM_ADJUST_MIN, OOM_DISABLE, OOM_SCORE_ADJ_MIN, RLIM_INFINITY, errno, error, ino_t, off_t,
47 pid_t, uapi,
48};
49use std::borrow::Cow;
50use std::ops::{Deref, Range};
51use std::sync::{Arc, LazyLock, Weak};
52
53fn task_entries(scope: TaskEntryScope) -> Vec<(FsString, FileMode)> {
55 let mut entries = vec![
57 (b"cgroup".into(), mode!(IFREG, 0o444)),
58 (b"cwd".into(), mode!(IFLNK, 0o777)),
59 (b"exe".into(), mode!(IFLNK, 0o777)),
60 (b"fd".into(), mode!(IFDIR, 0o500)),
61 (b"fdinfo".into(), mode!(IFDIR, 0o555)),
62 (b"io".into(), mode!(IFREG, 0o400)),
63 (b"limits".into(), mode!(IFREG, 0o444)),
64 (b"maps".into(), mode!(IFREG, 0o444)),
65 (b"mem".into(), mode!(IFREG, 0o600)),
66 (b"root".into(), mode!(IFLNK, 0o777)),
67 (b"sched".into(), mode!(IFREG, 0o644)),
68 (b"schedstat".into(), mode!(IFREG, 0o444)),
69 (b"smaps".into(), mode!(IFREG, 0o444)),
70 (b"stat".into(), mode!(IFREG, 0o444)),
71 (b"statm".into(), mode!(IFREG, 0o444)),
72 (b"status".into(), mode!(IFREG, 0o444)),
73 (b"cmdline".into(), mode!(IFREG, 0o444)),
74 (b"environ".into(), mode!(IFREG, 0o400)),
75 (b"auxv".into(), mode!(IFREG, 0o400)),
76 (b"comm".into(), mode!(IFREG, 0o644)),
77 (b"attr".into(), mode!(IFDIR, 0o555)),
78 (b"ns".into(), mode!(IFDIR, 0o511)),
79 (b"mountinfo".into(), mode!(IFREG, 0o444)),
80 (b"mounts".into(), mode!(IFREG, 0o444)),
81 (b"oom_adj".into(), mode!(IFREG, 0o744)),
82 (b"oom_score".into(), mode!(IFREG, 0o444)),
83 (b"oom_score_adj".into(), mode!(IFREG, 0o744)),
84 (b"timerslack_ns".into(), mode!(IFREG, 0o666)),
85 (b"wchan".into(), mode!(IFREG, 0o444)),
86 (b"clear_refs".into(), mode!(IFREG, 0o200)),
87 (b"pagemap".into(), mode!(IFREG, 0o400)),
88 ];
89
90 if scope == TaskEntryScope::ThreadGroup {
91 entries.push((b"task".into(), mode!(IFDIR, 0o555)));
92 }
93
94 entries
95}
96
97#[derive(Copy, Clone, Eq, PartialEq)]
98pub enum TaskEntryScope {
99 Task,
100 ThreadGroup,
101}
102
103pub struct TaskDirectory {
111 task_weak: Weak<Task>,
112 scope: TaskEntryScope,
113 inode_range: Range<ino_t>,
114}
115
116#[derive(Clone)]
117struct TaskDirectoryNode(Arc<TaskDirectory>);
118
119impl Deref for TaskDirectoryNode {
120 type Target = TaskDirectory;
121
122 fn deref(&self) -> &Self::Target {
123 &self.0
124 }
125}
126
127impl TaskDirectory {
128 fn new(fs: &FileSystemHandle, task: &Arc<Task>, scope: TaskEntryScope) -> FsNodeHandle {
129 let creds = task.real_creds().euid_as_fscred();
130 let task_weak = Arc::downgrade(task);
131 fs.create_node_and_allocate_node_id(
132 TaskDirectoryNode(Arc::new(TaskDirectory {
133 task_weak,
134 scope,
135 inode_range: fs.allocate_ino_range(task_entries(scope).len()),
136 })),
137 FsNodeInfo::new(mode!(IFDIR, 0o555), creds),
138 )
139 }
140}
141
142impl FsNodeOps for TaskDirectoryNode {
143 fs_node_impl_dir_readonly!();
144
145 fn create_file_ops(
146 &self,
147 _locked: &mut Locked<FileOpsCore>,
148 _node: &FsNode,
149 _current_task: &CurrentTask,
150 _flags: OpenFlags,
151 ) -> Result<Box<dyn FileOps>, Errno> {
152 Ok(Box::new(self.clone()))
153 }
154
155 fn lookup(
156 &self,
157 _locked: &mut Locked<FileOpsCore>,
158 node: &FsNode,
159 _current_task: &CurrentTask,
160 name: &FsStr,
161 ) -> Result<FsNodeHandle, Errno> {
162 let task_weak = self.task_weak.clone();
163 let creds = node.info().cred();
164 let fs = node.fs();
165 let (mode, ino) = task_entries(self.scope)
166 .into_iter()
167 .enumerate()
168 .find_map(|(index, (n, mode))| {
169 if name == *n {
170 Some((mode, self.inode_range.start + index as ino_t))
171 } else {
172 None
173 }
174 })
175 .ok_or_else(|| errno!(ENOENT))?;
176
177 let ops: Box<dyn FsNodeOps> = match &**name {
179 b"cgroup" => Box::new(CgroupFile::new_node(task_weak)),
180 b"cwd" => Box::new(CallbackSymlinkNode::new({
181 move || {
182 Ok(SymlinkTarget::Node(
183 Task::from_weak(&task_weak)?.running_state()?.fs().cwd(),
184 ))
185 }
186 })),
187 b"exe" => Box::new(CallbackSymlinkNode::new({
188 move || {
189 let task = Task::from_weak(&task_weak)?;
190 if let Some(node) = task.mm().ok().and_then(|mm| mm.executable_node()) {
191 Ok(SymlinkTarget::Node(node))
192 } else {
193 error!(ENOENT)
194 }
195 }
196 })),
197 b"fd" => Box::new(FdDirectory::new(task_weak)),
198 b"fdinfo" => Box::new(FdInfoDirectory::new(task_weak)),
199 b"io" => Box::new(IoFile::new_node()),
200 b"limits" => Box::new(LimitsFile::new_node(task_weak)),
201 b"maps" => Box::new(PtraceCheckedNode::new_node(
202 task_weak,
203 PTRACE_MODE_READ_FSCREDS,
204 |_, _, task| Ok(ProcMapsFile::new(task)),
205 )),
206 b"mem" => Box::new(MemFile::new_node(task_weak)),
207 b"root" => Box::new(CallbackSymlinkNode::new({
208 move || {
209 Ok(SymlinkTarget::Node(
210 Task::from_weak(&task_weak)?.running_state()?.fs().root(),
211 ))
212 }
213 })),
214 b"sched" => Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322893980"))),
215 b"schedstat" => {
216 Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322894256")))
217 }
218 b"smaps" => Box::new(PtraceCheckedNode::new_node(
219 task_weak,
220 PTRACE_MODE_READ_FSCREDS,
221 |_, _, task| Ok(ProcSmapsFile::new(task)),
222 )),
223 b"stat" => Box::new(StatFile::new_node(task_weak, self.scope)),
224 b"statm" => Box::new(StatmFile::new_node(task_weak)),
225 b"status" => Box::new(StatusFile::new_node(task_weak)),
226 b"cmdline" => Box::new(CmdlineFile::new_node(task_weak)),
227 b"environ" => Box::new(EnvironFile::new_node(task_weak)),
228 b"auxv" => Box::new(AuxvFile::new_node(task_weak)),
229 b"comm" => {
230 let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
231 Box::new(CommFile::new_node(task_weak, task.persistent_info.clone()))
232 }
233 b"attr" => {
234 let dir = SimpleDirectory::new();
235 dir.edit(&fs, |dir| {
236 for (attr, name) in [
237 (security::ProcAttr::Current, "current"),
238 (security::ProcAttr::Exec, "exec"),
239 (security::ProcAttr::FsCreate, "fscreate"),
240 (security::ProcAttr::KeyCreate, "keycreate"),
241 (security::ProcAttr::SockCreate, "sockcreate"),
242 ] {
243 dir.entry_etc(
244 name.into(),
245 AttrNode::new(task_weak.clone(), attr),
246 mode!(IFREG, 0o666),
247 DeviceId::NONE,
248 creds,
249 );
250 }
251 dir.entry_etc(
252 "prev".into(),
253 AttrNode::new(task_weak, security::ProcAttr::Previous),
254 mode!(IFREG, 0o444),
255 DeviceId::NONE,
256 creds,
257 );
258 });
259 Box::new(dir)
260 }
261 b"ns" => Box::new(NsDirectory { task: task_weak }),
262 b"mountinfo" => Box::new(ProcMountinfoFile::new_node(task_weak)),
263 b"mounts" => Box::new(ProcMountsFile::new_node(task_weak)),
264 b"oom_adj" => Box::new(OomAdjFile::new_node(task_weak)),
265 b"oom_score" => Box::new(OomScoreFile::new_node(task_weak)),
266 b"oom_score_adj" => Box::new(OomScoreAdjFile::new_node(task_weak)),
267 b"timerslack_ns" => Box::new(TimerslackNsFile::new_node(task_weak)),
268 b"wchan" => Box::new(BytesFile::new_node(b"0".to_vec())),
269 b"clear_refs" => Box::new(ClearRefsFile::new_node(task_weak)),
270 b"pagemap" => Box::new(PtraceCheckedNode::new_node(
271 task_weak,
272 PTRACE_MODE_READ_FSCREDS,
273 |_, _, _| Ok(StubEmptyFile::new(bug_ref!("https://fxbug.dev/452096300"))),
274 )),
275 b"task" => {
276 let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
277 Box::new(TaskListDirectory { thread_group: Arc::downgrade(&task.thread_group()) })
278 }
279 name => unreachable!(
280 "entry \"{:?}\" should be supported to keep in sync with task_entries()",
281 name
282 ),
283 };
284
285 Ok(fs.create_node(ino, ops, FsNodeInfo::new(mode, creds)))
286 }
287}
288
289impl CloseFreeSafe for TaskDirectory {}
291impl FileOps for TaskDirectory {
292 fileops_impl_directory!();
293 fileops_impl_noop_sync!();
294 fileops_impl_unbounded_seek!();
295
296 fn readdir(
297 &self,
298 _locked: &mut Locked<FileOpsCore>,
299 file: &FileObject,
300 _current_task: &CurrentTask,
301 sink: &mut dyn DirentSink,
302 ) -> Result<(), Errno> {
303 emit_dotdot(file, sink)?;
304
305 for (index, (name, mode)) in
308 task_entries(self.scope).into_iter().enumerate().skip(sink.offset() as usize - 2)
309 {
310 sink.add(
311 self.inode_range.start + index as ino_t,
312 sink.offset() + 1,
313 DirectoryEntryType::from_mode(mode),
314 name.as_ref(),
315 )?;
316 }
317 Ok(())
318 }
319
320 fn as_thread_group_key(&self, _file: &FileObject) -> Result<ThreadGroupKey, Errno> {
321 let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
322 Ok(task.thread_group().into())
323 }
324}
325
326pub fn pid_directory(
328 current_task: &CurrentTask,
329 fs: &FileSystemHandle,
330 task: &Arc<Task>,
331) -> FsNodeHandle {
332 let fs_node = TaskDirectory::new(fs, task, TaskEntryScope::ThreadGroup);
335
336 security::task_to_fs_node(current_task, task, &fs_node);
337 fs_node
338}
339
340fn tid_directory(fs: &FileSystemHandle, task: &Arc<Task>) -> FsNodeHandle {
342 TaskDirectory::new(fs, task, TaskEntryScope::Task)
343}
344
345struct FdDirectory {
350 task: Weak<Task>,
351}
352
353impl FdDirectory {
354 fn new(task: Weak<Task>) -> Self {
355 Self { task }
356 }
357}
358
359impl FsNodeOps for FdDirectory {
360 fs_node_impl_dir_readonly!();
361
362 fn create_file_ops(
363 &self,
364 _locked: &mut Locked<FileOpsCore>,
365 _node: &FsNode,
366 _current_task: &CurrentTask,
367 _flags: OpenFlags,
368 ) -> Result<Box<dyn FileOps>, Errno> {
369 Ok(VecDirectory::new_file(fds_to_directory_entries(
370 Task::from_weak(&self.task)?.running_state()?.files.get_all_fds(),
371 )))
372 }
373
374 fn lookup(
375 &self,
376 _locked: &mut Locked<FileOpsCore>,
377 node: &FsNode,
378 _current_task: &CurrentTask,
379 name: &FsStr,
380 ) -> Result<FsNodeHandle, Errno> {
381 let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
382 let task = Task::from_weak(&self.task)?;
383 let file =
385 task.running_state()?.files.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
386 let mode = FileMode::IFLNK | Access::from_open_flags(file.flags()).user_mode();
388 let task_reference = self.task.clone();
389 Ok(node.fs().create_node_and_allocate_node_id(
390 CallbackSymlinkNode::new(move || {
391 let task = Task::from_weak(&task_reference)?;
392 let file = task
393 .running_state()?
394 .files
395 .get_allowing_opath(fd)
396 .map_err(|_| errno!(ENOENT))?;
397 Ok(SymlinkTarget::Node(file.name.to_passive()))
398 }),
399 FsNodeInfo::new(mode, task.real_fscred()),
400 ))
401 }
402}
403
404const NS_ENTRIES: &[&str] = &[
405 "cgroup",
406 "ipc",
407 "mnt",
408 "net",
409 "pid",
410 "pid_for_children",
411 "time",
412 "time_for_children",
413 "user",
414 "uts",
415];
416
417struct AttrNode {
419 attr: security::ProcAttr,
420 task: Weak<Task>,
421}
422
423impl AttrNode {
424 fn new(task: Weak<Task>, attr: security::ProcAttr) -> impl FsNodeOps {
425 SimpleFileNode::new(move |_, _| Ok(AttrNode { attr, task: task.clone() }))
426 }
427}
428
429impl FileOps for AttrNode {
430 fileops_impl_seekable!();
431 fileops_impl_noop_sync!();
432
433 fn writes_update_seek_offset(&self) -> bool {
434 false
435 }
436
437 fn read(
438 &self,
439 _locked: &mut Locked<FileOpsCore>,
440 _file: &FileObject,
441 current_task: &CurrentTask,
442 offset: usize,
443 data: &mut dyn OutputBuffer,
444 ) -> Result<usize, Errno> {
445 let task = Task::from_weak(&self.task)?;
446 let response = security::get_procattr(current_task, &task, self.attr)?;
447 data.write(&response[offset..])
448 }
449
450 fn write(
451 &self,
452 _locked: &mut Locked<FileOpsCore>,
453 _file: &FileObject,
454 current_task: &CurrentTask,
455 offset: usize,
456 data: &mut dyn InputBuffer,
457 ) -> Result<usize, Errno> {
458 let task = Task::from_weak(&self.task)?;
459
460 if current_task.task != task {
462 return error!(EPERM);
463 }
464 if offset != 0 {
465 return error!(EINVAL);
466 }
467
468 let data = data.read_all()?;
469 let data_len = data.len();
470 security::set_procattr(current_task, self.attr, data.as_slice())?;
471 Ok(data_len)
472 }
473}
474
475struct NsDirectory {
477 task: Weak<Task>,
478}
479
480impl FsNodeOps for NsDirectory {
481 fs_node_impl_dir_readonly!();
482
483 fn create_file_ops(
484 &self,
485 _locked: &mut Locked<FileOpsCore>,
486 _node: &FsNode,
487 _current_task: &CurrentTask,
488 _flags: OpenFlags,
489 ) -> Result<Box<dyn FileOps>, Errno> {
490 Ok(VecDirectory::new_file(
493 NS_ENTRIES
494 .iter()
495 .map(|&name| VecDirectoryEntry {
496 entry_type: DirectoryEntryType::LNK,
497 name: FsString::from(name),
498 inode: None,
499 })
500 .collect(),
501 ))
502 }
503
504 fn lookup(
505 &self,
506 _locked: &mut Locked<FileOpsCore>,
507 node: &FsNode,
508 current_task: &CurrentTask,
509 name: &FsStr,
510 ) -> Result<FsNodeHandle, Errno> {
511 let name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
516 let mut elements = name.split(':');
517 let ns = elements.next().expect("name must not be empty");
518 if !NS_ENTRIES.contains(&ns) {
520 return error!(ENOENT);
521 }
522
523 let task = Task::from_weak(&self.task)?;
524 if let Some(id) = elements.next() {
525 static NS_IDENTIFIER_RE: LazyLock<Regex> =
527 LazyLock::new(|| Regex::new("^\\[[0-9]+\\]$").unwrap());
528 if !NS_IDENTIFIER_RE.is_match(id) {
529 return error!(ENOENT);
530 }
531 let node_info = || FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred());
532 let fallback = || {
533 node.fs().create_node_and_allocate_node_id(BytesFile::new_node(vec![]), node_info())
534 };
535 Ok(match ns {
536 "cgroup" => {
537 track_stub!(TODO("https://fxbug.dev/297313673"), "cgroup namespaces");
538 fallback()
539 }
540 "ipc" => {
541 track_stub!(TODO("https://fxbug.dev/297313673"), "ipc namespaces");
542 fallback()
543 }
544 "mnt" => node
545 .fs()
546 .create_node_and_allocate_node_id(current_task.fs().namespace(), node_info()),
547 "net" => {
548 track_stub!(TODO("https://fxbug.dev/297313673"), "net namespaces");
549 fallback()
550 }
551 "pid" => {
552 track_stub!(TODO("https://fxbug.dev/297313673"), "pid namespaces");
553 fallback()
554 }
555 "pid_for_children" => {
556 track_stub!(TODO("https://fxbug.dev/297313673"), "pid_for_children namespaces");
557 fallback()
558 }
559 "time" => {
560 track_stub!(TODO("https://fxbug.dev/297313673"), "time namespaces");
561 fallback()
562 }
563 "time_for_children" => {
564 track_stub!(
565 TODO("https://fxbug.dev/297313673"),
566 "time_for_children namespaces"
567 );
568 fallback()
569 }
570 "user" => {
571 track_stub!(TODO("https://fxbug.dev/297313673"), "user namespaces");
572 fallback()
573 }
574 "uts" => {
575 track_stub!(TODO("https://fxbug.dev/297313673"), "uts namespaces");
576 fallback()
577 }
578 _ => return error!(ENOENT),
579 })
580 } else {
581 let id = current_task.fs().namespace().id;
583 Ok(node.fs().create_node_and_allocate_node_id(
584 CallbackSymlinkNode::new(move || {
585 Ok(SymlinkTarget::Path(format!("{name}:[{id}]").into()))
586 }),
587 FsNodeInfo::new(mode!(IFLNK, 0o7777), task.real_fscred()),
588 ))
589 }
590 }
591}
592
593struct FdInfoDirectory {
599 task: Weak<Task>,
600}
601
602impl FdInfoDirectory {
603 fn new(task: Weak<Task>) -> Self {
604 Self { task }
605 }
606}
607
608impl FsNodeOps for FdInfoDirectory {
609 fs_node_impl_dir_readonly!();
610
611 fn create_file_ops(
612 &self,
613 locked: &mut Locked<FileOpsCore>,
614 _node: &FsNode,
615 current_task: &CurrentTask,
616 _flags: OpenFlags,
617 ) -> Result<Box<dyn FileOps>, Errno> {
618 let task = Task::from_weak(&self.task)?;
619 current_task
620 .check_ptrace_access_mode(locked, PTRACE_MODE_READ_FSCREDS, &task)
621 .map_err(|_| errno!(EACCES))?;
622
623 Ok(VecDirectory::new_file(fds_to_directory_entries(
624 task.running_state()?.files.get_all_fds(),
625 )))
626 }
627
628 fn lookup(
629 &self,
630 locked: &mut Locked<FileOpsCore>,
631 node: &FsNode,
632 current_task: &CurrentTask,
633 name: &FsStr,
634 ) -> Result<FsNodeHandle, Errno> {
635 let task = Task::from_weak(&self.task)?;
636 let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
637 let file =
638 task.running_state()?.files.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
639 let pos = file.offset.read();
640 let flags = file.flags();
641 let mut data = format!("pos:\t{}\nflags:\t0{:o}\n", pos, flags.bits()).into_bytes();
642 if let Some(extra_fdinfo) = file.extra_fdinfo(locked, current_task) {
643 data.extend_from_slice(extra_fdinfo.as_slice());
644 }
645 Ok(node.fs().create_node_and_allocate_node_id(
646 BytesFile::new_node(data),
647 FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred()),
648 ))
649 }
650}
651
652fn fds_to_directory_entries(fds: Vec<FdNumber>) -> Vec<VecDirectoryEntry> {
653 fds.into_iter()
654 .map(|fd| VecDirectoryEntry {
655 entry_type: DirectoryEntryType::DIR,
656 name: fd.raw().to_string().into(),
657 inode: None,
658 })
659 .collect()
660}
661
662struct TaskListDirectory {
664 thread_group: Weak<ThreadGroup>,
665}
666
667impl TaskListDirectory {
668 fn thread_group(&self) -> Result<Arc<ThreadGroup>, Errno> {
669 self.thread_group.upgrade().ok_or_else(|| errno!(ESRCH))
670 }
671}
672
673impl FsNodeOps for TaskListDirectory {
674 fs_node_impl_dir_readonly!();
675
676 fn create_file_ops(
677 &self,
678 _locked: &mut Locked<FileOpsCore>,
679 _node: &FsNode,
680 _current_task: &CurrentTask,
681 _flags: OpenFlags,
682 ) -> Result<Box<dyn FileOps>, Errno> {
683 Ok(VecDirectory::new_file(
684 self.thread_group()?
685 .read()
686 .task_ids()
687 .map(|tid| VecDirectoryEntry {
688 entry_type: DirectoryEntryType::DIR,
689 name: tid.to_string().into(),
690 inode: None,
691 })
692 .collect(),
693 ))
694 }
695
696 fn lookup(
697 &self,
698 _locked: &mut Locked<FileOpsCore>,
699 node: &FsNode,
700 _current_task: &CurrentTask,
701 name: &FsStr,
702 ) -> Result<FsNodeHandle, Errno> {
703 let thread_group = self.thread_group()?;
704 let tid = std::str::from_utf8(name)
705 .map_err(|_| errno!(ENOENT))?
706 .parse::<pid_t>()
707 .map_err(|_| errno!(ENOENT))?;
708 if !thread_group.read().contains_task(tid) {
710 return error!(ENOENT);
711 }
712
713 let pid_state = thread_group.kernel.pids.read();
714 let task = pid_state.get_task(tid).map_err(|_| errno!(ENOENT))?;
715 std::mem::drop(pid_state);
716
717 Ok(tid_directory(&node.fs(), &task))
718 }
719}
720
721#[derive(Clone)]
722struct CgroupFile(Weak<Task>);
723impl CgroupFile {
724 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
725 DynamicFile::new_node(Self(task))
726 }
727}
728impl DynamicFileSource for CgroupFile {
729 fn generate(
730 &self,
731 _current_task: &CurrentTask,
732 sink: &mut DynamicFileBuf,
733 ) -> Result<(), Errno> {
734 let task = Task::from_weak(&self.0)?;
735 let cgroup = task.kernel().cgroups.cgroup2.get_cgroup(task.thread_group());
736 let path = path_from_root(cgroup)?;
737 sink.write(format!("0::{}\n", path).as_bytes());
738 Ok(())
739 }
740}
741
742fn fill_buf_from_addr_range(
743 task: &Task,
744 range_start: UserAddress,
745 range_end: UserAddress,
746 sink: &mut DynamicFileBuf,
747) -> Result<(), Errno> {
748 #[allow(clippy::manual_saturating_arithmetic)]
749 let len = range_end.ptr().checked_sub(range_start.ptr()).unwrap_or(0);
750 let buf = task.read_memory_partial_to_vec(range_start, len)?;
754 sink.write(&buf[..]);
755 Ok(())
756}
757
758#[derive(Clone)]
760pub struct CmdlineFile {
761 task: Weak<Task>,
762}
763impl CmdlineFile {
764 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
765 DynamicFile::new_node(Self { task })
766 }
767}
768impl DynamicFileSource for CmdlineFile {
769 fn generate(
770 &self,
771 _current_task: &CurrentTask,
772 sink: &mut DynamicFileBuf,
773 ) -> Result<(), Errno> {
774 let Some(task) = self.task.upgrade() else {
776 return Ok(());
777 };
778 let Ok(mm) = task.mm() else {
780 return Ok(());
781 };
782 let (start, end) = {
783 let mm_state = mm.state.read();
784 (mm_state.argv_start, mm_state.argv_end)
785 };
786 fill_buf_from_addr_range(&task, start, end, sink)
787 }
788}
789
790struct PtraceCheckedNode {}
791
792impl PtraceCheckedNode {
793 pub fn new_node<F, O>(task: Weak<Task>, mode: PtraceAccessMode, create_ops: F) -> impl FsNodeOps
794 where
795 F: Fn(&mut Locked<FileOpsCore>, &CurrentTask, Arc<Task>) -> Result<O, Errno>
796 + Send
797 + Sync
798 + 'static,
799 O: FileOps,
800 {
801 SimpleFileNode::new(move |locked, current_task: &CurrentTask| {
802 let task = Task::from_weak(&task)?;
803 if task.mm().is_ok() {
805 current_task
806 .check_ptrace_access_mode(locked, mode, &task)
807 .map_err(|_| errno!(EACCES))?;
808 }
809 create_ops(locked, current_task, task)
810 })
811 }
812}
813
814#[derive(Clone)]
816pub struct EnvironFile {
817 task: Weak<Task>,
818}
819impl EnvironFile {
820 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
821 PtraceCheckedNode::new_node(task, PTRACE_MODE_READ_FSCREDS, |_, _, task| {
822 Ok(DynamicFile::new(Self { task: Arc::downgrade(&task) }))
823 })
824 }
825}
826impl DynamicFileSource for EnvironFile {
827 fn generate(
828 &self,
829 _current_task: &CurrentTask,
830 sink: &mut DynamicFileBuf,
831 ) -> Result<(), Errno> {
832 let task = Task::from_weak(&self.task)?;
833 let Ok(mm) = task.mm() else {
835 return Ok(());
836 };
837 let (start, end) = {
838 let mm_state = mm.state.read();
839 (mm_state.environ_start, mm_state.environ_end)
840 };
841 fill_buf_from_addr_range(&task, start, end, sink)
842 }
843}
844
845#[derive(Clone)]
847pub struct AuxvFile {
848 task: Weak<Task>,
849}
850impl AuxvFile {
851 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
852 PtraceCheckedNode::new_node(task, PTRACE_MODE_READ_FSCREDS, |_, _, task| {
853 Ok(DynamicFile::new(Self { task: Arc::downgrade(&task) }))
854 })
855 }
856}
857impl DynamicFileSource for AuxvFile {
858 fn generate(
859 &self,
860 _current_task: &CurrentTask,
861 sink: &mut DynamicFileBuf,
862 ) -> Result<(), Errno> {
863 let task = Task::from_weak(&self.task)?;
864 let Ok(mm) = task.mm() else {
866 return Ok(());
867 };
868 let (start, end) = {
869 let mm_state = mm.state.read();
870 (mm_state.auxv_start, mm_state.auxv_end)
871 };
872 fill_buf_from_addr_range(&task, start, end, sink)
873 }
874}
875
876pub struct CommFile {
878 task: Weak<Task>,
879 info: TaskPersistentInfo,
880}
881impl CommFile {
882 pub fn new_node(task: Weak<Task>, info: TaskPersistentInfo) -> impl FsNodeOps {
883 SimpleFileNode::new(move |_, _| {
884 Ok(DynamicFile::new(CommFile { task: task.clone(), info: info.clone() }))
885 })
886 }
887}
888
889impl DynamicFileSource for CommFile {
890 fn generate(
891 &self,
892 _current_task: &CurrentTask,
893 sink: &mut DynamicFileBuf,
894 ) -> Result<(), Errno> {
895 sink.write(self.info.command_guard().comm_name());
896 sink.write(b"\n");
897 Ok(())
898 }
899
900 fn write(
901 &self,
902 _locked: &mut Locked<FileOpsCore>,
903 current_task: &CurrentTask,
904 _offset: usize,
905 data: &mut dyn InputBuffer,
906 ) -> Result<usize, Errno> {
907 let task = Task::from_weak(&self.task)?;
908 if !Arc::ptr_eq(&task.thread_group(), ¤t_task.thread_group()) {
909 return error!(EINVAL);
910 }
911 let bytes = data.read_all()?;
914 task.set_command_name(TaskCommand::new(&bytes));
915 Ok(bytes.len())
916 }
917}
918
919#[derive(Clone)]
921pub struct IoFile {}
922impl IoFile {
923 pub fn new_node() -> impl FsNodeOps {
924 DynamicFile::new_node(Self {})
925 }
926}
927impl DynamicFileSource for IoFile {
928 fn generate(
929 &self,
930 _current_task: &CurrentTask,
931 sink: &mut DynamicFileBuf,
932 ) -> Result<(), Errno> {
933 track_stub!(TODO("https://fxbug.dev/322874250"), "/proc/pid/io");
934 sink.write(b"rchar: 0\n");
935 sink.write(b"wchar: 0\n");
936 sink.write(b"syscr: 0\n");
937 sink.write(b"syscw: 0\n");
938 sink.write(b"read_bytes: 0\n");
939 sink.write(b"write_bytes: 0\n");
940 sink.write(b"cancelled_write_bytes: 0\n");
941 Ok(())
942 }
943}
944
945#[derive(Clone)]
947pub struct LimitsFile(Weak<Task>);
948impl LimitsFile {
949 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
950 DynamicFile::new_node(Self(task))
951 }
952}
953impl DynamicFileSource for LimitsFile {
954 fn generate_locked(
955 &self,
956 locked: &mut Locked<FileOpsCore>,
957 _current_task: &CurrentTask,
958 sink: &mut DynamicFileBuf,
959 ) -> Result<(), Errno> {
960 let task = Task::from_weak(&self.0)?;
961 let limits = task.thread_group().limits.lock(locked);
962
963 let write_limit = |sink: &mut DynamicFileBuf, value| {
964 if value == RLIM_INFINITY as u64 {
965 sink.write(format!("{:<20}", "unlimited").as_bytes());
966 } else {
967 sink.write(format!("{:<20}", value).as_bytes());
968 }
969 };
970 sink.write(
971 format!("{:<25}{:<20}{:<20}{:<10}\n", "Limit", "Soft Limit", "Hard Limit", "Units")
972 .as_bytes(),
973 );
974 for resource in Resource::ALL {
975 let desc = resource.desc();
976 let limit = limits.get(resource);
977 sink.write(format!("{:<25}", desc.name).as_bytes());
978 write_limit(sink, limit.rlim_cur);
979 write_limit(sink, limit.rlim_max);
980 if !desc.unit.is_empty() {
981 sink.write(format!("{:<10}", desc.unit).as_bytes());
982 }
983 sink.write(b"\n");
984 }
985 Ok(())
986 }
987}
988
989pub struct MemFile {
991 mm: Weak<MemoryManager>,
992
993 task: Weak<Task>,
997}
998
999impl MemFile {
1000 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1001 PtraceCheckedNode::new_node(task, PTRACE_MODE_ATTACH_FSCREDS, |_, _, task| {
1002 let mm = task.mm().ok().as_ref().map(Arc::downgrade).unwrap_or_default();
1003 Ok(Self { mm, task: Arc::downgrade(&task) })
1004 })
1005 }
1006}
1007
1008impl FileOps for MemFile {
1009 fileops_impl_noop_sync!();
1010
1011 fn is_seekable(&self) -> bool {
1012 true
1013 }
1014
1015 fn seek(
1016 &self,
1017 _locked: &mut Locked<FileOpsCore>,
1018 _file: &FileObject,
1019 _current_task: &CurrentTask,
1020 current_offset: off_t,
1021 target: SeekTarget,
1022 ) -> Result<off_t, Errno> {
1023 default_seek(current_offset, target, || error!(EINVAL))
1024 }
1025
1026 fn read(
1027 &self,
1028 _locked: &mut Locked<FileOpsCore>,
1029 _file: &FileObject,
1030 current_task: &CurrentTask,
1031 offset: usize,
1032 data: &mut dyn OutputBuffer,
1033 ) -> Result<usize, Errno> {
1034 let Some(_task) = self.task.upgrade() else {
1035 return Ok(0);
1036 };
1037 let Some(mm) = self.mm.upgrade() else {
1038 return Ok(0);
1039 };
1040 let mut addr = UserAddress::from(offset as u64);
1041 data.write_each(&mut |bytes| {
1042 let read_bytes = if current_task.has_same_address_space(Some(&mm)) {
1043 current_task.read_memory_partial(addr, bytes)
1044 } else {
1045 mm.syscall_read_memory_partial(addr, bytes)
1046 }
1047 .map_err(|_| errno!(EIO))?;
1048 let actual = read_bytes.len();
1049 addr = (addr + actual)?;
1050 Ok(actual)
1051 })
1052 }
1053
1054 fn write(
1055 &self,
1056 _locked: &mut Locked<FileOpsCore>,
1057 _file: &FileObject,
1058 current_task: &CurrentTask,
1059 offset: usize,
1060 data: &mut dyn InputBuffer,
1061 ) -> Result<usize, Errno> {
1062 let Some(_task) = self.task.upgrade() else {
1063 return Ok(0);
1064 };
1065 let Some(mm) = self.mm.upgrade() else {
1066 return Ok(0);
1067 };
1068 let addr = UserAddress::from(offset as u64);
1069 let mut written = 0;
1070 let result = data.peek_each(&mut |bytes| {
1071 let actual = if current_task.has_same_address_space(Some(&mm)) {
1072 current_task.write_memory_partial((addr + written)?, bytes)
1073 } else {
1074 mm.syscall_write_memory_partial((addr + written)?, bytes)
1075 }
1076 .map_err(|_| errno!(EIO))?;
1077 written += actual;
1078 Ok(actual)
1079 });
1080 data.advance(written)?;
1081 result
1082 }
1083}
1084
1085#[derive(Clone)]
1086pub struct StatFile {
1087 task: Weak<Task>,
1088 scope: TaskEntryScope,
1089}
1090
1091impl StatFile {
1092 pub fn new_node(task: Weak<Task>, scope: TaskEntryScope) -> impl FsNodeOps {
1093 DynamicFile::new_node(Self { task, scope })
1094 }
1095}
1096impl DynamicFileSource for StatFile {
1097 fn generate_locked(
1098 &self,
1099 locked: &mut Locked<FileOpsCore>,
1100 current_task: &CurrentTask,
1101 sink: &mut DynamicFileBuf,
1102 ) -> Result<(), Errno> {
1103 let task = Task::from_weak(&self.task)?;
1104
1105 let pid: pid_t; let comm: TaskCommand;
1109 let state: char;
1110 let ppid: pid_t;
1111 let pgrp: pid_t; let session: pid_t;
1113 let tty_nr: i32;
1114 let tpgid: i32 = 0;
1115 let flags: u32 = 0;
1116 let minflt: u64 = 0; let cminflt: u64 = 0;
1118 let majflt: u64 = 0;
1119 let cmajflt: u64 = 0;
1120 let utime: i64;
1121 let stime: i64; let cutime: i64;
1123 let cstime: i64;
1124 let priority: i64 = 0;
1125 let nice: i64;
1126 let num_threads: i64; let itrealvalue: i64 = 0;
1128 let mut starttime: u64 = 0;
1129 let mut vsize: usize = 0;
1130 let mut rss: usize = 0;
1131 let mut rsslim: u64 = 0; let mut startcode: u64 = 0;
1133 let mut endcode: u64 = 0;
1134 let mut startstack: usize = 0;
1135 let mut kstkesp: u64 = 0;
1136 let mut kstkeip: u64 = 0; let signal: u64 = 0;
1138 let blocked: u64 = 0;
1139 let siginore: u64 = 0;
1140 let sigcatch: u64 = 0;
1141 let mut wchan: u64 = 0; let nswap: u64 = 0;
1143 let cnswap: u64 = 0;
1144 let exit_signal: i32 = 0;
1145 let processor: i32 = 0;
1146 let rt_priority: u32 = 0; let policy: u32 = 0;
1148 let delayacct_blkio_ticks: u64 = 0;
1149 let guest_time: u64 = 0;
1150 let cguest_time: i64 = 0;
1151 let mut start_data: u64 = 0; let mut end_data: u64 = 0;
1153 let mut start_brk: u64 = 0;
1154 let mut arg_start: usize = 0;
1155 let mut arg_end: usize = 0;
1156 let mut env_start: usize = 0; let mut env_end: usize = 0;
1158 let mut exit_code: i32 = 0;
1159
1160 pid = task.get_tid();
1161 comm = task.command();
1162 state = task.state_code().code_char();
1163 nice = task.read().scheduler_state.normal_priority().as_nice() as i64;
1164
1165 {
1166 let thread_group = task.thread_group().read();
1167 ppid = thread_group.get_ppid();
1168 pgrp = thread_group.process_group.leader;
1169 session = thread_group.process_group.session.leader;
1170
1171 {
1173 let session = thread_group.process_group.session.read();
1174 tty_nr = session
1175 .controlling_terminal
1176 .as_ref()
1177 .map(|t| t.terminal.device().bits())
1178 .unwrap_or(0) as i32;
1179 }
1180
1181 cutime = duration_to_scheduler_clock(thread_group.children_time_stats.user_time);
1182 cstime = duration_to_scheduler_clock(thread_group.children_time_stats.system_time);
1183
1184 num_threads = thread_group.tasks_count() as i64;
1185 }
1186
1187 let time_stats = match self.scope {
1188 TaskEntryScope::Task => task.time_stats(),
1189 TaskEntryScope::ThreadGroup => task.thread_group().time_stats(),
1190 };
1191 utime = duration_to_scheduler_clock(time_stats.user_time);
1192 stime = duration_to_scheduler_clock(time_stats.system_time);
1193
1194 if let Ok(info) = task.thread_group().process.info() {
1195 starttime =
1196 duration_to_scheduler_clock(info.start_time - zx::MonotonicInstant::ZERO) as u64;
1197 }
1198
1199 if let Ok(mm) = task.mm() {
1200 let mem_stats = mm.get_stats(current_task);
1201 let page_size = *PAGE_SIZE as usize;
1202 vsize = mem_stats.vm_size;
1203 rss = mem_stats.vm_rss / page_size;
1204 rsslim = task.thread_group().limits.lock(locked).get(Resource::RSS).rlim_max;
1205
1206 {
1207 let mm_state = mm.state.read();
1208 startstack = mm_state.stack_start.ptr();
1209 arg_start = mm_state.argv_start.ptr();
1210 arg_end = mm_state.argv_end.ptr();
1211 env_start = mm_state.environ_start.ptr();
1212 env_end = mm_state.environ_end.ptr();
1213 }
1214 }
1215
1216 if !current_task
1220 .check_ptrace_access_mode(locked, PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT, &task)
1221 .is_ok()
1222 {
1223 startcode = 1;
1224 endcode = 1;
1225 startstack = 0;
1226 kstkesp = 0;
1227 kstkeip = 0;
1228 wchan = 0;
1229 start_data = 0;
1230 end_data = 0;
1231 start_brk = 0;
1232 arg_start = 0;
1233 arg_end = 0;
1234 env_start = 0;
1235 env_end = 0;
1236 exit_code = 0;
1237 }
1238
1239 writeln!(
1240 sink,
1241 "{pid} ({comm}) {state} {ppid} {pgrp} {session} {tty_nr} {tpgid} {flags} {minflt} {cminflt} {majflt} {cmajflt} {utime} {stime} {cutime} {cstime} {priority} {nice} {num_threads} {itrealvalue} {starttime} {vsize} {rss} {rsslim} {startcode} {endcode} {startstack} {kstkesp} {kstkeip} {signal} {blocked} {siginore} {sigcatch} {wchan} {nswap} {cnswap} {exit_signal} {processor} {rt_priority} {policy} {delayacct_blkio_ticks} {guest_time} {cguest_time} {start_data} {end_data} {start_brk} {arg_start} {arg_end} {env_start} {env_end} {exit_code}"
1242 )?;
1243
1244 Ok(())
1245 }
1246}
1247
1248#[derive(Clone)]
1249pub struct StatmFile {
1250 task: Weak<Task>,
1251}
1252impl StatmFile {
1253 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1254 DynamicFile::new_node(Self { task })
1255 }
1256}
1257impl DynamicFileSource for StatmFile {
1258 fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1259 let task = Task::from_weak(&self.task)?;
1261 let mem_stats = match task.mm() {
1262 Ok(mm) => mm.get_stats(current_task),
1263 Err(_) => Default::default(),
1264 };
1265 let page_size = *PAGE_SIZE as usize;
1266
1267 writeln!(
1269 sink,
1270 "{} {} {} {} 0 {} 0",
1271 mem_stats.vm_size / page_size,
1272 mem_stats.vm_rss / page_size,
1273 mem_stats.rss_shared / page_size,
1274 mem_stats.vm_exe / page_size,
1275 (mem_stats.vm_data + mem_stats.vm_stack) / page_size
1276 )?;
1277 Ok(())
1278 }
1279}
1280
1281#[derive(Clone)]
1282pub struct StatusFile(Weak<Task>);
1283impl StatusFile {
1284 pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1285 DynamicFile::new_node(Self(task))
1286 }
1287}
1288impl DynamicFileSource for StatusFile {
1289 fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1290 let task = &self.0.upgrade();
1291 let (tgid, pid, creds_string) = {
1292 if let Some(task) = task {
1293 track_stub!(TODO("https://fxbug.dev/297440106"), "/proc/pid/status zombies");
1294 write!(sink, "Name:\t")?;
1298 sink.write(task.persistent_info.command_guard().comm_name());
1299 let creds = task.persistent_info.real_creds();
1300 (
1301 Some(task.persistent_info.pid()),
1302 Some(task.persistent_info.tid()),
1303 Some(format!(
1304 "Uid:\t{}\t{}\t{}\t{}\nGid:\t{}\t{}\t{}\t{}\nGroups:\t{}",
1305 creds.uid,
1306 creds.euid,
1307 creds.saved_uid,
1308 creds.fsuid,
1309 creds.gid,
1310 creds.egid,
1311 creds.saved_gid,
1312 creds.fsgid,
1313 creds.groups.iter().map(|n| n.to_string()).join(" ")
1314 )),
1315 )
1316 } else {
1317 (None, None, None)
1318 }
1319 };
1320
1321 writeln!(sink)?;
1322
1323 if let Some(task) = task {
1324 if let Ok(running_state) = task.running_state() {
1325 writeln!(sink, "Umask:\t0{:03o}", running_state.fs().umask().bits())?;
1326 }
1327 let task_state = task.read();
1328 writeln!(sink, "SigBlk:\t{:016x}", task_state.signal_mask().0)?;
1329 writeln!(sink, "SigPnd:\t{:016x}", task_state.task_specific_pending_signals().0)?;
1330 writeln!(
1331 sink,
1332 "ShdPnd:\t{:x}",
1333 task.thread_group().pending_signals.lock().pending().0
1334 )?;
1335 writeln!(sink, "NoNewPrivs:\t{}", task_state.no_new_privs() as u8)?;
1336
1337 let creds = task.real_creds();
1339 let cap_mask = Capabilities::all_existent();
1340 writeln!(sink, "CapInh:\t{:016x}", creds.cap_inheritable & cap_mask)?;
1341 writeln!(sink, "CapPrm:\t{:016x}", creds.cap_permitted & cap_mask)?;
1342 writeln!(sink, "CapEff:\t{:016x}", creds.cap_effective & cap_mask)?;
1343 writeln!(sink, "CapBnd:\t{:016x}", creds.cap_bounding & cap_mask)?;
1344 writeln!(sink, "CapAmb:\t{:016x}", creds.cap_ambient & cap_mask)?;
1345 }
1346
1347 let state_code =
1348 if let Some(task) = task { task.state_code() } else { TaskStateCode::Zombie };
1349 writeln!(sink, "State:\t{} ({})", state_code.code_char(), state_code.name())?;
1350
1351 if let Some(tgid) = tgid {
1352 writeln!(sink, "Tgid:\t{}", tgid)?;
1353 }
1354 if let Some(pid) = pid {
1355 writeln!(sink, "Pid:\t{}", pid)?;
1356 }
1357 let (ppid, threads, tracer_pid) = if let Some(task) = task {
1358 let tracer_pid = task.read().ptrace.as_ref().map_or(0, |p| p.get_pid());
1359 let task_group = task.thread_group().read();
1360 (task_group.get_ppid(), task_group.tasks_count(), tracer_pid)
1361 } else {
1362 (1, 1, 0)
1363 };
1364 writeln!(sink, "PPid:\t{}", ppid)?;
1365 writeln!(sink, "TracerPid:\t{}", tracer_pid)?;
1366
1367 if let Some(creds_string) = creds_string {
1368 writeln!(sink, "{}", creds_string)?;
1369 }
1370
1371 if let Some(task) = task {
1372 if let Ok(mm) = task.mm() {
1373 let mem_stats = mm.get_stats(current_task);
1374 writeln!(sink, "VmSize:\t{} kB", mem_stats.vm_size / 1024)?;
1375 writeln!(sink, "VmLck:\t{} kB", mem_stats.vm_lck / 1024)?;
1376 writeln!(sink, "VmRSS:\t{} kB", mem_stats.vm_rss / 1024)?;
1377 writeln!(sink, "RssAnon:\t{} kB", mem_stats.rss_anonymous / 1024)?;
1378 writeln!(sink, "RssFile:\t{} kB", mem_stats.rss_file / 1024)?;
1379 writeln!(sink, "RssShmem:\t{} kB", mem_stats.rss_shared / 1024)?;
1380 writeln!(sink, "VmData:\t{} kB", mem_stats.vm_data / 1024)?;
1381 writeln!(sink, "VmStk:\t{} kB", mem_stats.vm_stack / 1024)?;
1382 writeln!(sink, "VmExe:\t{} kB", mem_stats.vm_exe / 1024)?;
1383 writeln!(sink, "VmSwap:\t{} kB", mem_stats.vm_swap / 1024)?;
1384 writeln!(sink, "VmHWM:\t{} kB", mem_stats.vm_rss_hwm / 1024)?;
1385 }
1386 let seccomp = task.seccomp_filter_state.get() as u8;
1388 writeln!(sink, "Seccomp:\t{}", seccomp)?;
1389 }
1390
1391 writeln!(sink, "Threads:\t{}", std::cmp::max(1, threads))?;
1393
1394 Ok(())
1395 }
1396}
1397
1398struct OomScoreFile(Weak<Task>);
1399
1400impl OomScoreFile {
1401 fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1402 BytesFile::new_node(Self(task))
1403 }
1404}
1405
1406impl BytesFileOps for OomScoreFile {
1407 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1408 let _task = Task::from_weak(&self.0)?;
1409 track_stub!(TODO("https://fxbug.dev/322873459"), "/proc/pid/oom_score");
1410 Ok(serialize_for_file(0).into())
1411 }
1412}
1413
1414const OOM_ADJUST_MAX: i32 = uapi::OOM_ADJUST_MAX as i32;
1416const OOM_SCORE_ADJ_MAX: i32 = uapi::OOM_SCORE_ADJ_MAX as i32;
1417
1418struct OomAdjFile(Weak<Task>);
1419impl OomAdjFile {
1420 fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1421 BytesFile::new_node(Self(task))
1422 }
1423}
1424
1425impl BytesFileOps for OomAdjFile {
1426 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1427 let value = parse_i32_file(&data)?;
1428 let oom_score_adj = if value == OOM_DISABLE {
1429 OOM_SCORE_ADJ_MIN
1430 } else {
1431 if !(OOM_ADJUST_MIN..=OOM_ADJUST_MAX).contains(&value) {
1432 return error!(EINVAL);
1433 }
1434 let fraction = (value - OOM_ADJUST_MIN) / (OOM_ADJUST_MAX - OOM_ADJUST_MIN);
1435 fraction * (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN) + OOM_SCORE_ADJ_MIN
1436 };
1437 security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1438 let task = Task::from_weak(&self.0)?;
1439 task.write().oom_score_adj = oom_score_adj;
1440 Ok(())
1441 }
1442
1443 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1444 let task = Task::from_weak(&self.0)?;
1445 let oom_score_adj = task.read().oom_score_adj;
1446 let oom_adj = if oom_score_adj == OOM_SCORE_ADJ_MIN {
1447 OOM_DISABLE
1448 } else {
1449 let fraction =
1450 (oom_score_adj - OOM_SCORE_ADJ_MIN) / (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN);
1451 fraction * (OOM_ADJUST_MAX - OOM_ADJUST_MIN) + OOM_ADJUST_MIN
1452 };
1453 Ok(serialize_for_file(oom_adj).into())
1454 }
1455}
1456
1457struct OomScoreAdjFile(Weak<Task>);
1458
1459impl OomScoreAdjFile {
1460 fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1461 BytesFile::new_node(Self(task))
1462 }
1463}
1464
1465impl BytesFileOps for OomScoreAdjFile {
1466 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1467 let value = parse_i32_file(&data)?;
1468 if !(OOM_SCORE_ADJ_MIN..=OOM_SCORE_ADJ_MAX).contains(&value) {
1469 return error!(EINVAL);
1470 }
1471 security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1472 let task = Task::from_weak(&self.0)?;
1473 task.write().oom_score_adj = value;
1474 Ok(())
1475 }
1476
1477 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1478 let task = Task::from_weak(&self.0)?;
1479 let oom_score_adj = task.read().oom_score_adj;
1480 Ok(serialize_for_file(oom_score_adj).into())
1481 }
1482}
1483
1484struct TimerslackNsFile(Weak<Task>);
1485
1486impl TimerslackNsFile {
1487 fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1488 BytesFile::new_node(Self(task))
1489 }
1490}
1491
1492impl BytesFileOps for TimerslackNsFile {
1493 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1494 let target_task = Task::from_weak(&self.0)?;
1495 let same_task =
1496 current_task.task.thread_group().leader == target_task.thread_group().leader;
1497 if !same_task {
1498 security::check_task_capable(current_task, CAP_SYS_NICE)?;
1499 security::check_setsched_access(current_task, &target_task)?;
1500 };
1501
1502 let value = parse_unsigned_file(&data)?;
1503 target_task.write().set_timerslack_ns(value);
1504 Ok(())
1505 }
1506
1507 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1508 let target_task = Task::from_weak(&self.0)?;
1509 let same_task =
1510 current_task.task.thread_group().leader == target_task.thread_group().leader;
1511 if !same_task {
1512 security::check_task_capable(current_task, CAP_SYS_NICE)?;
1513 security::check_getsched_access(current_task, &target_task)?;
1514 };
1515
1516 let timerslack_ns = target_task.read().timerslack_ns;
1517 Ok(serialize_for_file(timerslack_ns).into())
1518 }
1519}
1520
1521struct ClearRefsFile(Weak<Task>);
1522
1523impl ClearRefsFile {
1524 fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1525 BytesFile::new_node(Self(task))
1526 }
1527}
1528
1529impl BytesFileOps for ClearRefsFile {
1530 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
1531 let _task = Task::from_weak(&self.0)?;
1532 track_stub!(TODO("https://fxbug.dev/396221597"), "/proc/pid/clear_refs");
1533 Ok(())
1534 }
1535}