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