1use crate::fs::{HidePid, ProcFs};
6use itertools::Itertools;
7use regex_lite::Regex;
8use starnix_core::mm::{
9 MemoryAccessor, MemoryAccessorExt, MemoryManager, MemoryStats, PAGE_SIZE, ProcMapsFile,
10 ProcPagemapFile, ProcSmapsFile, ProcSmapsRollupFile, SelfStatsCacheMode,
11};
12use starnix_core::security;
13use starnix_core::task::{
14 CurrentTask, Pid, Task, TaskContainer, TaskEntryScope, TaskStateCode, 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, CheckAccessReason, CloseFreeSafe, DirEntry, DirEntryOps,
27 DirectoryEntryType, DirentSink, FdFlags, FdNumber, FdTable, FileObject, FileOps,
28 FileSystemHandle, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
29 ProcMountinfoFile, ProcMountsFile, SeekTarget, StatxFlags, SymlinkTarget, default_seek,
30 emit_dotdot, fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
31 fileops_impl_unbounded_seek, fs_node_impl_dir_readonly, fs_node_impl_dir_readonly_ops,
32};
33use starnix_logging::{bug_ref, track_stub};
34use starnix_sync::{DynamicLockDepRwLock, LockDepReadGuard};
35
36use starnix_task_command::TaskCommand;
37use starnix_types::time::duration_to_scheduler_clock;
38use starnix_uapi::auth::{
39 CAP_SYS_NICE, CAP_SYS_RESOURCE, PTRACE_MODE_ATTACH_FSCREDS, PTRACE_MODE_NOAUDIT,
40 PTRACE_MODE_READ_FSCREDS, PtraceAccessMode,
41};
42use starnix_uapi::device_id::DeviceId;
43use starnix_uapi::errors::Errno;
44use starnix_uapi::file_mode::{Access, FileMode, mode};
45use starnix_uapi::open_flags::OpenFlags;
46use starnix_uapi::resource_limits::Resource;
47use starnix_uapi::user_address::UserAddress;
48use starnix_uapi::{
49 OOM_ADJUST_MIN, OOM_DISABLE, OOM_SCORE_ADJ_MIN, RLIM_INFINITY, errno, error, ino_t, off_t,
50 pid_t, uapi,
51};
52use std::borrow::Cow;
53use std::ops::{Deref, Range};
54use std::sync::{Arc, LazyLock, Weak};
55
56const TASK_ENTRIES: &[(&[u8], FileMode)] = &[
58 (b"cgroup", mode!(IFREG, 0o444)),
60 (b"cwd", mode!(IFLNK, 0o777)),
61 (b"exe", mode!(IFLNK, 0o777)),
62 (b"fd", mode!(IFDIR, 0o500)),
63 (b"fdinfo", mode!(IFDIR, 0o555)),
64 (b"io", mode!(IFREG, 0o400)),
65 (b"limits", mode!(IFREG, 0o444)),
66 (b"maps", mode!(IFREG, 0o444)),
67 (b"mem", mode!(IFREG, 0o600)),
68 (b"root", mode!(IFLNK, 0o777)),
69 (b"sched", mode!(IFREG, 0o644)),
70 (b"schedstat", mode!(IFREG, 0o444)),
71 (b"smaps", mode!(IFREG, 0o444)),
72 (b"smaps_rollup", mode!(IFREG, 0o444)),
73 (b"stat", mode!(IFREG, 0o444)),
74 (b"statm", mode!(IFREG, 0o444)),
75 (b"status", mode!(IFREG, 0o444)),
76 (b"cmdline", mode!(IFREG, 0o444)),
77 (b"environ", mode!(IFREG, 0o400)),
78 (b"auxv", mode!(IFREG, 0o400)),
79 (b"comm", mode!(IFREG, 0o644)),
80 (b"attr", mode!(IFDIR, 0o555)),
81 (b"ns", mode!(IFDIR, 0o511)),
82 (b"mountinfo", mode!(IFREG, 0o444)),
83 (b"mounts", mode!(IFREG, 0o444)),
84 (b"oom_adj", mode!(IFREG, 0o744)),
85 (b"oom_score", mode!(IFREG, 0o444)),
86 (b"oom_score_adj", mode!(IFREG, 0o744)),
87 (b"timerslack_ns", mode!(IFREG, 0o666)),
88 (b"wchan", mode!(IFREG, 0o444)),
89 (b"clear_refs", mode!(IFREG, 0o200)),
90 (b"pagemap", mode!(IFREG, 0o400)),
91 (b"task", mode!(IFDIR, 0o555)),
93];
94
95fn task_entries(scope: TaskEntryScope) -> &'static [(&'static [u8], FileMode)] {
97 match scope {
98 TaskEntryScope::Task => &TASK_ENTRIES[..TASK_ENTRIES.len() - 1],
99 TaskEntryScope::ThreadGroup => TASK_ENTRIES,
100 }
101}
102
103pub struct TaskDirectory {
111 target: TaskContainer,
112 inode_range: Range<ino_t>,
113}
114
115#[derive(Clone)]
116struct TaskDirectoryNode {
117 task_directory: Arc<TaskDirectory>,
118}
119
120impl Deref for TaskDirectoryNode {
121 type Target = TaskDirectory;
122
123 fn deref(&self) -> &Self::Target {
124 &self.task_directory
125 }
126}
127
128impl TaskDirectory {
129 fn new(fs: &FileSystemHandle, task: &Arc<Task>, target: TaskContainer) -> FsNodeHandle {
130 let creds = task.real_creds().euid_as_fscred();
131 let scope = target.scope;
132 fs.create_node_and_allocate_node_id(
133 TaskDirectoryNode {
134 task_directory: Arc::new(TaskDirectory {
135 target,
136 inode_range: fs.allocate_ino_range(task_entries(scope).len()),
137 }),
138 },
139 FsNodeInfo::new(mode!(IFDIR, 0o555), creds),
140 )
141 }
142}
143
144struct TaskDirectoryDirEntryOps {
145 target: TaskContainer,
146}
147
148impl DirEntryOps for TaskDirectoryDirEntryOps {
149 fn revalidate(&self, current_task: &CurrentTask, dir_entry: &DirEntry) -> Result<bool, Errno> {
150 let Ok(target_task) = self.target.get_task() else {
151 return Ok(false);
152 };
153 if let Some(proc_fs) = dir_entry.node.fs().downcast_ops::<ProcFs>() {
154 if !proc_fs.has_pid_permissions(current_task, &target_task, HidePid::Invisible) {
155 return error!(ENOENT);
156 }
157 }
158 Ok(true)
159 }
160}
161
162macro_rules! task_dir_impl_readonly {
163 () => {
164 fn create_dir_entry_ops(&self) -> Box<dyn DirEntryOps> {
165 Box::new(TaskDirectoryDirEntryOps { target: self.target.clone() })
166 }
167
168 fn check_access(
169 &self,
170 node: &FsNode,
171 current_task: &CurrentTask,
172 permission_flags: security::PermissionFlags,
173 info: &DynamicLockDepRwLock<FsNodeInfo>,
174 reason: CheckAccessReason,
175 audit_context: security::Auditable<'_>,
176 ) -> Result<(), Errno> {
177 if let Some(proc_fs) = node.fs().downcast_ops::<ProcFs>() {
178 proc_fs.check_pid_access(current_task, &self.target, HidePid::NoAccess)?;
179 }
180 if permission_flags.as_access().contains(Access::WRITE) {
181 return error!(EROFS, format!("check_access failed: read-only directory"));
182 }
183 node.default_check_access_impl(
184 current_task,
185 permission_flags,
186 reason,
187 info.read(),
188 audit_context,
189 )
190 }
191
192 fn fetch_and_refresh_info<'a>(
193 &self,
194 node: &FsNode,
195 current_task: &CurrentTask,
196 info: &'a DynamicLockDepRwLock<FsNodeInfo>,
197 _flags: StatxFlags,
198 ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
199 if let (Some(proc_fs), Ok(target_task)) =
200 (node.fs().downcast_ops::<ProcFs>(), self.target.get_task())
201 {
202 if !proc_fs.has_pid_permissions(current_task, &target_task, HidePid::Invisible) {
203 return error!(ENOENT);
204 }
205 }
206 Ok(info.read())
207 }
208
209 fs_node_impl_dir_readonly_ops!();
210 };
211}
212
213impl FsNodeOps for TaskDirectoryNode {
214 task_dir_impl_readonly!();
215
216 fn create_file_ops(
217 &self,
218 _node: &FsNode,
219 _current_task: &CurrentTask,
220 _flags: OpenFlags,
221 ) -> Result<Box<dyn FileOps>, Errno> {
222 Ok(Box::new(self.clone()))
223 }
224
225 fn lookup(
226 &self,
227 entry: &DirEntry,
228 _current_task: &CurrentTask,
229 name: &FsStr,
230 ) -> Result<FsNodeHandle, Errno> {
231 let _task = self.target.get_task()?;
232 let target = self.target.clone();
233 let creds = entry.node.info().cred();
234 let fs = entry.node.fs();
235 let (mode, ino) = task_entries(self.target.scope)
236 .iter()
237 .enumerate()
238 .find_map(|(index, (n, mode))| {
239 if name == *n {
240 Some((*mode, self.inode_range.start + index as ino_t))
241 } else {
242 None
243 }
244 })
245 .ok_or_else(|| errno!(ENOENT))?;
246
247 let ops: Box<dyn FsNodeOps> = match &**name {
249 b"cgroup" => Box::new(CgroupFile::new_node(target)),
250 b"cwd" => Box::new(CallbackSymlinkNode::new(move || {
251 Ok(SymlinkTarget::Node(target.get_task()?.fs()?.cwd()))
252 })),
253 b"exe" => Box::new(CallbackSymlinkNode::new(move || {
254 let task = target.get_task()?;
255 if let Some(node) = task.mm().ok().and_then(|mm| mm.executable_node()) {
256 Ok(SymlinkTarget::Node(node))
257 } else {
258 error!(ENOENT)
259 }
260 })),
261 b"fd" => Box::new(FdDirectory::new(target)),
262 b"fdinfo" => Box::new(FdInfoDirectory::new(target)),
263 b"io" => Box::new(IoFile::new_node()),
264 b"limits" => Box::new(LimitsFile::new_node(target)),
265 b"maps" => {
266 let target_for_file = target.clone();
267 Box::new(PtraceCheckedNode::new_node(
268 target,
269 PTRACE_MODE_READ_FSCREDS,
270 move |task| Ok(ProcMapsFile::new(task, target_for_file.clone())),
271 ))
272 }
273 b"mem" => Box::new(MemFile::new_node(target)),
274 b"root" => Box::new(CallbackSymlinkNode::new(move || {
275 Ok(SymlinkTarget::Node(target.get_task()?.fs()?.root()))
276 })),
277 b"sched" => Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322893980"))),
278 b"schedstat" => {
279 Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322894256")))
280 }
281 b"smaps" => {
282 let target_for_file = target.clone();
283 Box::new(PtraceCheckedNode::new_node(
284 target,
285 PTRACE_MODE_READ_FSCREDS,
286 move |task| Ok(ProcSmapsFile::new(task, target_for_file.clone())),
287 ))
288 }
289 b"smaps_rollup" => {
290 let target_for_file = target.clone();
291 Box::new(PtraceCheckedNode::new_node(
292 target,
293 PTRACE_MODE_READ_FSCREDS,
294 move |task| Ok(ProcSmapsRollupFile::new(task, target_for_file.clone())),
295 ))
296 }
297 b"stat" => Box::new(StatFile::new_node(target)),
298 b"statm" => Box::new(StatmFile::new_node(target)),
299 b"status" => Box::new(StatusFile::new_node(target)),
300 b"cmdline" => Box::new(CmdlineFile::new_node(target)),
301 b"environ" => Box::new(EnvironFile::new_node(target)),
302 b"auxv" => Box::new(AuxvFile::new_node(target)),
303 b"comm" => Box::new(CommFile::new_node(target)),
304 b"attr" => {
305 let dir = SimpleDirectory::new();
306 dir.edit(&fs, |dir| {
307 for (attr, name) in [
308 (security::ProcAttr::Current, "current"),
309 (security::ProcAttr::Exec, "exec"),
310 (security::ProcAttr::FsCreate, "fscreate"),
311 (security::ProcAttr::KeyCreate, "keycreate"),
312 (security::ProcAttr::SockCreate, "sockcreate"),
313 ] {
314 dir.entry_etc(
315 name.into(),
316 AttrNode::new(target.clone(), attr),
317 mode!(IFREG, 0o666),
318 DeviceId::NONE,
319 creds,
320 );
321 }
322 dir.entry_etc(
323 "prev".into(),
324 AttrNode::new(target, security::ProcAttr::Previous),
325 mode!(IFREG, 0o444),
326 DeviceId::NONE,
327 creds,
328 );
329 });
330 Box::new(dir)
331 }
332 b"ns" => Box::new(NsDirectory::new(target)),
333 b"mountinfo" => Box::new(ProcMountinfoFile::new_node(target)),
334 b"mounts" => Box::new(ProcMountsFile::new_node(target)),
335 b"oom_adj" => Box::new(OomAdjFile::new_node(target)),
336 b"oom_score" => Box::new(OomScoreFile::new_node(target)),
337 b"oom_score_adj" => Box::new(OomScoreAdjFile::new_node(target)),
338 b"timerslack_ns" => Box::new(TimerslackNsFile::new_node(target)),
339 b"wchan" => Box::new(BytesFile::new_node(b"0".to_vec())),
340 b"clear_refs" => Box::new(ClearRefsFile::new_node(target)),
341 b"pagemap" => Box::new(PtraceCheckedNode::new_node_with_current_task(
342 target,
343 PTRACE_MODE_READ_FSCREDS,
344 |current_task, task| Ok(ProcPagemapFile::new(current_task, &task)),
345 )),
346 b"task" => Box::new(TaskListDirectory::new_node(target.pid)),
347 name => unreachable!(
348 "entry \"{:?}\" should be supported to keep in sync with task_entries()",
349 name
350 ),
351 };
352
353 Ok(fs.create_node(ino, ops, FsNodeInfo::new(mode, creds)))
354 }
355}
356
357impl CloseFreeSafe for TaskDirectory {}
359impl FileOps for TaskDirectory {
360 fileops_impl_directory!();
361 fileops_impl_noop_sync!();
362 fileops_impl_unbounded_seek!();
363
364 fn readdir(
365 &self,
366 file: &FileObject,
367 _current_task: &CurrentTask,
368 sink: &mut dyn DirentSink,
369 ) -> Result<(), Errno> {
370 let _task = self.target.get_task().map_err(|_| errno!(ENOENT))?;
371 emit_dotdot(file, sink)?;
372
373 for (index, (name, mode)) in
376 task_entries(self.target.scope).iter().enumerate().skip(sink.offset() as usize - 2)
377 {
378 sink.add(
379 self.inode_range.start + index as ino_t,
380 sink.offset() + 1,
381 DirectoryEntryType::from_mode(*mode),
382 (*name).into(),
383 )?;
384 }
385 Ok(())
386 }
387
388 fn as_pid(&self, _file: &FileObject) -> Result<Pid, Errno> {
389 Ok(self.target.pid.clone())
390 }
391}
392
393pub fn pid_directory(
395 current_task: &CurrentTask,
396 fs: &FileSystemHandle,
397 task: &Arc<Task>,
398 tid: Pid,
399) -> FsNodeHandle {
400 let fs_node =
403 TaskDirectory::new(fs, task, TaskContainer::from_thread_group(task.pid.clone(), tid));
404
405 security::task_to_fs_node(current_task, task, &fs_node);
406 fs_node
407}
408
409fn tid_directory(fs: &FileSystemHandle, task: &Arc<Task>) -> FsNodeHandle {
411 TaskDirectory::new(fs, task, TaskContainer::from_task(task))
412}
413
414struct FdDirectory {
419 target: TaskContainer,
420}
421
422impl FdDirectory {
423 fn new(target: TaskContainer) -> Self {
424 Self { target }
425 }
426}
427
428impl FsNodeOps for FdDirectory {
429 fs_node_impl_dir_readonly!();
430
431 fn create_file_ops(
432 &self,
433 _node: &FsNode,
434 _current_task: &CurrentTask,
435 _flags: OpenFlags,
436 ) -> Result<Box<dyn FileOps>, Errno> {
437 let task = self.target.get_task()?;
438 let entries =
439 task.files().map_or_else(|_| Vec::new(), |files| fds_to_directory_entries(&files));
440 Ok(VecDirectory::new_file(entries))
441 }
442
443 fn lookup(
444 &self,
445 entry: &DirEntry,
446 _current_task: &CurrentTask,
447 name: &FsStr,
448 ) -> Result<FsNodeHandle, Errno> {
449 let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
450 let task = self.target.get_task()?;
451 let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
453 let mut access = Access::from_open_flags(file.flags());
457 if access.is_nontrivial() {
458 access |= Access::EXEC;
459 }
460 let mode = FileMode::IFLNK | access.user_mode();
461 let target = self.target.clone();
462 Ok(entry.node.fs().create_node_and_allocate_node_id(
463 CallbackSymlinkNode::new(move || {
464 let task = target.get_task()?;
465 let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
466 Ok(SymlinkTarget::Node(file.name.to_passive()))
467 }),
468 FsNodeInfo::new(mode, task.real_fscred()),
469 ))
470 }
471}
472
473const NS_ENTRIES: &[&str] = &[
474 "cgroup",
475 "ipc",
476 "mnt",
477 "net",
478 "pid",
479 "pid_for_children",
480 "time",
481 "time_for_children",
482 "user",
483 "uts",
484];
485
486struct AttrNode {
488 attr: security::ProcAttr,
489 target: TaskContainer,
490}
491
492impl AttrNode {
493 fn new(target: TaskContainer, attr: security::ProcAttr) -> impl FsNodeOps {
494 SimpleFileNode::new(move |_| Ok(AttrNode { attr, target: target.clone() }))
495 }
496}
497
498impl FileOps for AttrNode {
499 fileops_impl_seekable!();
500 fileops_impl_noop_sync!();
501
502 fn writes_update_seek_offset(&self) -> bool {
503 false
504 }
505
506 fn read(
507 &self,
508 _file: &FileObject,
509 current_task: &CurrentTask,
510 offset: usize,
511 data: &mut dyn OutputBuffer,
512 ) -> Result<usize, Errno> {
513 let task = self.target.get_task()?;
514 let response = security::get_procattr(current_task, &task, self.attr)?;
515 data.write(&response[offset..])
516 }
517
518 fn write(
519 &self,
520 _file: &FileObject,
521 current_task: &CurrentTask,
522 offset: usize,
523 data: &mut dyn InputBuffer,
524 ) -> Result<usize, Errno> {
525 let task = self.target.get_task()?;
526
527 if current_task.task != task {
529 return error!(EPERM);
530 }
531 if offset != 0 {
532 return error!(EINVAL);
533 }
534
535 let data = data.read_all()?;
536 let data_len = data.len();
537 security::set_procattr(current_task, self.attr, data.as_slice())?;
538 Ok(data_len)
539 }
540}
541
542struct NsDirectory {
544 target: TaskContainer,
545}
546
547impl NsDirectory {
548 fn new(target: TaskContainer) -> Self {
549 Self { target }
550 }
551}
552
553impl FsNodeOps for NsDirectory {
554 fs_node_impl_dir_readonly!();
555
556 fn create_file_ops(
557 &self,
558 _node: &FsNode,
559 _current_task: &CurrentTask,
560 _flags: OpenFlags,
561 ) -> Result<Box<dyn FileOps>, Errno> {
562 Ok(VecDirectory::new_file(
565 NS_ENTRIES
566 .iter()
567 .map(|&name| VecDirectoryEntry {
568 entry_type: DirectoryEntryType::LNK,
569 name: FsString::from(name),
570 inode: None,
571 })
572 .collect(),
573 ))
574 }
575
576 fn lookup(
577 &self,
578 entry: &DirEntry,
579 current_task: &CurrentTask,
580 name: &FsStr,
581 ) -> Result<FsNodeHandle, Errno> {
582 let name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
587 let mut elements = name.split(':');
588 let ns = elements.next().expect("name must not be empty");
589 if !NS_ENTRIES.contains(&ns) {
591 return error!(ENOENT);
592 }
593
594 let task = self.target.get_task()?;
595 if let Some(id) = elements.next() {
596 static NS_IDENTIFIER_RE: LazyLock<Regex> =
598 LazyLock::new(|| Regex::new("^\\[[0-9]+\\]$").unwrap());
599 if !NS_IDENTIFIER_RE.is_match(id) {
600 return error!(ENOENT);
601 }
602 let node_info = || FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred());
603 let fallback = || {
604 entry
605 .node
606 .fs()
607 .create_node_and_allocate_node_id(BytesFile::new_node(vec![]), node_info())
608 };
609 Ok(match ns {
610 "cgroup" => {
611 track_stub!(TODO("https://fxbug.dev/297313673"), "cgroup namespaces");
612 fallback()
613 }
614 "ipc" => {
615 track_stub!(TODO("https://fxbug.dev/297313673"), "ipc namespaces");
616 fallback()
617 }
618 "mnt" => entry
619 .node
620 .fs()
621 .create_node_and_allocate_node_id(current_task.fs().namespace(), node_info()),
622 "net" => {
623 track_stub!(TODO("https://fxbug.dev/297313673"), "net namespaces");
624 fallback()
625 }
626 "pid" => {
627 track_stub!(TODO("https://fxbug.dev/297313673"), "pid namespaces");
628 fallback()
629 }
630 "pid_for_children" => {
631 track_stub!(TODO("https://fxbug.dev/297313673"), "pid_for_children namespaces");
632 fallback()
633 }
634 "time" => {
635 track_stub!(TODO("https://fxbug.dev/297313673"), "time namespaces");
636 fallback()
637 }
638 "time_for_children" => {
639 track_stub!(
640 TODO("https://fxbug.dev/297313673"),
641 "time_for_children namespaces"
642 );
643 fallback()
644 }
645 "user" => {
646 track_stub!(TODO("https://fxbug.dev/297313673"), "user namespaces");
647 fallback()
648 }
649 "uts" => {
650 track_stub!(TODO("https://fxbug.dev/297313673"), "uts namespaces");
651 fallback()
652 }
653 _ => return error!(ENOENT),
654 })
655 } else {
656 let id = current_task.fs().namespace().id;
658 Ok(entry.node.fs().create_node_and_allocate_node_id(
659 CallbackSymlinkNode::new(move || {
660 Ok(SymlinkTarget::Path(format!("{name}:[{id}]").into()))
661 }),
662 FsNodeInfo::new(mode!(IFLNK, 0o777), task.real_fscred()),
663 ))
664 }
665 }
666}
667
668struct FdInfoDirectory {
674 target: TaskContainer,
675}
676
677impl FdInfoDirectory {
678 fn new(target: TaskContainer) -> Self {
679 Self { target }
680 }
681}
682
683impl FsNodeOps for FdInfoDirectory {
684 fs_node_impl_dir_readonly!();
685
686 fn create_file_ops(
687 &self,
688 _node: &FsNode,
689 current_task: &CurrentTask,
690 _flags: OpenFlags,
691 ) -> Result<Box<dyn FileOps>, Errno> {
692 let task = self.target.get_task()?;
693 current_task
694 .check_ptrace_access_mode(PTRACE_MODE_READ_FSCREDS, &task)
695 .map_err(|_| errno!(EACCES))?;
696
697 let entries =
698 task.files().map_or_else(|_| Vec::new(), |files| fds_to_directory_entries(&files));
699 Ok(VecDirectory::new_file(entries))
700 }
701
702 fn lookup(
703 &self,
704 entry: &DirEntry,
705 _current_task: &CurrentTask,
706 name: &FsStr,
707 ) -> Result<FsNodeHandle, Errno> {
708 let task = self.target.get_task()?;
709 let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
710 task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
714 Ok(entry.node.fs().create_node_and_allocate_node_id(
715 FdInfoFile::new_node(self.target.clone(), fd),
716 FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred()),
717 ))
718 }
719}
720
721#[derive(Clone)]
722struct FdInfoFile {
723 target: TaskContainer,
724 fd: FdNumber,
725}
726
727impl FdInfoFile {
728 fn new_node(target: TaskContainer, fd: FdNumber) -> impl FsNodeOps {
729 DynamicFile::new_node(Self { target, fd })
730 }
731}
732
733impl DynamicFileSource for FdInfoFile {
734 fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
735 let task = self.target.get_task().map_err(|_| errno!(ENOENT))?;
736 let (file, fd_flags) = task
737 .files()
738 .map_err(|_| errno!(ENOENT))?
739 .get_allowing_opath_with_flags(self.fd)
740 .map_err(|_| errno!(ENOENT))?;
741 let pos = file.offset.read();
742 let mut flags = file.flags();
743 flags.set(OpenFlags::CLOEXEC, fd_flags.contains(FdFlags::CLOEXEC));
744 writeln!(sink, "pos:\t{}\nflags:\t0{:o}", pos, flags.bits())?;
745 if let Some(extra_fdinfo) = file.extra_fdinfo(current_task) {
746 sink.write(extra_fdinfo.as_slice());
747 }
748 Ok(())
749 }
750}
751
752fn fds_to_directory_entries(files: &FdTable) -> Vec<VecDirectoryEntry> {
753 let mut buf = itoa::Buffer::new();
754 let mut entries = Vec::new();
755 files.for_each_fd(|fd| {
756 entries.push(VecDirectoryEntry {
757 entry_type: DirectoryEntryType::DIR,
758 name: buf.format(fd.raw()).as_bytes().into(),
759 inode: None,
760 });
761 });
762 entries
763}
764
765struct TaskListDirectory {
767 target: TaskContainer,
768}
769
770struct TaskListFile {
771 target: TaskContainer,
772 inner: Box<dyn FileOps>,
773}
774
775impl FileOps for TaskListFile {
776 fileops_impl_directory!();
777 fileops_impl_noop_sync!();
778 fileops_impl_unbounded_seek!();
779
780 fn readdir(
781 &self,
782 file: &FileObject,
783 current_task: &CurrentTask,
784 sink: &mut dyn DirentSink,
785 ) -> Result<(), Errno> {
786 let _task = self.target.get_task().map_err(|_| errno!(ENOENT))?;
787 self.inner.readdir(file, current_task, sink)
788 }
789}
790
791impl TaskListDirectory {
792 fn new_node(pid: Pid) -> impl FsNodeOps {
793 Self { target: TaskContainer::from_pid(pid) }
794 }
795}
796
797impl FsNodeOps for TaskListDirectory {
798 task_dir_impl_readonly!();
799
800 fn create_file_ops(
801 &self,
802 _node: &FsNode,
803 _current_task: &CurrentTask,
804 _flags: OpenFlags,
805 ) -> Result<Box<dyn FileOps>, Errno> {
806 let task = self.target.get_task().map_err(|_| errno!(ENOENT))?;
807 let mut buf = itoa::Buffer::new();
808 let inner = VecDirectory::new_file(
809 task.thread_group()
810 .read()
811 .task_ids()
812 .map(|tid| VecDirectoryEntry {
813 entry_type: DirectoryEntryType::DIR,
814 name: buf.format(tid).as_bytes().into(),
815 inode: None,
816 })
817 .collect(),
818 );
819 Ok(Box::new(TaskListFile { target: self.target.clone(), inner }))
820 }
821
822 fn lookup(
823 &self,
824 entry: &DirEntry,
825 current_task: &CurrentTask,
826 name: &FsStr,
827 ) -> Result<FsNodeHandle, Errno> {
828 let target_task = self.target.get_task().map_err(|_| errno!(ENOENT))?;
829 let tid = std::str::from_utf8(name)
830 .map_err(|_| errno!(ENOENT))?
831 .parse::<pid_t>()
832 .map_err(|_| errno!(ENOENT))?;
833
834 let task = current_task.get_task(tid).map_err(|_| errno!(ENOENT))?;
835 if task.pid != target_task.thread_group().leader {
837 return error!(ENOENT);
838 }
839 if let Some(proc_fs) = entry.node.fs().downcast_ops::<ProcFs>() {
840 if !proc_fs.has_pid_permissions(current_task, &task, HidePid::Invisible) {
841 return error!(ENOENT);
842 }
843 }
844
845 Ok(tid_directory(&entry.node.fs(), &task))
846 }
847}
848
849#[derive(Clone)]
850struct CgroupFile {
851 target: TaskContainer,
852}
853impl CgroupFile {
854 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
855 DynamicFile::new_node(Self { target })
856 }
857}
858impl DynamicFileSource for CgroupFile {
859 fn generate(
860 &self,
861 _current_task: &CurrentTask,
862 sink: &mut DynamicFileBuf,
863 ) -> Result<(), Errno> {
864 let task = self.target.get_task()?;
865 let cgroup1 = task.kernel().cgroups.cgroup1.lock();
866 for (key, root) in &cgroup1.hierarchies {
867 let mut parts: Vec<&str> = key.controllers.iter().map(|c| c.as_str()).collect();
868 let name_storage;
869 if let Some(name) = &key.name {
870 name_storage = format!("name={}", name);
871 parts.push(&name_storage);
872 }
873 let controller_str = parts.join(",");
874 let cgroup = root.get_cgroup(&task.pid);
875 let path = path_from_root(cgroup)?;
876 sink.write(format!("{}:{}:{}\n", root.hierarchy_id, controller_str, path).as_bytes());
877 }
878 let cgroup = task.kernel().cgroups.cgroup2.get_cgroup(&task.pid);
879 let path = path_from_root(cgroup)?;
880 sink.write(format!("0::{}\n", path).as_bytes());
881 Ok(())
882 }
883}
884
885fn fill_buf_from_addr_range(
886 task: &Task,
887 range_start: UserAddress,
888 range_end: UserAddress,
889 sink: &mut DynamicFileBuf,
890) -> Result<(), Errno> {
891 #[allow(clippy::manual_saturating_arithmetic)]
892 let len = range_end.ptr().checked_sub(range_start.ptr()).unwrap_or(0);
893 let buf = task.read_memory_partial_to_vec(range_start, len)?;
897 sink.write(&buf[..]);
898 Ok(())
899}
900
901#[derive(Clone)]
903pub struct CmdlineFile {
904 target: TaskContainer,
905}
906impl CmdlineFile {
907 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
908 DynamicFile::new_node(Self { target })
909 }
910}
911impl DynamicFileSource for CmdlineFile {
912 fn generate(
913 &self,
914 _current_task: &CurrentTask,
915 sink: &mut DynamicFileBuf,
916 ) -> Result<(), Errno> {
917 let Ok(task) = self.target.get_task() else {
919 return Ok(());
920 };
921 let Ok(mm) = task.mm() else {
923 return Ok(());
924 };
925 let (start, end) = {
926 let mm_state = mm.state.read();
927 (mm_state.argv_start, mm_state.argv_end)
928 };
929 fill_buf_from_addr_range(&task, start, end, sink)
930 }
931}
932
933struct PtraceCheckedNode {}
934
935impl PtraceCheckedNode {
936 pub fn new_node<F, O>(
937 target: TaskContainer,
938 mode: PtraceAccessMode,
939 create_ops: F,
940 ) -> impl FsNodeOps
941 where
942 F: Fn(Arc<Task>) -> Result<O, Errno> + Send + Sync + 'static,
943 O: FileOps,
944 {
945 Self::new_node_with_current_task(target, mode, move |_current_task, task| create_ops(task))
946 }
947
948 pub fn new_node_with_current_task<F, O>(
949 target: TaskContainer,
950 mode: PtraceAccessMode,
951 create_ops: F,
952 ) -> impl FsNodeOps
953 where
954 F: Fn(&CurrentTask, Arc<Task>) -> Result<O, Errno> + Send + Sync + 'static,
955 O: FileOps,
956 {
957 SimpleFileNode::new(move |current_task: &CurrentTask| {
958 let task = target.get_task()?;
959 if task.mm().is_ok() || task.state_code() == TaskStateCode::Zombie {
961 current_task.check_ptrace_access_mode(mode, &task).map_err(|_| errno!(EACCES))?;
962 }
963 create_ops(current_task, task)
964 })
965 }
966}
967
968#[derive(Clone)]
970pub struct EnvironFile {
971 target: TaskContainer,
972}
973impl EnvironFile {
974 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
975 let target_for_file = target.clone();
976 PtraceCheckedNode::new_node(target, PTRACE_MODE_READ_FSCREDS, move |_| {
977 Ok(DynamicFile::new(Self { target: target_for_file.clone() }))
978 })
979 }
980}
981impl DynamicFileSource for EnvironFile {
982 fn generate(
983 &self,
984 _current_task: &CurrentTask,
985 sink: &mut DynamicFileBuf,
986 ) -> Result<(), Errno> {
987 let task = self.target.get_task()?;
988 let Ok(mm) = task.mm() else {
990 return Ok(());
991 };
992 let (start, end) = {
993 let mm_state = mm.state.read();
994 (mm_state.environ_start, mm_state.environ_end)
995 };
996 fill_buf_from_addr_range(&task, start, end, sink)
997 }
998}
999
1000#[derive(Clone)]
1002pub struct AuxvFile {
1003 target: TaskContainer,
1004}
1005impl AuxvFile {
1006 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1007 let target_for_file = target.clone();
1008 PtraceCheckedNode::new_node(target, PTRACE_MODE_READ_FSCREDS, move |_| {
1009 Ok(DynamicFile::new(Self { target: target_for_file.clone() }))
1010 })
1011 }
1012}
1013impl DynamicFileSource for AuxvFile {
1014 fn generate(
1015 &self,
1016 _current_task: &CurrentTask,
1017 sink: &mut DynamicFileBuf,
1018 ) -> Result<(), Errno> {
1019 let task = self.target.get_task()?;
1020 let Ok(mm) = task.mm() else {
1022 return Ok(());
1023 };
1024 let (start, end) = {
1025 let mm_state = mm.state.read();
1026 (mm_state.auxv_start, mm_state.auxv_end)
1027 };
1028 fill_buf_from_addr_range(&task, start, end, sink)
1029 }
1030}
1031
1032#[derive(Clone)]
1034pub struct CommFile {
1035 target: TaskContainer,
1036}
1037impl CommFile {
1038 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1039 DynamicFile::new_node(Self { target })
1040 }
1041}
1042
1043impl DynamicFileSource for CommFile {
1044 fn generate(
1045 &self,
1046 _current_task: &CurrentTask,
1047 sink: &mut DynamicFileBuf,
1048 ) -> Result<(), Errno> {
1049 let task = self.target.get_task()?;
1050 sink.write(task.persistent_info.command_guard().comm_name());
1051 sink.write(b"\n");
1052 Ok(())
1053 }
1054
1055 fn write(
1056 &self,
1057 current_task: &CurrentTask,
1058 _offset: usize,
1059 data: &mut dyn InputBuffer,
1060 ) -> Result<usize, Errno> {
1061 let task = self.target.get_task()?;
1062 if !Arc::ptr_eq(&task.thread_group(), ¤t_task.thread_group()) {
1063 return error!(EINVAL);
1064 }
1065 let bytes = data.read_all()?;
1068 task.set_command_name(TaskCommand::new(&bytes));
1069 Ok(bytes.len())
1070 }
1071}
1072
1073#[derive(Clone)]
1075pub struct IoFile {}
1076impl IoFile {
1077 pub fn new_node() -> impl FsNodeOps {
1078 DynamicFile::new_node(Self {})
1079 }
1080}
1081impl DynamicFileSource for IoFile {
1082 fn generate(
1083 &self,
1084 _current_task: &CurrentTask,
1085 sink: &mut DynamicFileBuf,
1086 ) -> Result<(), Errno> {
1087 track_stub!(TODO("https://fxbug.dev/322874250"), "/proc/pid/io");
1088 sink.write(b"rchar: 0\n");
1089 sink.write(b"wchar: 0\n");
1090 sink.write(b"syscr: 0\n");
1091 sink.write(b"syscw: 0\n");
1092 sink.write(b"read_bytes: 0\n");
1093 sink.write(b"write_bytes: 0\n");
1094 sink.write(b"cancelled_write_bytes: 0\n");
1095 Ok(())
1096 }
1097}
1098
1099#[derive(Clone)]
1101pub struct LimitsFile {
1102 target: TaskContainer,
1103}
1104impl LimitsFile {
1105 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1106 DynamicFile::new_node(Self { target })
1107 }
1108}
1109impl DynamicFileSource for LimitsFile {
1110 fn generate_locked(
1111 &self,
1112 _current_task: &CurrentTask,
1113 sink: &mut DynamicFileBuf,
1114 ) -> Result<(), Errno> {
1115 let task = self.target.get_task()?;
1116 let limits = task.thread_group().limits.lock();
1117
1118 let write_limit = |sink: &mut DynamicFileBuf, value| {
1119 if value == RLIM_INFINITY as u64 {
1120 sink.write(format!("{:<20}", "unlimited").as_bytes());
1121 } else {
1122 sink.write(format!("{:<20}", value).as_bytes());
1123 }
1124 };
1125 sink.write(
1126 format!("{:<25}{:<20}{:<20}{:<10}\n", "Limit", "Soft Limit", "Hard Limit", "Units")
1127 .as_bytes(),
1128 );
1129 for resource in Resource::ALL {
1130 let desc = resource.desc();
1131 let limit = limits.get(resource);
1132 sink.write(format!("{:<25}", desc.name).as_bytes());
1133 write_limit(sink, limit.rlim_cur);
1134 write_limit(sink, limit.rlim_max);
1135 if !desc.unit.is_empty() {
1136 sink.write(format!("{:<10}", desc.unit).as_bytes());
1137 }
1138 sink.write(b"\n");
1139 }
1140 Ok(())
1141 }
1142}
1143
1144pub struct MemFile {
1146 mm: Weak<MemoryManager>,
1147
1148 target: TaskContainer,
1152}
1153
1154impl MemFile {
1155 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1156 let target_for_file = target.clone();
1157 PtraceCheckedNode::new_node(target, PTRACE_MODE_ATTACH_FSCREDS, move |task| {
1158 let mm = task.mm().ok().as_ref().map(Arc::downgrade).unwrap_or_default();
1159 Ok(Self { mm, target: target_for_file.clone() })
1160 })
1161 }
1162}
1163
1164impl FileOps for MemFile {
1165 fileops_impl_noop_sync!();
1166
1167 fn is_seekable(&self) -> bool {
1168 true
1169 }
1170
1171 fn seek(
1172 &self,
1173 _file: &FileObject,
1174 _current_task: &CurrentTask,
1175 current_offset: off_t,
1176 target: SeekTarget,
1177 ) -> Result<off_t, Errno> {
1178 default_seek(current_offset, target, || error!(EINVAL))
1179 }
1180
1181 fn read(
1182 &self,
1183 _file: &FileObject,
1184 current_task: &CurrentTask,
1185 offset: usize,
1186 data: &mut dyn OutputBuffer,
1187 ) -> Result<usize, Errno> {
1188 let Ok(_task) = self.target.get_task() else {
1189 return Ok(0);
1190 };
1191 let Some(mm) = self.mm.upgrade() else {
1192 return Ok(0);
1193 };
1194 let mut addr = UserAddress::from(offset as u64);
1195 data.write_each(&mut |bytes| {
1196 let read_bytes = if current_task.has_same_address_space(Some(&mm)) {
1197 current_task.read_memory_partial(addr, bytes)
1198 } else {
1199 mm.syscall_read_memory_partial(addr, bytes)
1200 }
1201 .map_err(|_| errno!(EIO))?;
1202 let actual = read_bytes.len();
1203 addr = (addr + actual)?;
1204 Ok(actual)
1205 })
1206 }
1207
1208 fn write(
1209 &self,
1210 _file: &FileObject,
1211 current_task: &CurrentTask,
1212 offset: usize,
1213 data: &mut dyn InputBuffer,
1214 ) -> Result<usize, Errno> {
1215 let Ok(_task) = self.target.get_task() else {
1216 return Ok(0);
1217 };
1218 let Some(mm) = self.mm.upgrade() else {
1219 return Ok(0);
1220 };
1221 let addr = UserAddress::from(offset as u64);
1222 let mut written = 0;
1223 let result = data.peek_each(&mut |bytes| {
1224 let actual = if current_task.has_same_address_space(Some(&mm)) {
1225 current_task.write_memory_partial((addr + written)?, bytes)
1226 } else {
1227 mm.syscall_write_memory_partial((addr + written)?, bytes)
1228 }
1229 .map_err(|_| errno!(EIO))?;
1230 written += actual;
1231 Ok(actual)
1232 });
1233 data.advance(written)?;
1234 result
1235 }
1236}
1237
1238const STUBBED_MEM_BYTES: usize = 4096;
1239
1240fn should_skip_memory_stats(current_task: &CurrentTask) -> bool {
1245 let comm = current_task.persistent_info.command_guard();
1246 let name = comm.comm_name();
1247 name == b"traced_probes" || name == b"lmkd"
1248}
1249fn stub_memory_stats() -> MemoryStats {
1250 MemoryStats {
1251 vm_size: STUBBED_MEM_BYTES,
1252 vm_rss: STUBBED_MEM_BYTES,
1253 vm_rss_hwm: STUBBED_MEM_BYTES,
1254 rss_anonymous: STUBBED_MEM_BYTES,
1255 rss_file: 0,
1256 rss_shared: 0,
1257 vm_data: 0,
1258 vm_stack: STUBBED_MEM_BYTES,
1259 vm_exe: STUBBED_MEM_BYTES,
1260 vm_swap: 0,
1261 vm_lck: 0,
1262 }
1263}
1264
1265#[derive(Clone)]
1266pub struct StatFile {
1267 target: TaskContainer,
1268}
1269
1270impl StatFile {
1271 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1272 DynamicFile::new_node(Self { target })
1273 }
1274}
1275impl DynamicFileSource for StatFile {
1276 fn generate_locked(
1277 &self,
1278 current_task: &CurrentTask,
1279 sink: &mut DynamicFileBuf,
1280 ) -> Result<(), Errno> {
1281 let task = self.target.get_task()?;
1282
1283 let pid: pid_t; let comm: TaskCommand;
1287 let state: char;
1288 let ppid: pid_t;
1289 let pgrp: pid_t; let session: pid_t;
1291 let tty_nr: i32;
1292 let tpgid: i32 = 0;
1293 let flags: u32 = 0;
1294 let minflt: u64 = 0; let cminflt: u64 = 0;
1296 let majflt: u64 = 0;
1297 let cmajflt: u64 = 0;
1298 let utime: i64;
1299 let stime: i64; let cutime: i64;
1301 let cstime: i64;
1302 let mut priority: i64;
1303 let nice: i64;
1304 let num_threads: i64; let itrealvalue: i64 = 0;
1306 let mut starttime: u64 = 0;
1307 let mut vsize: usize = 0;
1308 let mut rss: usize = 0;
1309 let mut rsslim: u64 = 0; let mut startcode: u64 = 0;
1311 let mut endcode: u64 = 0;
1312 let mut startstack: usize = 0;
1313 let mut kstkesp: u64 = 0;
1314 let mut kstkeip: u64 = 0; let signal: u64 = 0;
1316 let blocked: u64 = 0;
1317 let siginore: u64 = 0;
1318 let sigcatch: u64 = 0;
1319 let mut wchan: u64 = 0; let nswap: u64 = 0;
1321 let cnswap: u64 = 0;
1322 let exit_signal: i32 = 0;
1323 let processor: i32 = 0;
1324 let rt_priority: u32 = 0; let policy: u32 = 0;
1326 let delayacct_blkio_ticks: u64 = 0;
1327 let guest_time: u64 = 0;
1328 let cguest_time: i64 = 0;
1329 let mut start_data: u64 = 0; let mut end_data: u64 = 0;
1331 let mut start_brk: u64 = 0;
1332 let mut arg_start: usize = 0;
1333 let mut arg_end: usize = 0;
1334 let mut env_start: usize = 0; let mut env_end: usize = 0;
1336 let mut exit_code: i32 = 0;
1337
1338 pid = self.target.tid.id;
1339 comm = task.command();
1340 state = task.state_code().code_char();
1341 {
1342 let scheduler_state = task.scheduler_state.lock();
1343 priority = scheduler_state.task_priority();
1344 nice = scheduler_state.normal_priority().as_nice() as i64;
1345 }
1346 if let Some(boost) =
1347 task.kernel().shared_futexes.get_rt_mutex_priority_boost(task.get_tid() as u32)
1348 {
1349 priority = std::cmp::min(priority, boost);
1350 }
1351
1352 {
1353 let thread_group = task.thread_group();
1354 let tg_state = thread_group.read();
1355 ppid = tg_state.get_ppid();
1356 pgrp = thread_group.pgid().id;
1357 session = tg_state.session.id;
1358
1359 tty_nr = tg_state
1361 .controlling_terminal
1362 .as_ref()
1363 .map(|t| t.terminal.device().bits())
1364 .unwrap_or(0) as i32;
1365
1366 cutime = duration_to_scheduler_clock(tg_state.children_time_stats.user_time);
1367 cstime = duration_to_scheduler_clock(tg_state.children_time_stats.system_time);
1368
1369 num_threads = std::cmp::max(1, tg_state.tasks_count()) as i64;
1370 }
1371
1372 let time_stats = match self.target.scope {
1373 TaskEntryScope::Task => task.time_stats(),
1374 TaskEntryScope::ThreadGroup => task.thread_group().time_stats(),
1375 };
1376 utime = duration_to_scheduler_clock(time_stats.user_time);
1377 stime = duration_to_scheduler_clock(time_stats.system_time);
1378
1379 if !task.thread_group().process.is_invalid() {
1380 starttime = duration_to_scheduler_clock(
1381 task.thread_group().start_time - zx::MonotonicInstant::ZERO,
1382 ) as u64;
1383 }
1384
1385 if let Ok(mm) = task.mm() {
1386 let mem_stats = if should_skip_memory_stats(current_task) {
1388 stub_memory_stats()
1389 } else {
1390 mm.get_stats(current_task, SelfStatsCacheMode::BypassCache)
1391 };
1392 let page_size = *PAGE_SIZE as usize;
1393 vsize = mem_stats.vm_size;
1394 rss = mem_stats.vm_rss / page_size;
1395 rsslim = task.thread_group().limits.lock().get(Resource::RSS).rlim_max;
1396
1397 {
1398 let mm_state = mm.state.read();
1399 startstack = mm_state.stack_start.ptr();
1400 arg_start = mm_state.argv_start.ptr();
1401 arg_end = mm_state.argv_end.ptr();
1402 env_start = mm_state.environ_start.ptr();
1403 env_end = mm_state.environ_end.ptr();
1404 }
1405 }
1406
1407 if !current_task
1411 .check_ptrace_access_mode(PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT, &task)
1412 .is_ok()
1413 {
1414 startcode = 1;
1415 endcode = 1;
1416 startstack = 0;
1417 kstkesp = 0;
1418 kstkeip = 0;
1419 wchan = 0;
1420 start_data = 0;
1421 end_data = 0;
1422 start_brk = 0;
1423 arg_start = 0;
1424 arg_end = 0;
1425 env_start = 0;
1426 env_end = 0;
1427 exit_code = 0;
1428 }
1429
1430 write!(sink, "{pid} (")?;
1431 sink.write(comm.comm_name());
1432 writeln!(
1433 sink,
1434 ") {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}"
1435 )?;
1436
1437 Ok(())
1438 }
1439}
1440
1441#[derive(Clone)]
1442pub struct StatmFile {
1443 target: TaskContainer,
1444}
1445impl StatmFile {
1446 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1447 DynamicFile::new_node(Self { target })
1448 }
1449}
1450impl DynamicFileSource for StatmFile {
1451 fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1452 let task = self.target.get_task()?;
1454 let mem_stats = if should_skip_memory_stats(current_task) {
1456 stub_memory_stats()
1457 } else {
1458 match task.mm() {
1459 Ok(mm) => mm.get_stats(current_task, SelfStatsCacheMode::BypassCache),
1460 Err(_) => Default::default(),
1461 }
1462 };
1463 let page_size = *PAGE_SIZE as usize;
1464
1465 writeln!(
1467 sink,
1468 "{} {} {} {} 0 {} 0",
1469 mem_stats.vm_size / page_size,
1470 mem_stats.vm_rss / page_size,
1471 mem_stats.rss_shared / page_size,
1472 mem_stats.vm_exe / page_size,
1473 (mem_stats.vm_data + mem_stats.vm_stack) / page_size
1474 )?;
1475 Ok(())
1476 }
1477}
1478
1479#[derive(Clone)]
1480pub struct StatusFile {
1481 target: TaskContainer,
1482}
1483impl StatusFile {
1484 pub fn new_node(target: TaskContainer) -> impl FsNodeOps {
1485 DynamicFile::new_node(Self { target })
1486 }
1487}
1488impl DynamicFileSource for StatusFile {
1489 fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1490 let start_monotonic = zx::MonotonicInstant::get();
1491 let start_boot = zx::BootInstant::get();
1492 let task = self.target.get_task()?;
1493 let creds_string = {
1494 write!(sink, "Name:\t")?;
1498 sink.write(task.persistent_info.command_guard().comm_name());
1499 let creds = task.persistent_info.real_creds();
1500 format!(
1501 "Uid:\t{}\t{}\t{}\t{}\nGid:\t{}\t{}\t{}\t{}\nGroups:\t{}",
1502 creds.uid,
1503 creds.euid,
1504 creds.saved_uid,
1505 creds.fsuid,
1506 creds.gid,
1507 creds.egid,
1508 creds.saved_gid,
1509 creds.fsgid,
1510 creds.groups.iter().map(|n| n.to_string()).join(" ")
1511 )
1512 };
1513
1514 writeln!(sink)?;
1515
1516 if let Ok(fs) = task.fs() {
1517 writeln!(sink, "Umask:\t0{:03o}", fs.umask().bits())?;
1518 }
1519 {
1520 let task_state = task.read();
1521 writeln!(sink, "SigBlk:\t{:016x}", task_state.signal_mask().0)?;
1522 writeln!(sink, "SigPnd:\t{:016x}", task_state.task_specific_pending_signals().0)?;
1523 writeln!(
1524 sink,
1525 "ShdPnd:\t{:016x}",
1526 task.thread_group().pending_signals.lock().pending().0
1527 )?;
1528
1529 let (sigign, sigcgt) = task.thread_group().signal_actions.ignored_and_caught();
1530 writeln!(sink, "SigIgn:\t{:016x}", sigign.0)?;
1531 writeln!(sink, "SigCgt:\t{:016x}", sigcgt.0)?;
1532
1533 writeln!(sink, "NoNewPrivs:\t{}", task_state.no_new_privs() as u8)?;
1534 }
1535
1536 let creds = task.real_creds();
1538 writeln!(sink, "CapInh:\t{:016x}", creds.cap_inheritable)?;
1539 writeln!(sink, "CapPrm:\t{:016x}", creds.cap_permitted)?;
1540 writeln!(sink, "CapEff:\t{:016x}", creds.cap_effective)?;
1541 writeln!(sink, "CapBnd:\t{:016x}", creds.cap_bounding)?;
1542 writeln!(sink, "CapAmb:\t{:016x}", creds.cap_ambient)?;
1543
1544 let state_code = task.state_code();
1545 writeln!(sink, "State:\t{} ({})", state_code.code_char(), state_code.name())?;
1546
1547 writeln!(sink, "Tgid:\t{}", task.pid)?;
1548 writeln!(sink, "Pid:\t{}", self.target.tid)?;
1549 let (ppid, threads, tracer_pid) = {
1550 let tracer_pid =
1551 task.read().ptrace.as_ref().map_or(0, |p| {
1552 p.core_state.thread_group.upgrade().map_or(0, |tg| tg.leader.id)
1553 });
1554 let task_group = task.thread_group().read();
1555 (task_group.get_ppid(), task_group.tasks_count(), tracer_pid)
1556 };
1557 writeln!(sink, "PPid:\t{}", ppid)?;
1558 writeln!(sink, "TracerPid:\t{}", tracer_pid)?;
1559
1560 writeln!(sink, "{}", creds_string)?;
1561
1562 if let Ok(mm) = task.mm() {
1563 let mem_stats = if should_skip_memory_stats(current_task) {
1565 stub_memory_stats()
1566 } else {
1567 mm.get_stats(current_task, SelfStatsCacheMode::AllowCached)
1568 };
1569 writeln!(sink, "VmSize:\t{} kB", mem_stats.vm_size / 1024)?;
1570 writeln!(sink, "VmLck:\t{} kB", mem_stats.vm_lck / 1024)?;
1571 writeln!(sink, "VmRSS:\t{} kB", mem_stats.vm_rss / 1024)?;
1572 writeln!(sink, "RssAnon:\t{} kB", mem_stats.rss_anonymous / 1024)?;
1573 writeln!(sink, "RssFile:\t{} kB", mem_stats.rss_file / 1024)?;
1574 writeln!(sink, "RssShmem:\t{} kB", mem_stats.rss_shared / 1024)?;
1575 writeln!(sink, "VmData:\t{} kB", mem_stats.vm_data / 1024)?;
1576 writeln!(sink, "VmStk:\t{} kB", mem_stats.vm_stack / 1024)?;
1577 writeln!(sink, "VmExe:\t{} kB", mem_stats.vm_exe / 1024)?;
1578 writeln!(sink, "VmSwap:\t{} kB", mem_stats.vm_swap / 1024)?;
1579 writeln!(sink, "VmHWM:\t{} kB", mem_stats.vm_rss_hwm / 1024)?;
1580 }
1581 let seccomp = task.seccomp_filter_state.get() as u8;
1583 writeln!(sink, "Seccomp:\t{}", seccomp)?;
1584
1585 writeln!(sink, "Threads:\t{}", std::cmp::max(1, threads))?;
1587
1588 let elapsed_monotonic = zx::MonotonicInstant::get() - start_monotonic;
1589 let elapsed_boot = zx::BootInstant::get() - start_boot;
1590 if elapsed_monotonic > zx::MonotonicDuration::from_millis(100)
1591 || elapsed_boot > zx::BootDuration::from_seconds(1)
1592 {
1593 let target_pid = task.pid.id;
1594 let target_comm =
1595 String::from_utf8_lossy(task.persistent_info.command_guard().comm_name())
1596 .into_owned();
1597 starnix_logging::log_warn!(
1598 "StatusFile::generate for task {} ({}) took {} ms (monotonic), {} ms (boot)",
1599 target_pid,
1600 target_comm,
1601 elapsed_monotonic.into_millis(),
1602 elapsed_boot.into_millis()
1603 );
1604 }
1605
1606 Ok(())
1607 }
1608}
1609
1610struct OomScoreFile {
1611 target: TaskContainer,
1612}
1613
1614impl OomScoreFile {
1615 fn new_node(target: TaskContainer) -> impl FsNodeOps {
1616 BytesFile::new_node(Self { target })
1617 }
1618}
1619
1620impl BytesFileOps for OomScoreFile {
1621 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1622 let _task = self.target.get_task()?;
1623 track_stub!(TODO("https://fxbug.dev/322873459"), "/proc/pid/oom_score");
1624 Ok(serialize_for_file(0).into())
1625 }
1626}
1627
1628const OOM_ADJUST_MAX: i32 = uapi::OOM_ADJUST_MAX as i32;
1630const OOM_SCORE_ADJ_MAX: i32 = uapi::OOM_SCORE_ADJ_MAX as i32;
1631
1632struct OomAdjFile {
1633 target: TaskContainer,
1634}
1635impl OomAdjFile {
1636 fn new_node(target: TaskContainer) -> impl FsNodeOps {
1637 BytesFile::new_node(Self { target })
1638 }
1639}
1640
1641impl BytesFileOps for OomAdjFile {
1642 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1643 let value = parse_i32_file(&data)?;
1644 let oom_score_adj = if value == OOM_DISABLE {
1645 OOM_SCORE_ADJ_MIN
1646 } else {
1647 if !(OOM_ADJUST_MIN..=OOM_ADJUST_MAX).contains(&value) {
1648 return error!(EINVAL);
1649 }
1650 let fraction = (value - OOM_ADJUST_MIN) / (OOM_ADJUST_MAX - OOM_ADJUST_MIN);
1651 fraction * (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN) + OOM_SCORE_ADJ_MIN
1652 };
1653 security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1654 let task = self.target.get_task()?;
1655 task.write().oom_score_adj = oom_score_adj;
1656 Ok(())
1657 }
1658
1659 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1660 let task = self.target.get_task()?;
1661 let oom_score_adj = task.read().oom_score_adj;
1662 let oom_adj = if oom_score_adj == OOM_SCORE_ADJ_MIN {
1663 OOM_DISABLE
1664 } else {
1665 let fraction =
1666 (oom_score_adj - OOM_SCORE_ADJ_MIN) / (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN);
1667 fraction * (OOM_ADJUST_MAX - OOM_ADJUST_MIN) + OOM_ADJUST_MIN
1668 };
1669 Ok(serialize_for_file(oom_adj).into())
1670 }
1671}
1672
1673struct OomScoreAdjFile {
1674 target: TaskContainer,
1675}
1676
1677impl OomScoreAdjFile {
1678 fn new_node(target: TaskContainer) -> impl FsNodeOps {
1679 BytesFile::new_node(Self { target })
1680 }
1681}
1682
1683impl BytesFileOps for OomScoreAdjFile {
1684 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1685 let value = parse_i32_file(&data)?;
1686 if !(OOM_SCORE_ADJ_MIN..=OOM_SCORE_ADJ_MAX).contains(&value) {
1687 return error!(EINVAL);
1688 }
1689 security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1690 let task = self.target.get_task()?;
1691 task.write().oom_score_adj = value;
1692 Ok(())
1693 }
1694
1695 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1696 let task = self.target.get_task()?;
1697 let oom_score_adj = task.read().oom_score_adj;
1698 Ok(serialize_for_file(oom_score_adj).into())
1699 }
1700}
1701
1702struct TimerslackNsFile {
1703 target: TaskContainer,
1704}
1705
1706impl TimerslackNsFile {
1707 fn new_node(target: TaskContainer) -> impl FsNodeOps {
1708 BytesFile::new_node(Self { target })
1709 }
1710}
1711
1712impl BytesFileOps for TimerslackNsFile {
1713 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1714 let target_task = self.target.get_task()?;
1715 let same_task = current_task.task.pid == target_task.pid;
1716 if !same_task {
1717 security::check_task_capable(current_task, CAP_SYS_NICE)?;
1718 security::check_task_setscheduler_access(current_task, &target_task)?;
1719 };
1720
1721 let value = parse_unsigned_file(&data)?;
1722 target_task.write().set_timerslack_ns(value);
1723 Ok(())
1724 }
1725
1726 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1727 let target_task = self.target.get_task()?;
1728 let same_task = current_task.task.pid == target_task.pid;
1729 if !same_task {
1730 security::check_task_capable(current_task, CAP_SYS_NICE)?;
1731 security::check_task_getscheduler_access(current_task, &target_task)?;
1732 };
1733
1734 let timerslack_ns = target_task.read().timerslack_ns;
1735 Ok(serialize_for_file(timerslack_ns).into())
1736 }
1737}
1738
1739struct ClearRefsFile {
1740 target: TaskContainer,
1741}
1742
1743impl ClearRefsFile {
1744 fn new_node(target: TaskContainer) -> impl FsNodeOps {
1745 BytesFile::new_node(Self { target })
1746 }
1747}
1748
1749impl BytesFileOps for ClearRefsFile {
1750 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
1751 let _task = self.target.get_task()?;
1752 track_stub!(TODO("https://fxbug.dev/396221597"), "/proc/pid/clear_refs");
1753 Ok(())
1754 }
1755}
1756#[cfg(test)]
1757mod tests {
1758 use super::*;
1759 use starnix_core::testing::{anon_test_file, spawn_kernel_and_run};
1760 use starnix_core::vfs::VecOutputBuffer;
1761
1762 #[::fuchsia::test]
1763 async fn test_fdinfo_file_lazy_and_cloexec() {
1764 spawn_kernel_and_run(async move |current_task| {
1765 let target_file = anon_test_file(
1766 current_task,
1767 Box::new(BytesFile::new(b"0123456789".to_vec())),
1768 OpenFlags::RDONLY,
1769 );
1770 let fd = current_task
1771 .files()
1772 .add(current_task, target_file.clone(), FdFlags::empty())
1773 .expect("add fd");
1774
1775 let fdinfo_file = anon_test_file(
1776 current_task,
1777 Box::new(DynamicFile::new(FdInfoFile {
1778 target: TaskContainer::from_task(¤t_task.task),
1779 fd,
1780 })),
1781 OpenFlags::RDONLY,
1782 );
1783
1784 let mut buf = VecOutputBuffer::new(256);
1786 let n = fdinfo_file.read_at(current_task, 0, &mut buf).expect("read fdinfo");
1787 let text = String::from_utf8(buf.data()[..n].to_vec()).unwrap();
1788 let expected_initial = format!("pos:\t0\nflags:\t0{:o}\n", OpenFlags::RDONLY.bits());
1789 assert_eq!(text, expected_initial);
1790
1791 target_file.seek(current_task, SeekTarget::Set(5)).expect("seek");
1793 current_task
1794 .files()
1795 .set_fd_flags_allowing_opath(fd, FdFlags::CLOEXEC)
1796 .expect("set cloexec");
1797
1798 let mut buf2 = VecOutputBuffer::new(256);
1799 let n2 = fdinfo_file.read_at(current_task, 0, &mut buf2).expect("read fdinfo after");
1800 let text2 = String::from_utf8(buf2.data()[..n2].to_vec()).unwrap();
1801 let expected_updated = format!(
1802 "pos:\t5\nflags:\t0{:o}\n",
1803 (OpenFlags::RDONLY | OpenFlags::CLOEXEC).bits()
1804 );
1805 assert_eq!(text2, expected_updated);
1806
1807 current_task.files().close(fd).expect("close fd");
1810 let mut buf3 = VecOutputBuffer::new(256);
1811 assert_eq!(fdinfo_file.read_at(current_task, 0, &mut buf3), error!(ENOENT));
1812 })
1813 .await;
1814 }
1815}