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