Skip to main content

starnix_modules_procfs/
pid_directory.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
53/// Loads entries for the `scope` of a task.
54fn task_entries(scope: TaskEntryScope) -> Vec<(FsString, FileMode)> {
55    // NOTE: keep entries in sync with `TaskDirectory::lookup()`.
56    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
104/// Represents a directory node for either `/proc/<pid>` or `/proc/<pid>/task/<tid>`.
105///
106/// This directory lazily creates its child entries to save memory.
107///
108/// It pre-allocates a range of inode numbers (`inode_range`) for all its child entries to mark
109/// them as unchanged when re-accessed.
110/// The `creds` stored within is applied to the directory node itself and child entries.
111pub 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        // NOTE: keep entries in sync with `task_entries()`.
181        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
284/// `TaskDirectory` doesn't implement the `close` method.
285impl 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        // Skip through the entries until the current offset is reached.
300        // Subtract 2 from the offset to account for `.` and `..`.
301        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
319/// Creates an [`FsNode`] that represents the `/proc/<pid>` directory for `task`.
320pub fn pid_directory(
321    current_task: &CurrentTask,
322    fs: &FileSystemHandle,
323    task: &Arc<Task>,
324) -> FsNodeHandle {
325    // proc(5): "The files inside each /proc/pid directory are normally
326    // owned by the effective user and effective group ID of the process."
327    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
333/// Creates an [`FsNode`] that represents the `/proc/<pid>/task/<tid>` directory for `task`.
334fn tid_directory(fs: &FileSystemHandle, task: &Arc<Task>) -> FsNodeHandle {
335    TaskDirectory::new(fs, task, TaskEntryScope::Task)
336}
337
338/// `FdDirectory` implements the directory listing operations for a `proc/<pid>/fd` directory.
339///
340/// Reading the directory returns a list of all the currently open file descriptors for the
341/// associated task.
342struct 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        // Make sure that the file descriptor exists before creating the node.
375        let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
376        // Derive the symlink's mode from the mode in which the file was opened.
377        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
403/// /proc/<pid>/attr directory entry.
404struct 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 the current task is not the target then writes are not allowed.
445        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
459/// /proc/[pid]/ns directory
460struct 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        // For each namespace, this contains a link to the current identifier of the given namespace
480        // for the current task.
481        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        // If name is a given namespace, link to the current identifier of the that namespace for
500        // the current task.
501        // If name is {namespace}:[id], get a file descriptor for the given namespace.
502
503        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        // The name doesn't starts with a known namespace.
507        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            // The name starts with {namespace}:, check that it matches {namespace}:[id]
514            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            // The name is {namespace}, link to the correct one of the current task.
570            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
581/// `FdInfoDirectory` implements the directory listing operations for a `proc/<pid>/fdinfo`
582/// directory.
583///
584/// Reading the directory returns a list of all the currently open file descriptors for the
585/// associated task.
586struct 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
645/// Directory that lists the task IDs (tid) in a process. Located at `/proc/<pid>/task/`.
646struct 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        // Make sure the tid belongs to this process.
696        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    // NB: If this is exercised in a hot-path, we can plumb the reading task
749    // (`CurrentTask`) here to perform a copy without going through the VMO when
750    // unified aspaces is enabled.
751    let buf = task.read_memory_partial_to_vec(range_start, len)?;
752    sink.write(&buf[..]);
753    Ok(())
754}
755
756/// `CmdlineFile` implements `proc/<pid>/cmdline` file.
757#[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        // Opened cmdline file should still be functional once the task is a zombie.
773        let Ok(task) = self.tid.get_task() else {
774            return Ok(());
775        };
776        // /proc/<pid>/cmdline is empty for kthreads.
777        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            // proc-pid nodes for kthreads do not require ptrace access checks.
799            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/// `EnvironFile` implements `proc/<pid>/environ` file.
808#[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        // /proc/<pid>/environ is empty for kthreads.
827        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/// `AuxvFile` implements `proc/<pid>/auxv` file.
839#[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        // /proc/<pid>/auxv is empty for kthreads.
858        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
869/// `CommFile` implements `proc/<pid>/comm` file.
870pub 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(), &current_task.thread_group()) {
901            return error!(EINVAL);
902        }
903        // What happens if userspace writes to this file in multiple syscalls? We need more
904        // detailed tests to see when the data is actually committed back to the task.
905        let bytes = data.read_all()?;
906        task.set_command_name(TaskCommand::new(&bytes));
907        Ok(bytes.len())
908    }
909}
910
911/// `IoFile` implements `proc/<pid>/io` file.
912#[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/// `LimitsFile` implements `proc/<pid>/limits` file.
938#[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
982/// `MemFile` implements `proc/<pid>/mem` file.
983pub struct MemFile {
984    mm: Weak<MemoryManager>,
985
986    // TODO: https://fxbug.dev/442459337 - Tear-down MemoryManager internals on process exit, to
987    // avoid extension of the MM lifetime prolonging access to memory via "/proc/pid/mem", etc
988    // beyond that of the actual process/address-space.
989    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
1077// Workaround for b/525059309: Zircon VMAR walks (`ZX_INFO_VMAR_MAPS`) are extremely
1078// slow and cause Perfetto's `traced_probes` watchdog timeouts when sweeping thread status
1079// files. We bypass them when the reader is `traced_probes` by returning a 1 page/KB stub.
1080fn 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        // All fields and their types as specified in the man page.
1119        // Unimplemented fields are set to 0 here.
1120        let pid: pid_t; // 1
1121        let comm: TaskCommand;
1122        let state: char;
1123        let ppid: pid_t;
1124        let pgrp: pid_t; // 5
1125        let session: pid_t;
1126        let tty_nr: i32;
1127        let tpgid: i32 = 0;
1128        let flags: u32 = 0;
1129        let minflt: u64 = 0; // 10
1130        let cminflt: u64 = 0;
1131        let majflt: u64 = 0;
1132        let cmajflt: u64 = 0;
1133        let utime: i64;
1134        let stime: i64; // 15
1135        let cutime: i64;
1136        let cstime: i64;
1137        let priority: i64 = 0;
1138        let nice: i64;
1139        let num_threads: i64; // 20
1140        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; // 25
1145        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; // 30
1150        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; // 35
1155        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; // 40
1160        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; // 45
1165        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; // 50
1170        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            // TTY device ID.
1185            {
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            // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1214            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        // The man page describes that the following fields have "... values displayed as 0" if the
1235        // caller does not have ptrace read access to the target.
1236        // In practice the `startcode` and `endcode` fields appear to be displayed as 1.
1237        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        // /proc/<pid>/statm reports zeroes for kthreads.
1278        let task = self.tid.get_task()?;
1279        // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1280        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        // 5th and 7th fields are deprecated and should be set to 0.
1291        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                // Collect everything stored in info in this block.  There is a lock ordering
1323                // issue with the task lock acquired below, and cloning info is
1324                // expensive.
1325                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            // Since version 3.8 all nonexistent capabilities are reported as not-enabled.
1366            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                // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1404                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            // Report seccomp filter status.
1422            let seccomp = task.seccomp_filter_state.get() as u8;
1423            writeln!(sink, "Seccomp:\t{}", seccomp)?;
1424        }
1425
1426        // There should be at least one thread in Zombie processes.
1427        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
1473// Redefine these constants as i32 to avoid conversions below.
1474const 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}