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